summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/content/aboutaddonsCommon.js')
-rw-r--r--toolkit/mozapps/extensions/content/aboutaddonsCommon.js275
1 files changed, 275 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/content/aboutaddonsCommon.js b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
new file mode 100644
index 0000000000..739e7629d7
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
@@ -0,0 +1,275 @@
+/* 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/. */
+/* eslint max-len: ["error", 80] */
+
+"use strict";
+
+/* exported attachUpdateHandler, detachUpdateHandler, gBrowser,
+ * getBrowserElement, installAddonsFromFilePicker,
+ * isCorrectlySigned, isDisabledUnsigned, isDiscoverEnabled,
+ * isPending, loadReleaseNotes, openOptionsInTab, promiseEvent,
+ * shouldShowPermissionsPrompt, showPermissionsPrompt,
+ * PREF_UI_LASTCATEGORY */
+
+const { AddonSettings } = ChromeUtils.importESModule(
+ "resource://gre/modules/addons/AddonSettings.sys.mjs"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+ChromeUtils.defineESModuleGetters(this, {
+ Extension: "resource://gre/modules/Extension.sys.mjs",
+});
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "XPINSTALL_ENABLED",
+ "xpinstall.enabled",
+ true
+);
+
+const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
+const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
+
+function isDiscoverEnabled() {
+ try {
+ if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED)) {
+ return false;
+ }
+ } catch (e) {}
+
+ if (!XPINSTALL_ENABLED) {
+ return false;
+ }
+
+ return true;
+}
+
+function getBrowserElement() {
+ return window.docShell.chromeEventHandler;
+}
+
+function promiseEvent(event, target, capture = false) {
+ return new Promise(resolve => {
+ target.addEventListener(event, resolve, { capture, once: true });
+ });
+}
+
+function installPromptHandler(info) {
+ const install = this;
+
+ let oldPerms = info.existingAddon.userPermissions;
+ if (!oldPerms) {
+ // Updating from a legacy add-on, let it proceed
+ return Promise.resolve();
+ }
+
+ let newPerms = info.addon.userPermissions;
+
+ let difference = Extension.comparePermissions(oldPerms, newPerms);
+
+ // If there are no new permissions, just proceed
+ if (!difference.origins.length && !difference.permissions.length) {
+ return Promise.resolve();
+ }
+
+ return new Promise((resolve, reject) => {
+ let subject = {
+ wrappedJSObject: {
+ target: getBrowserElement(),
+ info: {
+ type: "update",
+ addon: info.addon,
+ icon: info.addon.iconURL,
+ // Reference to the related AddonInstall object (used in
+ // AMTelemetry to link the recorded event to the other events from
+ // the same install flow).
+ install,
+ permissions: difference,
+ resolve,
+ reject,
+ },
+ },
+ };
+ Services.obs.notifyObservers(subject, "webextension-permission-prompt");
+ });
+}
+
+function attachUpdateHandler(install) {
+ install.promptHandler = installPromptHandler;
+}
+
+function detachUpdateHandler(install) {
+ if (install?.promptHandler === installPromptHandler) {
+ install.promptHandler = null;
+ }
+}
+
+async function loadReleaseNotes(uri) {
+ const res = await fetch(uri.spec, { credentials: "omit" });
+
+ if (!res.ok) {
+ throw new Error("Error loading release notes");
+ }
+
+ // Load the content.
+ const text = await res.text();
+
+ // Setup the content sanitizer.
+ const ParserUtils = Cc["@mozilla.org/parserutils;1"].getService(
+ Ci.nsIParserUtils
+ );
+ const flags =
+ ParserUtils.SanitizerDropMedia |
+ ParserUtils.SanitizerDropNonCSSPresentation |
+ ParserUtils.SanitizerDropForms;
+
+ // Sanitize and parse the content to a fragment.
+ const context = document.createElementNS(HTML_NS, "div");
+ return ParserUtils.parseFragment(text, flags, false, uri, context);
+}
+
+function openOptionsInTab(optionsURL) {
+ let mainWindow = window.windowRoot.ownerGlobal;
+ if ("switchToTabHavingURI" in mainWindow) {
+ mainWindow.switchToTabHavingURI(optionsURL, true, {
+ relatedToCurrent: true,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ return true;
+ }
+ return false;
+}
+
+function shouldShowPermissionsPrompt(addon) {
+ if (!addon.isWebExtension || addon.seen) {
+ return false;
+ }
+
+ let perms = addon.userPermissions;
+ return perms?.origins.length || perms?.permissions.length;
+}
+
+function showPermissionsPrompt(addon) {
+ return new Promise(resolve => {
+ const permissions = addon.userPermissions;
+ const target = getBrowserElement();
+
+ const onAddonEnabled = () => {
+ // 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 (
+ addon.permissions & AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS
+ ) {
+ Services.obs.notifyObservers(
+ { addon, target },
+ "webextension-install-notify"
+ );
+ }
+ resolve();
+ };
+
+ const subject = {
+ wrappedJSObject: {
+ target,
+ info: {
+ type: "sideload",
+ addon,
+ icon: addon.iconURL,
+ permissions,
+ resolve() {
+ addon.markAsSeen();
+ addon.enable().then(onAddonEnabled);
+ },
+ reject() {
+ // Ignore a cancelled permission prompt.
+ },
+ },
+ },
+ };
+ Services.obs.notifyObservers(subject, "webextension-permission-prompt");
+ });
+}
+
+// Stub tabbrowser implementation for use by the tab-modal alert code
+// when an alert/prompt/confirm method is called in a WebExtensions options_ui
+// page (See Bug 1385548 for rationale).
+var gBrowser = {
+ getTabModalPromptBox(browser) {
+ const parentWindow = window.docShell.chromeEventHandler.ownerGlobal;
+
+ if (parentWindow.gBrowser) {
+ return parentWindow.gBrowser.getTabModalPromptBox(browser);
+ }
+
+ return null;
+ },
+};
+
+function isCorrectlySigned(addon) {
+ // Add-ons without an "isCorrectlySigned" property are correctly signed as
+ // they aren't the correct type for signing.
+ return addon.isCorrectlySigned !== false;
+}
+
+function isDisabledUnsigned(addon) {
+ let signingRequired =
+ addon.type == "locale"
+ ? AddonSettings.LANGPACKS_REQUIRE_SIGNING
+ : AddonSettings.REQUIRE_SIGNING;
+ return signingRequired && !isCorrectlySigned(addon);
+}
+
+function isPending(addon, action) {
+ const amAction = AddonManager["PENDING_" + action.toUpperCase()];
+ return !!(addon.pendingOperations & amAction);
+}
+
+async function installAddonsFromFilePicker() {
+ let [dialogTitle, filterName] = await document.l10n.formatMessages([
+ { id: "addon-install-from-file-dialog-title" },
+ { id: "addon-install-from-file-filter-name" },
+ ]);
+ const nsIFilePicker = Ci.nsIFilePicker;
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ fp.init(window, dialogTitle.value, nsIFilePicker.modeOpenMultiple);
+ try {
+ fp.appendFilter(filterName.value, "*.xpi;*.jar;*.zip");
+ fp.appendFilters(nsIFilePicker.filterAll);
+ } catch (e) {}
+
+ return new Promise(resolve => {
+ fp.open(async result => {
+ if (result != nsIFilePicker.returnOK) {
+ return;
+ }
+
+ let installTelemetryInfo = {
+ source: "about:addons",
+ method: "install-from-file",
+ };
+
+ let browser = getBrowserElement();
+ let installs = [];
+ for (let file of fp.files) {
+ let install = await AddonManager.getInstallForFile(
+ file,
+ null,
+ installTelemetryInfo
+ );
+ AddonManager.installAddonFromAOM(
+ browser,
+ document.documentURIObject,
+ install
+ );
+ installs.push(install);
+ }
+ resolve(installs);
+ });
+ });
+}