mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
92862ecb60
Differential Revision: https://phabricator.services.mozilla.com/D218593
817 lines
25 KiB
JavaScript
817 lines
25 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
AMBrowserExtensionsImport: "resource://gre/modules/AddonManager.sys.mjs",
|
|
AMTelemetry: "resource://gre/modules/AddonManager.sys.mjs",
|
|
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
|
|
AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
|
|
AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.sys.mjs",
|
|
ExtensionData: "resource://gre/modules/Extension.sys.mjs",
|
|
ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.sys.mjs",
|
|
OriginControls: "resource://gre/modules/ExtensionPermissions.sys.mjs",
|
|
QuarantinedDomains: "resource://gre/modules/ExtensionPermissions.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(
|
|
lazy,
|
|
"l10n",
|
|
() =>
|
|
new Localization(["browser/extensionsUI.ftl", "branding/brand.ftl"], true)
|
|
);
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "logConsole", () =>
|
|
console.createInstance({
|
|
prefix: "ExtensionsUI",
|
|
maxLogLevelPref: "extensions.webextensions.log.level",
|
|
})
|
|
);
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"POSTINSTALL_PRIVATEBROWSING_CHECKBOX",
|
|
"extensions.ui.postInstallPrivateBrowsingCheckbox",
|
|
false
|
|
);
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"SHOW_FULL_DOMAINS_LIST",
|
|
"extensions.ui.installDialogFullDomains",
|
|
true
|
|
);
|
|
|
|
const DEFAULT_EXTENSION_ICON =
|
|
"chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
|
|
|
function getTabBrowser(browser) {
|
|
while (browser.ownerGlobal.docShell.itemType !== Ci.nsIDocShell.typeChrome) {
|
|
browser = browser.ownerGlobal.docShell.chromeEventHandler;
|
|
}
|
|
let window = browser.ownerGlobal;
|
|
let viewType = browser.getAttribute("webextension-view-type");
|
|
if (viewType == "sidebar") {
|
|
window = window.browsingContext.topChromeWindow;
|
|
}
|
|
if (viewType == "popup" || viewType == "sidebar") {
|
|
browser = window.gBrowser.selectedBrowser;
|
|
}
|
|
return { browser, window };
|
|
}
|
|
|
|
export var ExtensionsUI = {
|
|
sideloaded: new Set(),
|
|
updates: new Set(),
|
|
sideloadListener: null,
|
|
|
|
pendingNotifications: new WeakMap(),
|
|
|
|
get SHOW_FULL_DOMAINS_LIST() {
|
|
return lazy.SHOW_FULL_DOMAINS_LIST;
|
|
},
|
|
|
|
get POSTINSTALL_PRIVATEBROWSING_CHECKBOX() {
|
|
return lazy.POSTINSTALL_PRIVATEBROWSING_CHECKBOX;
|
|
},
|
|
|
|
async init() {
|
|
Services.obs.addObserver(this, "webextension-permission-prompt");
|
|
Services.obs.addObserver(this, "webextension-update-permissions");
|
|
Services.obs.addObserver(this, "webextension-install-notify");
|
|
Services.obs.addObserver(this, "webextension-optional-permission-prompt");
|
|
Services.obs.addObserver(this, "webextension-defaultsearch-prompt");
|
|
Services.obs.addObserver(this, "webextension-imported-addons-cancelled");
|
|
Services.obs.addObserver(this, "webextension-imported-addons-complete");
|
|
Services.obs.addObserver(this, "webextension-imported-addons-pending");
|
|
|
|
await Services.wm.getMostRecentWindow("navigator:browser")
|
|
.delayedStartupPromise;
|
|
|
|
this._checkForSideloaded();
|
|
},
|
|
|
|
async _checkForSideloaded() {
|
|
let sideloaded = await lazy.AddonManagerPrivate.getNewSideloads();
|
|
|
|
if (!sideloaded.length) {
|
|
// No new side-loads. We're done.
|
|
return;
|
|
}
|
|
|
|
// The ordering shouldn't matter, but tests depend on notifications
|
|
// happening in a specific order.
|
|
sideloaded.sort((a, b) => a.id.localeCompare(b.id));
|
|
|
|
if (!this.sideloadListener) {
|
|
this.sideloadListener = {
|
|
onEnabled: addon => {
|
|
if (!this.sideloaded.has(addon)) {
|
|
return;
|
|
}
|
|
|
|
this.sideloaded.delete(addon);
|
|
this._updateNotifications();
|
|
|
|
if (this.sideloaded.size == 0) {
|
|
lazy.AddonManager.removeAddonListener(this.sideloadListener);
|
|
this.sideloadListener = null;
|
|
}
|
|
},
|
|
};
|
|
lazy.AddonManager.addAddonListener(this.sideloadListener);
|
|
}
|
|
|
|
for (let addon of sideloaded) {
|
|
this.sideloaded.add(addon);
|
|
}
|
|
this._updateNotifications();
|
|
},
|
|
|
|
_updateNotifications() {
|
|
const { sideloaded, updates } = this;
|
|
const { importedAddonIDs } = lazy.AMBrowserExtensionsImport;
|
|
|
|
if (importedAddonIDs.length + sideloaded.size + updates.size == 0) {
|
|
lazy.AppMenuNotifications.removeNotification("addon-alert");
|
|
} else {
|
|
lazy.AppMenuNotifications.showBadgeOnlyNotification("addon-alert");
|
|
}
|
|
this.emit("change");
|
|
},
|
|
|
|
showAddonsManager(
|
|
tabbrowser,
|
|
strings,
|
|
icon,
|
|
addon = undefined,
|
|
shouldShowIncognitoCheckbox = false
|
|
) {
|
|
let global = tabbrowser.selectedBrowser.ownerGlobal;
|
|
return global.BrowserAddonUI.openAddonsMgr("addons://list/extension").then(
|
|
aomWin => {
|
|
let aomBrowser = aomWin.docShell.chromeEventHandler;
|
|
return this.showPermissionsPrompt(
|
|
aomBrowser,
|
|
strings,
|
|
icon,
|
|
addon,
|
|
shouldShowIncognitoCheckbox
|
|
);
|
|
}
|
|
);
|
|
},
|
|
|
|
showSideloaded(tabbrowser, addon) {
|
|
addon.markAsSeen();
|
|
this.sideloaded.delete(addon);
|
|
this._updateNotifications();
|
|
|
|
let strings = this._buildStrings({
|
|
addon,
|
|
permissions: addon.installPermissions,
|
|
type: "sideload",
|
|
});
|
|
|
|
lazy.AMTelemetry.recordManageEvent(addon, "sideload_prompt", {
|
|
num_strings: strings.msgs.length,
|
|
});
|
|
|
|
this.showAddonsManager(
|
|
tabbrowser,
|
|
strings,
|
|
addon.iconURL,
|
|
addon,
|
|
true /* shouldShowIncognitoCheckbox */
|
|
).then(async answer => {
|
|
if (answer) {
|
|
await addon.enable();
|
|
|
|
this._updateNotifications();
|
|
|
|
// The user has just enabled a sideloaded extension, if the permission
|
|
// can be changed for the extension, show the post-install panel to
|
|
// give the user that opportunity.
|
|
if (
|
|
ExtensionsUI.POSTINSTALL_PRIVATEBROWSING_CHECKBOX &&
|
|
addon.permissions &
|
|
lazy.AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS
|
|
) {
|
|
this.showInstallNotification(tabbrowser.selectedBrowser, addon);
|
|
}
|
|
}
|
|
this.emit("sideload-response");
|
|
});
|
|
},
|
|
|
|
showUpdate(browser, info) {
|
|
lazy.AMTelemetry.recordInstallEvent(info.install, {
|
|
step: "permissions_prompt",
|
|
num_strings: info.strings.msgs.length,
|
|
});
|
|
|
|
this.showAddonsManager(browser, info.strings, info.addon.iconURL).then(
|
|
answer => {
|
|
if (answer) {
|
|
info.resolve();
|
|
} else {
|
|
info.reject();
|
|
}
|
|
// At the moment, this prompt will re-appear next time we do an update
|
|
// check. See bug 1332360 for proposal to avoid this.
|
|
this.updates.delete(info);
|
|
this._updateNotifications();
|
|
}
|
|
);
|
|
},
|
|
|
|
observe(subject, topic) {
|
|
if (topic == "webextension-permission-prompt") {
|
|
let { target, info } = subject.wrappedJSObject;
|
|
|
|
let { browser, window } = getTabBrowser(target);
|
|
|
|
// Dismiss the progress notification. Note that this is bad if
|
|
// there are multiple simultaneous installs happening, see
|
|
// bug 1329884 for a longer explanation.
|
|
let progressNotification = window.PopupNotifications.getNotification(
|
|
"addon-progress",
|
|
browser
|
|
);
|
|
if (progressNotification) {
|
|
progressNotification.remove();
|
|
}
|
|
|
|
info.unsigned =
|
|
info.addon.signedState <= lazy.AddonManager.SIGNEDSTATE_MISSING;
|
|
if (
|
|
info.unsigned &&
|
|
Cu.isInAutomation &&
|
|
Services.prefs.getBoolPref("extensions.ui.ignoreUnsigned", false)
|
|
) {
|
|
info.unsigned = false;
|
|
}
|
|
|
|
let strings = this._buildStrings(info);
|
|
|
|
// If this is an update with no promptable permissions, just apply it
|
|
if (info.type == "update" && !strings.msgs.length) {
|
|
info.resolve();
|
|
return;
|
|
}
|
|
|
|
let icon = info.unsigned
|
|
? "chrome://global/skin/icons/warning.svg"
|
|
: info.icon;
|
|
|
|
if (info.type == "sideload") {
|
|
lazy.AMTelemetry.recordManageEvent(info.addon, "sideload_prompt", {
|
|
num_strings: strings.msgs.length,
|
|
});
|
|
} else {
|
|
lazy.AMTelemetry.recordInstallEvent(info.install, {
|
|
step: "permissions_prompt",
|
|
num_strings: strings.msgs.length,
|
|
});
|
|
}
|
|
|
|
this.showPermissionsPrompt(
|
|
browser,
|
|
strings,
|
|
icon,
|
|
info.addon,
|
|
true /* shouldShowIncognitoCheckbox */
|
|
).then(answer => {
|
|
if (answer) {
|
|
info.resolve();
|
|
} else {
|
|
info.reject();
|
|
}
|
|
});
|
|
} else if (topic == "webextension-update-permissions") {
|
|
let info = subject.wrappedJSObject;
|
|
info.type = "update";
|
|
let strings = this._buildStrings(info);
|
|
|
|
// If we don't prompt for any new permissions, just apply it
|
|
if (!strings.msgs.length) {
|
|
info.resolve();
|
|
return;
|
|
}
|
|
|
|
let update = {
|
|
strings,
|
|
permissions: info.permissions,
|
|
install: info.install,
|
|
addon: info.addon,
|
|
resolve: info.resolve,
|
|
reject: info.reject,
|
|
};
|
|
|
|
this.updates.add(update);
|
|
this._updateNotifications();
|
|
} else if (topic == "webextension-install-notify") {
|
|
let { target, addon, callback } = subject.wrappedJSObject;
|
|
this.showInstallNotification(target, addon).then(() => {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
});
|
|
} else if (topic == "webextension-optional-permission-prompt") {
|
|
let { browser, name, icon, permissions, resolve } =
|
|
subject.wrappedJSObject;
|
|
let strings = this._buildStrings({
|
|
type: "optional",
|
|
addon: { name },
|
|
permissions,
|
|
});
|
|
|
|
// If we don't have any promptable permissions, just proceed
|
|
if (!strings.msgs.length) {
|
|
resolve(true);
|
|
return;
|
|
}
|
|
resolve(this.showPermissionsPrompt(browser, strings, icon));
|
|
} else if (topic == "webextension-defaultsearch-prompt") {
|
|
let { browser, name, icon, respond, currentEngine, newEngine } =
|
|
subject.wrappedJSObject;
|
|
|
|
const [searchDesc, searchYes, searchNo] = lazy.l10n.formatMessagesSync([
|
|
{
|
|
id: "webext-default-search-description",
|
|
args: { addonName: "<>", currentEngine, newEngine },
|
|
},
|
|
"webext-default-search-yes",
|
|
"webext-default-search-no",
|
|
]);
|
|
|
|
const strings = { addonName: name, text: searchDesc.value };
|
|
for (let attr of searchYes.attributes) {
|
|
if (attr.name === "label") {
|
|
strings.acceptText = attr.value;
|
|
} else if (attr.name === "accesskey") {
|
|
strings.acceptKey = attr.value;
|
|
}
|
|
}
|
|
for (let attr of searchNo.attributes) {
|
|
if (attr.name === "label") {
|
|
strings.cancelText = attr.value;
|
|
} else if (attr.name === "accesskey") {
|
|
strings.cancelKey = attr.value;
|
|
}
|
|
}
|
|
|
|
this.showDefaultSearchPrompt(browser, strings, icon).then(respond);
|
|
} else if (
|
|
[
|
|
"webextension-imported-addons-cancelled",
|
|
"webextension-imported-addons-complete",
|
|
"webextension-imported-addons-pending",
|
|
].includes(topic)
|
|
) {
|
|
this._updateNotifications();
|
|
}
|
|
},
|
|
|
|
// Create a set of formatted strings for a permission prompt
|
|
_buildStrings(info) {
|
|
const strings = lazy.ExtensionData.formatPermissionStrings(
|
|
info,
|
|
this.SHOW_FULL_DOMAINS_LIST
|
|
? { fullDomainsList: true }
|
|
: { collapseOrigins: true }
|
|
);
|
|
strings.addonName = info.addon.name;
|
|
return strings;
|
|
},
|
|
|
|
async showPermissionsPrompt(
|
|
target,
|
|
strings,
|
|
icon,
|
|
addon = undefined,
|
|
shouldShowIncognitoCheckbox = false
|
|
) {
|
|
let { browser, window } = getTabBrowser(target);
|
|
|
|
let showIncognitoCheckbox =
|
|
shouldShowIncognitoCheckbox && !lazy.POSTINSTALL_PRIVATEBROWSING_CHECKBOX;
|
|
|
|
if (showIncognitoCheckbox) {
|
|
showIncognitoCheckbox = !!(
|
|
addon.permissions &
|
|
lazy.AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS
|
|
);
|
|
}
|
|
|
|
const incognitoPermissionName = "internal:privateBrowsingAllowed";
|
|
let grantPrivateBrowsingAllowed = false;
|
|
if (showIncognitoCheckbox) {
|
|
const { permissions } = await lazy.ExtensionPermissions.get(addon.id);
|
|
grantPrivateBrowsingAllowed = permissions.includes(
|
|
incognitoPermissionName
|
|
);
|
|
}
|
|
|
|
// Wait for any pending prompts to complete before showing the next one.
|
|
let pending;
|
|
while ((pending = this.pendingNotifications.get(browser))) {
|
|
await pending;
|
|
}
|
|
|
|
let promise = new Promise(resolve => {
|
|
function eventCallback(topic) {
|
|
if (topic == "swapping") {
|
|
return true;
|
|
}
|
|
if (topic == "removed") {
|
|
Services.tm.dispatchToMainThread(() => {
|
|
resolve(false);
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Show the SUMO link already part of the popupnotification by
|
|
// setting learnMoreURL option if there are permissions to be
|
|
// granted to the addon being installed (or if the private
|
|
// browsing checkbox is shown).
|
|
const learnMoreURL =
|
|
strings.msgs.length || showIncognitoCheckbox
|
|
? Services.urlFormatter.formatURLPref("app.support.baseURL") +
|
|
"extension-permissions"
|
|
: undefined;
|
|
|
|
let options = {
|
|
hideClose: true,
|
|
popupIconURL: icon || DEFAULT_EXTENSION_ICON,
|
|
popupIconClass: icon ? "" : "addon-warning-icon",
|
|
learnMoreURL,
|
|
persistent: true,
|
|
eventCallback,
|
|
removeOnDismissal: true,
|
|
popupOptions: {
|
|
position: "bottomright topright",
|
|
},
|
|
// Pass additional options used internally by the
|
|
// addon-webext-permissions-notification custom element
|
|
// (defined and registered by browser-addons.js).
|
|
customElementOptions: {
|
|
strings,
|
|
showIncognitoCheckbox,
|
|
grantPrivateBrowsingAllowed,
|
|
onPrivateBrowsingAllowedChanged(value) {
|
|
grantPrivateBrowsingAllowed = value;
|
|
},
|
|
},
|
|
};
|
|
// The prompt/notification machinery has a special affordance wherein
|
|
// certain subsets of the header string can be designated "names", and
|
|
// referenced symbolically as "<>" and "{}" to receive special formatting.
|
|
// That code assumes that the existence of |name| and |secondName| in the
|
|
// options object imply the presence of "<>" and "{}" (respectively) in
|
|
// in the string.
|
|
//
|
|
// At present, WebExtensions use this affordance while SitePermission
|
|
// add-ons don't, so we need to conditionally set the |name| field.
|
|
//
|
|
// NB: This could potentially be cleaned up, see bug 1799710.
|
|
if (strings.header.includes("<>")) {
|
|
options.name = strings.addonName;
|
|
}
|
|
|
|
let action = {
|
|
label: strings.acceptText,
|
|
accessKey: strings.acceptKey,
|
|
callback: () => {
|
|
resolve(true);
|
|
},
|
|
};
|
|
let secondaryActions = [
|
|
{
|
|
label: strings.cancelText,
|
|
accessKey: strings.cancelKey,
|
|
callback: () => {
|
|
resolve(false);
|
|
},
|
|
},
|
|
];
|
|
|
|
window.PopupNotifications.show(
|
|
browser,
|
|
"addon-webext-permissions",
|
|
strings.header,
|
|
browser.ownerGlobal.gUnifiedExtensions.getPopupAnchorID(
|
|
browser,
|
|
window
|
|
),
|
|
action,
|
|
secondaryActions,
|
|
options
|
|
);
|
|
});
|
|
|
|
this.pendingNotifications.set(browser, promise);
|
|
promise.finally(() => this.pendingNotifications.delete(browser));
|
|
// NOTE: this method is also called from showQuarantineConfirmation and some of its
|
|
// related test cases (from browser_ext_originControls.js) seem to be hitting a race
|
|
// if the promise returned requires an additional tick to be resolved.
|
|
// Look more into the failure and determine a better option to avoid those failures.
|
|
if (!showIncognitoCheckbox) {
|
|
return promise;
|
|
}
|
|
return promise.then(continueInstall => {
|
|
if (!continueInstall) {
|
|
return continueInstall;
|
|
}
|
|
const incognitoPermission = {
|
|
permissions: [incognitoPermissionName],
|
|
origins: [],
|
|
};
|
|
let permUpdatePromise;
|
|
if (grantPrivateBrowsingAllowed) {
|
|
permUpdatePromise = lazy.ExtensionPermissions.add(
|
|
addon.id,
|
|
incognitoPermission
|
|
).catch(err =>
|
|
lazy.logConsole.warn(
|
|
`Error on adding "${incognitoPermissionName}" permission to addon id "${addon.id}`,
|
|
err
|
|
)
|
|
);
|
|
} else {
|
|
permUpdatePromise = lazy.ExtensionPermissions.remove(
|
|
addon.id,
|
|
incognitoPermission
|
|
).catch(err =>
|
|
lazy.logConsole.warn(
|
|
`Error on removing "${incognitoPermissionName}" permission to addon id "${addon.id}`,
|
|
err
|
|
)
|
|
);
|
|
}
|
|
return permUpdatePromise.then(() => continueInstall);
|
|
});
|
|
},
|
|
|
|
showDefaultSearchPrompt(target, strings, icon) {
|
|
return new Promise(resolve => {
|
|
let options = {
|
|
hideClose: true,
|
|
popupIconURL: icon || DEFAULT_EXTENSION_ICON,
|
|
persistent: true,
|
|
removeOnDismissal: true,
|
|
eventCallback(topic) {
|
|
if (topic == "removed") {
|
|
resolve(false);
|
|
}
|
|
},
|
|
name: strings.addonName,
|
|
};
|
|
|
|
let action = {
|
|
label: strings.acceptText,
|
|
accessKey: strings.acceptKey,
|
|
callback: () => {
|
|
resolve(true);
|
|
},
|
|
};
|
|
let secondaryActions = [
|
|
{
|
|
label: strings.cancelText,
|
|
accessKey: strings.cancelKey,
|
|
callback: () => {
|
|
resolve(false);
|
|
},
|
|
},
|
|
];
|
|
|
|
let { browser, window } = getTabBrowser(target);
|
|
|
|
window.PopupNotifications.show(
|
|
browser,
|
|
"addon-webext-defaultsearch",
|
|
strings.text,
|
|
"addons-notification-icon",
|
|
action,
|
|
secondaryActions,
|
|
options
|
|
);
|
|
});
|
|
},
|
|
|
|
async showInstallNotification(target, addon) {
|
|
let { window } = getTabBrowser(target);
|
|
|
|
const message = await lazy.l10n.formatValue("addon-post-install-message", {
|
|
addonName: "<>",
|
|
});
|
|
|
|
const hideIncognitoCheckbox = !lazy.POSTINSTALL_PRIVATEBROWSING_CHECKBOX;
|
|
const permissionName = "internal:privateBrowsingAllowed";
|
|
const { permissions } = await lazy.ExtensionPermissions.get(addon.id);
|
|
const hasIncognito = permissions.includes(permissionName);
|
|
|
|
return new Promise(resolve => {
|
|
// Show or hide private permission ui based on the pref.
|
|
function setCheckbox(win) {
|
|
let checkbox = win.document.getElementById("addon-incognito-checkbox");
|
|
checkbox.checked = hasIncognito;
|
|
checkbox.hidden =
|
|
hideIncognitoCheckbox ||
|
|
!(
|
|
addon.permissions &
|
|
lazy.AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS
|
|
);
|
|
}
|
|
|
|
async function actionResolve(win) {
|
|
let checkbox = win.document.getElementById("addon-incognito-checkbox");
|
|
|
|
if (hideIncognitoCheckbox || checkbox.checked == hasIncognito) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
let incognitoPermission = {
|
|
permissions: [permissionName],
|
|
origins: [],
|
|
};
|
|
|
|
// The checkbox has been changed at this point, otherwise we would
|
|
// have exited early above.
|
|
if (checkbox.checked) {
|
|
await lazy.ExtensionPermissions.add(addon.id, incognitoPermission);
|
|
} else if (hasIncognito) {
|
|
await lazy.ExtensionPermissions.remove(addon.id, incognitoPermission);
|
|
}
|
|
// Reload the extension if it is already enabled. This ensures any change
|
|
// on the private browsing permission is properly handled.
|
|
if (addon.isActive) {
|
|
await addon.reload();
|
|
}
|
|
|
|
resolve();
|
|
}
|
|
|
|
let action = {
|
|
callback: actionResolve,
|
|
};
|
|
|
|
let icon = addon.isWebExtension
|
|
? lazy.AddonManager.getPreferredIconURL(addon, 32, window) ||
|
|
DEFAULT_EXTENSION_ICON
|
|
: "chrome://browser/skin/addons/addon-install-installed.svg";
|
|
let options = {
|
|
name: addon.name,
|
|
message,
|
|
popupIconURL: icon,
|
|
onRefresh: setCheckbox,
|
|
onDismissed: win => {
|
|
lazy.AppMenuNotifications.removeNotification("addon-installed");
|
|
actionResolve(win);
|
|
},
|
|
};
|
|
lazy.AppMenuNotifications.showNotification(
|
|
"addon-installed",
|
|
action,
|
|
null,
|
|
options
|
|
);
|
|
});
|
|
},
|
|
|
|
async showQuarantineConfirmation(browser, policy) {
|
|
let [title, line1, line2, allow, deny] = await lazy.l10n.formatMessages([
|
|
{
|
|
id: "webext-quarantine-confirmation-title",
|
|
args: { addonName: "<>" },
|
|
},
|
|
"webext-quarantine-confirmation-line-1",
|
|
"webext-quarantine-confirmation-line-2",
|
|
"webext-quarantine-confirmation-allow",
|
|
"webext-quarantine-confirmation-deny",
|
|
]);
|
|
|
|
let attr = (msg, name) => msg.attributes.find(a => a.name === name)?.value;
|
|
|
|
let strings = {
|
|
addonName: policy.name,
|
|
header: title.value,
|
|
text: line1.value + "\n\n" + line2.value,
|
|
msgs: [],
|
|
acceptText: attr(allow, "label"),
|
|
acceptKey: attr(allow, "accesskey"),
|
|
cancelText: attr(deny, "label"),
|
|
cancelKey: attr(deny, "accesskey"),
|
|
};
|
|
|
|
let icon = policy.extension?.getPreferredIcon(32);
|
|
|
|
if (await ExtensionsUI.showPermissionsPrompt(browser, strings, icon)) {
|
|
lazy.QuarantinedDomains.setUserAllowedAddonIdPref(policy.id, true);
|
|
}
|
|
},
|
|
|
|
// Populate extension toolbar popup menu with origin controls.
|
|
originControlsMenu(popup, extensionId) {
|
|
let policy = WebExtensionPolicy.getByID(extensionId);
|
|
|
|
let win = popup.ownerGlobal;
|
|
let doc = popup.ownerDocument;
|
|
let tab = win.gBrowser.selectedTab;
|
|
let uri = tab.linkedBrowser?.currentURI;
|
|
let state = lazy.OriginControls.getState(policy, tab);
|
|
|
|
let headerItem = doc.createXULElement("menuitem");
|
|
headerItem.setAttribute("disabled", true);
|
|
let items = [headerItem];
|
|
|
|
// MV2 normally don't have controls, but we show the quarantined state.
|
|
if (!policy?.extension.originControls && !state.quarantined) {
|
|
return;
|
|
}
|
|
|
|
if (state.noAccess) {
|
|
doc.l10n.setAttributes(headerItem, "origin-controls-no-access");
|
|
} else {
|
|
doc.l10n.setAttributes(headerItem, "origin-controls-options");
|
|
}
|
|
|
|
if (state.quarantined) {
|
|
doc.l10n.setAttributes(headerItem, "origin-controls-quarantined-status");
|
|
|
|
let allowQuarantined = doc.createXULElement("menuitem");
|
|
doc.l10n.setAttributes(
|
|
allowQuarantined,
|
|
"origin-controls-quarantined-allow"
|
|
);
|
|
allowQuarantined.addEventListener("command", () => {
|
|
this.showQuarantineConfirmation(tab.linkedBrowser, policy);
|
|
});
|
|
items.push(allowQuarantined);
|
|
}
|
|
|
|
if (state.allDomains) {
|
|
let allDomains = doc.createXULElement("menuitem");
|
|
allDomains.setAttribute("type", "radio");
|
|
allDomains.setAttribute("checked", state.hasAccess);
|
|
doc.l10n.setAttributes(allDomains, "origin-controls-option-all-domains");
|
|
items.push(allDomains);
|
|
}
|
|
|
|
if (state.whenClicked) {
|
|
let whenClicked = doc.createXULElement("menuitem");
|
|
whenClicked.setAttribute("type", "radio");
|
|
whenClicked.setAttribute("checked", !state.hasAccess);
|
|
doc.l10n.setAttributes(
|
|
whenClicked,
|
|
"origin-controls-option-when-clicked"
|
|
);
|
|
whenClicked.addEventListener("command", async () => {
|
|
await lazy.OriginControls.setWhenClicked(policy, uri);
|
|
win.gUnifiedExtensions.updateAttention();
|
|
});
|
|
items.push(whenClicked);
|
|
}
|
|
|
|
if (state.alwaysOn) {
|
|
let alwaysOn = doc.createXULElement("menuitem");
|
|
alwaysOn.setAttribute("type", "radio");
|
|
alwaysOn.setAttribute("checked", state.hasAccess);
|
|
doc.l10n.setAttributes(alwaysOn, "origin-controls-option-always-on", {
|
|
domain: uri.host,
|
|
});
|
|
alwaysOn.addEventListener("command", async () => {
|
|
await lazy.OriginControls.setAlwaysOn(policy, uri);
|
|
win.gUnifiedExtensions.updateAttention();
|
|
});
|
|
items.push(alwaysOn);
|
|
}
|
|
|
|
items.push(doc.createXULElement("menuseparator"));
|
|
|
|
// Insert all items before Pin to toolbar OR Manage Extension, but after
|
|
// any extension's menu items.
|
|
let manageItem =
|
|
popup.querySelector(".customize-context-manageExtension") ||
|
|
popup.querySelector(".unified-extensions-context-menu-pin-to-toolbar");
|
|
items.forEach(item => item && popup.insertBefore(item, manageItem));
|
|
|
|
let cleanup = e => {
|
|
if (e.target === popup) {
|
|
items.forEach(item => item?.remove());
|
|
popup.removeEventListener("popuphidden", cleanup);
|
|
}
|
|
};
|
|
popup.addEventListener("popuphidden", cleanup);
|
|
},
|
|
};
|
|
|
|
EventEmitter.decorate(ExtensionsUI);
|