1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
// TODO(philc): Import JS modules here.
const ActionPage = {
async init() {
// Is it possible for the current tab's URL to change while this action popup is open?
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const activeTab = tabs[0];
this.tabUrl = activeTab.url;
const hideUI = () => {
document.querySelector("#dialogBody").style.display = "none";
document.querySelector("#footer").style.display = "none";
};
// In Firefox, prompt the user if they haven't enabled the "all hosts" permission. Vimium needs
// this permission to work correctly, and as of 2023-11-06, Firefox does not grant this
// permission without user consent, and doesn't make it clear that the user needs to do
// anything. See #4348 for discussion, and https://stackoverflow.com/q/76083327 for
// implementation notes.
const permission = { origins: ["<all_urls>"] };
if (BgUtils.isFirefox()) {
const hasAllHostsPermission = await browser.permissions.contains(permission);
if (!hasAllHostsPermission) {
hideUI();
document.querySelector("#grant-hosts-permission").addEventListener("click", async (e) => {
browser.permissions.request(permission);
// We close the action page because if the user clicks on this button once, clicks "deny"
// on the browser's permissions dialog, and then clicks on the button a second time, the
// browser permissions dialog will now be shown *under* the action page!
window.close();
});
document.querySelector("#firefoxMissingPermissionsError").style.display = "block";
return;
}
}
if (!await this.isVimiumInstalledInTab(activeTab.id)) {
hideUI();
document.querySelector("#notEnabledError").style.display = "block";
return;
}
document.querySelector("#optionsLink").href = chrome.runtime.getURL("pages/options.html");
const saveOptionsEl = document.querySelector("#saveOptions");
saveOptionsEl.addEventListener("click", (e) => this.onSave());
document.querySelector("#cancel").addEventListener("click", () => window.close());
const onUpdated = () => {
saveOptionsEl.disabled = false;
saveOptionsEl.textContent = "Save changes";
this.syncEnabledKeysCaption();
this.showValidationErrors();
};
const defaultPatternForNewRules = this.generateDefaultPattern(this.tabUrl);
document.querySelector("#addFirstRule").addEventListener(
"click",
() => {
ExclusionRulesEditor.addRow(defaultPatternForNewRules);
this.showExclusionRulesEditor();
onUpdated();
},
);
ExclusionRulesEditor.defaultPatternForNewRules = defaultPatternForNewRules;
ExclusionRulesEditor.init();
ExclusionRulesEditor.addEventListener("input", onUpdated);
const rules = Settings.get("exclusionRules").filter((r) =>
this.tabUrl.match(this.getPatternRegExp(r.pattern))
);
ExclusionRulesEditor.setForm(rules);
this.syncEnabledKeysCaption();
if (rules.length > 0) this.showExclusionRulesEditor();
},
async isVimiumInstalledInTab(tabId) {
try {
// There is no handler in our content script for this message, but that's OK. We just want to
// see if sending any message triggers an error.
await chrome.tabs.sendMessage(tabId, { handler: "isVimiumInstalledInTab" });
return true;
} catch {
// If there's no content script running in the activeTab, we'll get a connection error.
return false;
}
},
showValidationErrors() {
const rows = document.querySelectorAll(".rule");
for (const row of rows) {
const pattern = row.querySelector("input[name=pattern]").value;
const regExp = this.getPatternRegExp(pattern);
const validationEl = row.querySelector(".validationMessage");
const patternMatchesUrl = this.tabUrl.match(regExp);
if (patternMatchesUrl) {
row.classList.remove("validationError");
validationEl.innerText = "";
} else {
row.classList.add("validationError");
validationEl.innerText = "Pattern does not match the current URL";
}
}
},
showExclusionRulesEditor() {
document.querySelector("#exclusionsContainer").style.display = "block";
document.querySelector("#addFirstRuleContainer").style.display = "none";
},
syncEnabledKeysCaption() {
let caption = "All";
const rules = ExclusionRulesEditor.getRules();
if (rules.length > 0) {
const hasBlankPassKeysRule = rules.find((r) => r.passKeys.length == 0);
caption = hasBlankPassKeysRule ? "No" : "Some";
}
document.querySelector("#howManyEnabled").innerText = caption;
},
async onSave() {
let rules = await Settings.get("exclusionRules");
// Remove any rules which match the current URL, and replace them with the contents of this dialog.
rules = rules.filter((r) => !this.tabUrl.match(this.getPatternRegExp(r.pattern)));
rules = rules.concat(ExclusionRulesEditor.getRules());
Settings.set("exclusionRules", rules);
const el = document.querySelector("#saveOptions");
el.disabled = true;
el.textContent = "Saved";
},
getPatternRegExp(patternStr) {
return new RegExp("^" + patternStr.replace(/\*/g, ".*") + "$");
},
// Returns an exclusion pattern which matches the domain of the given URL.
// This is used as the default starter pattern when the "Add rule" button is clicked.
generateDefaultPattern(url) {
if (/^https?:\/\/./.test(url)) {
// The common use case is to disable Vimium at the domain level.
// Generate "https?://www.example.com/*" from "http://www.example.com/path/to/page.html".
// Note: IPV6 host addresses will contain "[" and "]" (which must be escaped).
const hostname = url.split("/", 3).slice(1).join("/").replace("[", "\\[").replace(
"]",
"\\]",
);
return "https?:/" + hostname + "/*";
} else if (/^[a-z]{3,}:\/\/./.test(url)) {
// Anything else which seems to be a URL.
return url.split("/", 3).join("/") + "/*";
} else {
return url + "*";
}
},
};
document.addEventListener("DOMContentLoaded", async () => {
await Settings.onLoaded();
ActionPage.init();
});
|