diff --git a/toolkit/mozapps/extensions/content/aboutaddons.js b/toolkit/mozapps/extensions/content/aboutaddons.js
index d14b4f0fba98..e9b44804db21 100644
--- a/toolkit/mozapps/extensions/content/aboutaddons.js
+++ b/toolkit/mozapps/extensions/content/aboutaddons.js
@@ -2,7 +2,7 @@
* 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] */
-/* exported initialize, hide, show */
+/* exported hide, initialize, show */
/* import-globals-from aboutaddonsCommon.js */
/* import-globals-from abuse-reports.js */
/* global MozXULElement, windowRoot */
@@ -59,6 +59,13 @@ XPCOMUtils.defineLazyPreferenceGetter(
val => Services.urlFormatter.formatURL(val)
);
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "XPINSTALL_ENABLED",
+ "xpinstall.enabled",
+ true
+);
+
const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds)
XPCOMUtils.defineLazyPreferenceGetter(
@@ -124,19 +131,19 @@ const AddonCardListenerHandler = {
},
delegateEvent(name, addon, args) {
- let cards;
+ let elements;
if (this.MANAGER_EVENTS.has(name)) {
- cards = document.querySelectorAll("addon-card");
+ elements = document.querySelectorAll("addon-card, addon-page-options");
} else {
let cardSelector = `addon-card[addon-id="${addon.id}"]`;
- cards = document.querySelectorAll(
+ elements = document.querySelectorAll(
`${cardSelector}, ${cardSelector} addon-details`
);
}
- for (let card of cards) {
+ for (let el of elements) {
try {
- if (name in card) {
- card[name](...args);
+ if (name in el) {
+ el[name](...args);
}
} catch (e) {
Cu.reportError(e);
@@ -275,6 +282,55 @@ async function getAddonMessageInfo(addon) {
return {};
}
+function checkForUpdate(addon) {
+ return new Promise(resolve => {
+ let listener = {
+ onUpdateAvailable(addon, install) {
+ attachUpdateHandler(install);
+
+ if (AddonManager.shouldAutoUpdate(addon)) {
+ let failed = () => {
+ install.removeListener(updateListener);
+ resolve({ installed: false, pending: false, found: true });
+ };
+ let updateListener = {
+ onDownloadFailed: failed,
+ onInstallCancelled: failed,
+ onInstallFailed: failed,
+ onInstallEnded: (...args) => {
+ install.removeListener(updateListener);
+ resolve({ installed: true, pending: false, found: true });
+ },
+ };
+ install.addListener(updateListener);
+ install.install();
+ } else {
+ resolve({ installed: false, pending: true, found: true });
+ }
+ },
+ onNoUpdateAvailable() {
+ resolve({ found: false });
+ },
+ };
+ addon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+ });
+}
+
+async function checkForUpdates() {
+ let addons = await AddonManager.getAddonsByTypes(null);
+ addons = addons.filter(addon => hasPermission(addon, "upgrade"));
+ let updates = await Promise.all(addons.map(addon => checkForUpdate(addon)));
+ Services.obs.notifyObservers(null, "EM-update-check-finished");
+ return updates.reduce(
+ (counts, update) => ({
+ installed: counts.installed + (update.installed ? 1 : 0),
+ pending: counts.pending + (update.pending ? 1 : 0),
+ found: counts.found + (update.found ? 1 : 0),
+ }),
+ { installed: 0, pending: 0, found: 0 }
+ );
+}
+
// Don't change how we handle this while the page is open.
const INLINE_OPTIONS_ENABLED = Services.prefs.getBoolPref(
"extensions.htmlaboutaddons.inline-options.enabled"
@@ -552,6 +608,9 @@ class PanelList extends HTMLElement {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(importTemplate("panel-list"));
+ }
+
+ connectedCallback() {
this.setAttribute("role", "menu");
}
@@ -579,8 +638,19 @@ class PanelList extends HTMLElement {
}
hide(triggeringEvent) {
+ let openingEvent = this.triggeringEvent;
this.triggeringEvent = triggeringEvent;
this.open = false;
+ // Since the previously focused element (which is inside the panel) is now
+ // hidden, move the focus back to the element that opened the panel if it
+ // was opened with the keyboard.
+ if (
+ openingEvent &&
+ openingEvent.target &&
+ openingEvent.mozInputSource === MouseEvent.MOZ_SOURCE_KEYBOARD
+ ) {
+ openingEvent.target.focus();
+ }
}
toggle(triggeringEvent) {
@@ -733,10 +803,15 @@ class PanelList extends HTMLElement {
}
break;
} else if (e.key === "Escape") {
- let { triggeringEvent } = this;
this.hide();
- if (triggeringEvent && triggeringEvent.target) {
- triggeringEvent.target.focus();
+ } else if (!e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey) {
+ // Check if any of the children have an accesskey for this letter.
+ let item = this.querySelector(
+ `[accesskey="${e.key.toLowerCase()}"],
+ [accesskey="${e.key.toUpperCase()}"]`
+ );
+ if (item) {
+ item.click();
}
}
break;
@@ -807,6 +882,7 @@ class PanelList extends HTMLElement {
async onShow() {
let { triggeringEvent } = this;
+ this.sendEvent("showing");
this.addHideListeners();
await this.setAlign();
@@ -838,12 +914,86 @@ class PanelList extends HTMLElement {
customElements.define("panel-list", PanelList);
class PanelItem extends HTMLElement {
+ static get observedAttributes() {
+ return ["accesskey"];
+ }
+
constructor() {
super();
this.attachShadow({ mode: "open" });
- this.shadowRoot.appendChild(importTemplate("panel-item"));
- this.button = this.shadowRoot.querySelector("button");
+
+ let style = document.createElement("link");
+ style.rel = "stylesheet";
+ style.href = "chrome://mozapps/content/extensions/panel-item.css";
+
+ this.button = document.createElement("button");
this.button.setAttribute("role", "menuitem");
+
+ // Use a XUL label element to show the accesskey.
+ this.label = document.createXULElement("label");
+ this.button.appendChild(this.label);
+
+ let supportLinkSlot = document.createElement("slot");
+ supportLinkSlot.name = "support-link";
+
+ let defaultSlot = document.createElement("slot");
+ defaultSlot.style.display = "none";
+
+ this.shadowRoot.append(style, this.button, supportLinkSlot, defaultSlot);
+
+ // When our content changes, move the text into the label. It doesn't work
+ // with a , unfortunately.
+ new MutationObserver(() => {
+ this.label.textContent = defaultSlot
+ .assignedNodes()
+ .map(node => node.textContent)
+ .join("");
+ }).observe(this, { characterData: true, childList: true, subtree: true });
+ }
+
+ connectedCallback() {
+ this.panel = this.closest("panel-list");
+
+ if (this.panel) {
+ this.panel.addEventListener("hidden", this);
+ this.panel.addEventListener("shown", this);
+ }
+ }
+
+ disconnectedCallback() {
+ if (this.panel) {
+ this.panel.removeEventListener("hidden", this);
+ this.panel.removeEventListener("shown", this);
+ this.panel = null;
+ }
+ }
+
+ attributeChangedCallback(name, oldVal, newVal) {
+ if (name === "accesskey") {
+ // Bug 1037709 - Accesskey doesn't work in shadow DOM.
+ // Ideally we'd have the accesskey set in shadow DOM, and on
+ // attributeChangedCallback we'd just update the shadow DOM accesskey.
+
+ // Skip this change event if we caused it.
+ if (this._modifyingAccessKey) {
+ this._modifyingAccessKey = false;
+ return;
+ }
+
+ this.label.accessKey = newVal || "";
+
+ // Bug 1588156 - Accesskey is not ignored for hidden non-input elements.
+ // Since the accesskey won't be ignored, we need to remove it ourselves
+ // when the panel is closed, and move it back when it opens.
+ if (!this.panel || !this.panel.open) {
+ // When the panel isn't open, just store the key for later.
+ this._accessKey = newVal || null;
+ this._modifyingAccessKey = true;
+ this.accessKey = "";
+ } else {
+ this._accessKey = null;
+ }
+ }
}
get disabled() {
@@ -865,9 +1015,408 @@ class PanelItem extends HTMLElement {
focus() {
this.button.focus();
}
+
+ handleEvent(e) {
+ // Bug 1588156 - Accesskey is not ignored for hidden non-input elements.
+ // Since the accesskey won't be ignored, we need to remove it ourselves
+ // when the panel is closed, and move it back when it opens.
+ switch (e.type) {
+ case "shown":
+ if (this._accessKey) {
+ this.accessKey = this._accessKey;
+ this._accessKey = null;
+ }
+ break;
+ case "hidden":
+ if (this.accessKey) {
+ this._accessKey = this.accessKey;
+ this._modifyingAccessKey = true;
+ this.accessKey = "";
+ }
+ break;
+ }
+ }
}
customElements.define("panel-item", PanelItem);
+class SearchAddons extends HTMLElement {
+ connectedCallback() {
+ if (this.childElementCount === 0) {
+ this.input = document.createXULElement("search-textbox");
+ this.input.setAttribute("searchbutton", true);
+ this.input.setAttribute("maxlength", 100);
+ this.input.setAttribute("data-l10n-attrs", "placeholder");
+ this.input.inputField.id = "search-addons";
+ document.l10n.setAttributes(this.input, "addons-heading-search-input");
+ this.append(this.input);
+ }
+ this.input.addEventListener("command", this);
+ document.addEventListener("keypress", this);
+ }
+
+ disconnectedCallback() {
+ this.input.removeEventListener("command", this);
+ document.removeEventListener("keypress", this);
+ }
+
+ focus() {
+ this.input.focus();
+ }
+
+ get focusKey() {
+ return this.getAttribute("key");
+ }
+
+ handleEvent(e) {
+ if (e.type === "command") {
+ this.searchAddons(this.value);
+ } else if (e.type === "keypress") {
+ if (e.key === "/" && !e.ctrlKey && !e.metaKey && !e.altKey) {
+ this.focus();
+ } else if (e.key == this.focusKey) {
+ if (e.altKey || e.shiftKey) {
+ return;
+ }
+
+ if (Services.appinfo.OS === "Darwin") {
+ if (e.metaKey && !e.ctrlKey) {
+ this.focus();
+ }
+ } else if (e.ctrlKey && !e.metaKey) {
+ this.focus();
+ }
+ }
+ }
+ }
+
+ get value() {
+ return this.input.value;
+ }
+
+ searchAddons(query) {
+ if (query.length === 0) {
+ return;
+ }
+
+ let url = AddonRepository.getSearchURL(query);
+
+ let browser = getBrowserElement();
+ let chromewin = browser.ownerGlobal;
+ chromewin.openLinkIn(url, "tab", {
+ fromChrome: true,
+ triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
+ {}
+ ),
+ });
+
+ AMTelemetry.recordLinkEvent({
+ object: "aboutAddons",
+ value: "search",
+ extra: {
+ type: this.closest("addon-page-header").getAttribute("type"),
+ view: getTelemetryViewName(this),
+ },
+ });
+ }
+}
+customElements.define("search-addons", SearchAddons);
+
+class AddonPageHeader extends HTMLElement {
+ connectedCallback() {
+ if (this.childElementCount === 0) {
+ this.appendChild(importTemplate("addon-page-header"));
+ this.heading = this.querySelector(".header-name");
+ this.searchLabel = this.querySelector(".search-label");
+ this.backButton = this.querySelector(".back-button");
+ this.pageOptionsMenu = this.querySelector("addon-page-options");
+ }
+ this.addEventListener("click", this);
+ }
+
+ disconnectedCallback() {
+ this.removeEventListener("click", this);
+ }
+
+ setViewInfo({ type, param }) {
+ this.setAttribute("current-view", type);
+ this.setAttribute("current-param", param);
+ let viewType = type === "list" ? param : type;
+ this.setAttribute("type", viewType);
+
+ this.heading.hidden = viewType === "detail";
+ this.backButton.hidden = viewType !== "detail" && viewType !== "shortcuts";
+
+ if (viewType !== "detail") {
+ document.l10n.setAttributes(this.heading, `${viewType}-heading`);
+ }
+
+ if (
+ viewType === "extension" ||
+ viewType === "theme" ||
+ viewType === "shortcuts"
+ ) {
+ let labelType = viewType === "shortcuts" ? "extension" : viewType;
+ document.l10n.setAttributes(
+ this.searchLabel,
+ `${labelType}-heading-search-label`
+ );
+ } else {
+ this.searchLabel.removeAttribute("data-l10n-id");
+ this.searchLabel.textContent = "";
+ }
+ }
+
+ handleEvent(e) {
+ if (e.type === "click") {
+ let action = e.target.getAttribute("action");
+ switch (action) {
+ case "go-back":
+ window.history.back();
+ break;
+ case "page-options":
+ this.pageOptionsMenu.toggle(e);
+ break;
+ }
+ }
+ }
+}
+customElements.define("addon-page-header", AddonPageHeader);
+
+class AddonUpdatesMessage extends HTMLElement {
+ static get observedAttributes() {
+ return ["state"];
+ }
+
+ constructor() {
+ super();
+ this.attachShadow({ mode: "open" });
+ let style = document.createElement("style");
+ style.textContent = `
+ @import "chrome://global/skin/in-content/common.css";
+ button {
+ margin: 0;
+ }
+ `;
+ this.message = document.createElement("span");
+ this.message.hidden = true;
+ this.button = document.createElement("button");
+ this.button.addEventListener("click", e => {
+ if (e.button === 0) {
+ loadViewFn("updates/available", e);
+ }
+ });
+ this.button.hidden = true;
+ this.shadowRoot.append(style, this.message, this.button);
+ }
+
+ attributeChangedCallback(name, oldVal, newVal) {
+ if (name === "state" && oldVal !== newVal) {
+ let l10nId = `addon-updates-${newVal}`;
+ switch (newVal) {
+ case "updating":
+ case "installed":
+ case "none-found":
+ this.button.hidden = true;
+ this.message.hidden = false;
+ document.l10n.setAttributes(this.message, l10nId);
+ break;
+ case "manual-updates-found":
+ this.message.hidden = true;
+ this.button.hidden = false;
+ document.l10n.setAttributes(this.button, l10nId);
+ break;
+ }
+ }
+ }
+
+ set state(val) {
+ this.setAttribute("state", val);
+ }
+}
+customElements.define("addon-updates-message", AddonUpdatesMessage);
+
+class AddonPageOptions extends HTMLElement {
+ connectedCallback() {
+ if (this.childElementCount === 0) {
+ this.render();
+ }
+ this.addEventListener("click", this);
+ this.panel.addEventListener("showing", this);
+ }
+
+ disconnectedCallback() {
+ this.removeEventListener("click", this);
+ this.panel.removeEventListener("showing", this);
+ }
+
+ toggle(...args) {
+ return this.panel.toggle(...args);
+ }
+
+ render() {
+ this.appendChild(importTemplate("addon-page-options"));
+ this.panel = this.querySelector("panel-list");
+ this.installFromFile = this.querySelector('[action="install-from-file"]');
+ this.toggleUpdatesEl = this.querySelector(
+ '[action="set-update-automatically"]'
+ );
+ this.resetUpdatesEl = this.querySelector('[action="reset-update-states"]');
+ this.onUpdateModeChanged();
+ }
+
+ async handleEvent(e) {
+ if (e.type === "click") {
+ e.target.disabled = true;
+ try {
+ await this.onClick(e);
+ } finally {
+ e.target.disabled = false;
+ }
+ } else if (e.type === "showing") {
+ this.installFromFile.hidden = !XPINSTALL_ENABLED;
+ }
+ }
+
+ async onClick(e) {
+ switch (e.target.getAttribute("action")) {
+ case "check-for-updates":
+ await this.checkForUpdates();
+ break;
+ case "view-recent-updates":
+ loadViewFn("updates/recent", e);
+ break;
+ case "install-from-file":
+ if (XPINSTALL_ENABLED) {
+ installAddonsFromFilePicker().then(installs => {
+ for (let install of installs) {
+ this.recordActionEvent({
+ action: "installFromFile",
+ value: install.installId,
+ });
+ }
+ });
+ }
+ break;
+ case "debug-addons":
+ this.openAboutDebugging();
+ break;
+ case "set-update-automatically":
+ await this.toggleAutomaticUpdates();
+ break;
+ case "reset-update-states":
+ await this.resetAutomaticUpdates();
+ break;
+ case "manage-shortcuts":
+ loadViewFn("shortcuts/shortcuts", e);
+ break;
+ }
+ }
+
+ onUpdateModeChanged() {
+ let updatesEnabled = this.automaticUpdatesEnabled();
+ this.toggleUpdatesEl.checked = updatesEnabled;
+ let resetType = updatesEnabled ? "automatic" : "manual";
+ let resetStringId = `addon-updates-reset-updates-to-${resetType}`;
+ document.l10n.setAttributes(this.resetUpdatesEl, resetStringId);
+ }
+
+ async checkForUpdates(e) {
+ this.recordActionEvent({ action: "checkForUpdates" });
+ let message = document.getElementById("updates-message");
+ message.state = "updating";
+ message.hidden = false;
+ let { installed, pending } = await checkForUpdates();
+ if (pending > 0) {
+ message.state = "manual-updates-found";
+ } else if (installed > 0) {
+ message.state = "installed";
+ } else {
+ message.state = "none-found";
+ }
+ }
+
+ openAboutDebugging() {
+ let mainWindow = window.windowRoot.ownerGlobal;
+ this.recordLinkEvent({ value: "about:debugging" });
+ if ("switchToTabHavingURI" in mainWindow) {
+ let principal = Services.scriptSecurityManager.getSystemPrincipal();
+ mainWindow.switchToTabHavingURI(
+ `about:debugging#/runtime/this-firefox`,
+ true,
+ {
+ ignoreFragment: "whenComparing",
+ triggeringPrincipal: principal,
+ }
+ );
+ }
+ }
+
+ automaticUpdatesEnabled() {
+ return AddonManager.updateEnabled && AddonManager.autoUpdateDefault;
+ }
+
+ toggleAutomaticUpdates() {
+ if (!this.automaticUpdatesEnabled()) {
+ // One or both of the prefs is false, i.e. the checkbox is not
+ // checked. Now toggle both to true. If the user wants us to
+ // auto-update add-ons, we also need to auto-check for updates.
+ AddonManager.updateEnabled = true;
+ AddonManager.autoUpdateDefault = true;
+ } else {
+ // Both prefs are true, i.e. the checkbox is checked.
+ // Toggle the auto pref to false, but don't touch the enabled check.
+ AddonManager.autoUpdateDefault = false;
+ }
+ // Record telemetry for changing the update policy.
+ let updatePolicy = [];
+ if (AddonManager.autoUpdateDefault) {
+ updatePolicy.push("default");
+ }
+ if (AddonManager.updateEnabled) {
+ updatePolicy.push("enabled");
+ }
+ this.recordActionEvent({
+ action: "setUpdatePolicy",
+ value: updatePolicy.join(","),
+ });
+ }
+
+ async resetAutomaticUpdates() {
+ let addons = await AddonManager.getAllAddons();
+ for (let addon of addons) {
+ if ("applyBackgroundUpdates" in addon) {
+ addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+ }
+ }
+ this.recordActionEvent({ action: "resetUpdatePolicy" });
+ }
+
+ getTelemetryViewName() {
+ return getTelemetryViewName(document.getElementById("page-header"));
+ }
+
+ recordActionEvent({ action, value }) {
+ AMTelemetry.recordActionEvent({
+ object: "aboutAddons",
+ view: this.getTelemetryViewName(),
+ action,
+ addon: this.addon,
+ value,
+ });
+ }
+
+ recordLinkEvent({ value }) {
+ AMTelemetry.recordLinkEvent({
+ object: "aboutAddons",
+ value,
+ extra: {
+ view: this.getTelemetryViewName(),
+ },
+ });
+ }
+}
+customElements.define("addon-page-options", AddonPageOptions);
+
class AddonOptions extends HTMLElement {
connectedCallback() {
if (!this.children.length) {
@@ -1719,6 +2268,8 @@ class AddonCard extends HTMLElement {
let install = addon.updateInstall;
if (install && install.state == AddonManager.STATE_AVAILABLE) {
this.updateInstall = install;
+ } else {
+ this.updateInstall = null;
}
if (this.children.length) {
this.render();
@@ -1760,18 +2311,14 @@ class AddonCard extends HTMLElement {
this.recordActionEvent("disable");
addon.userDisabled = true;
break;
- case "update-check":
+ case "update-check": {
this.recordActionEvent("checkForUpdate");
- let listener = {
- onUpdateAvailable(addon, install) {
- attachUpdateHandler(install);
- },
- onNoUpdateAvailable: () => {
- this.sendEvent("no-update");
- },
- };
- addon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+ let { found } = await checkForUpdate(addon);
+ if (!found) {
+ this.sendEvent("no-update");
+ }
break;
+ }
case "install-update":
this.updateInstall.install().then(
() => {
@@ -3197,6 +3744,7 @@ class DiscoveryView {
// Generic view management.
let mainEl = null;
+let addonPageHeader = null;
/**
* The name of the view for an element, used for telemetry.
@@ -3248,6 +3796,7 @@ var ScrollOffsets = {
*/
function initialize(opts) {
mainEl = document.getElementById("main");
+ addonPageHeader = document.getElementById("page-header");
loadViewFn = opts.loadViewFn;
replaceWithDefaultViewFn = opts.replaceWithDefaultViewFn;
setCategoryFn = opts.setCategoryFn;
@@ -3272,6 +3821,7 @@ function initialize(opts) {
async function show(type, param, { isKeyboardNavigation, historyEntryId }) {
let container = document.createElement("div");
container.setAttribute("current-view", type);
+ addonPageHeader.setViewInfo({ type, param });
if (type == "list") {
await new ListView({ param, root: container }).render();
} else if (type == "detail") {
diff --git a/toolkit/mozapps/extensions/content/aboutaddonsCommon.js b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
index 7263cf74536d..23cc190db456 100644
--- a/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
+++ b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
@@ -5,9 +5,10 @@
"use strict";
-/* exported attachUpdateHandler, gBrowser, getBrowserElement, isCorrectlySigned,
- * isDisabledUnsigned, isPending, loadReleaseNotes, openOptionsInTab,
- * promiseEvent, shouldShowPermissionsPrompt, showPermissionsPrompt */
+/* exported attachUpdateHandler, gBrowser, getBrowserElement,
+ * installAddonsFromFilePicker, isCorrectlySigned, isDisabledUnsigned,
+ * isPending, loadReleaseNotes, openOptionsInTab, promiseEvent,
+ * shouldShowPermissionsPrompt, showPermissionsPrompt */
const { AddonSettings } = ChromeUtils.import(
"resource://gre/modules/addons/AddonSettings.jsm"
@@ -206,3 +207,47 @@ 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);
+ });
+ });
+}
diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js
index 15455932043c..b378ca2a718f 100644
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -5,7 +5,6 @@
"use strict";
/* import-globals-from ../../../content/customElements.js */
-/* import-globals-from ../../../content/contentAreaUtils.js */
/* import-globals-from aboutaddonsCommon.js */
/* globals ProcessingInstruction */
/* exported loadView */
@@ -16,9 +15,6 @@ const { DeferredTask } = ChromeUtils.import(
const { AddonManager } = ChromeUtils.import(
"resource://gre/modules/AddonManager.jsm"
);
-const { AddonRepository } = ChromeUtils.import(
- "resource://gre/modules/addons/AddonRepository.jsm"
-);
ChromeUtils.defineModuleGetter(
this,
@@ -129,13 +125,15 @@ function initialize(event) {
addonPage.addEventListener("drop", function(event) {
gDragDrop.onDrop(event);
});
- addonPage.addEventListener("keypress", function(event) {
- gHeader.onKeyPress(event);
- });
if (!isDiscoverEnabled()) {
gViewDefault = "addons://list/extension";
}
+ // Support focusing the search bar from the XUL document.
+ document.addEventListener("keypress", e => {
+ htmlBrowser.contentDocument.querySelector("search-addons").handleEvent(e);
+ });
+
let helpButton = document.getElementById("helpButton");
let helpUrl =
Services.urlFormatter.formatURLPref("app.support.baseURL") + "addons-help";
@@ -166,15 +164,10 @@ function initialize(event) {
gViewController.initialize();
gCategories.initialize();
- gHeader.initialize();
gEventManager.initialize();
Services.obs.addObserver(sendEMPong, "EM-ping");
Services.obs.notifyObservers(window, "EM-loaded");
- if (!XPINSTALL_ENABLED) {
- document.getElementById("cmd_installFromFile").hidden = true;
- }
-
// If the initial view has already been selected (by a call to loadView from
// the above notifications) then bail out now
if (gViewController.initialViewSelected) {
@@ -229,25 +222,9 @@ function sendEMPong(aSubject, aTopic, aData) {
function recordLinkTelemetry(target) {
let extra = { view: getCurrentViewName() };
- if (target == "search") {
- let searchBar = document.getElementById("header-search");
- extra.type = searchBar.getAttribute("data-addon-type");
- }
AMTelemetry.recordLinkEvent({ object: "aboutAddons", value: target, extra });
}
-function recordActionTelemetry({ action, value, view, addon }) {
- view = view || getCurrentViewName();
- AMTelemetry.recordActionEvent({
- // The max-length for an object is 20, which it enough to be unique.
- object: "aboutAddons",
- value,
- view,
- action,
- addon,
- });
-}
-
async function recordViewTelemetry(param) {
let type;
let addon;
@@ -265,21 +242,6 @@ async function recordViewTelemetry(param) {
AMTelemetry.recordViewEvent({ view: getCurrentViewName(), addon, type });
}
-function recordSetUpdatePolicyTelemetry() {
- // Record telemetry for changing the update policy.
- let updatePolicy = [];
- if (AddonManager.autoUpdateDefault) {
- updatePolicy.push("default");
- }
- if (AddonManager.updateEnabled) {
- updatePolicy.push("enabled");
- }
- recordActionTelemetry({
- action: "setUpdatePolicy",
- value: updatePolicy.join(","),
- });
-}
-
function getCurrentViewName() {
let view = gViewController.currentViewObj;
let entries = Object.entries(gViewController.viewObjects);
@@ -324,27 +286,6 @@ function isDiscoverEnabled() {
return true;
}
-function setSearchLabel(type) {
- let searchLabel = document.getElementById("search-label");
- document
- .getElementById("header-search")
- .setAttribute("data-addon-type", type);
- let keyMap = {
- extension: "extension",
- shortcuts: "extension",
- theme: "theme",
- };
- if (type in keyMap) {
- searchLabel.textContent = gStrings.ext.GetStringFromName(
- `searchLabel.${keyMap[type]}`
- );
- searchLabel.hidden = false;
- } else {
- searchLabel.textContent = "";
- searchLabel.hidden = true;
- }
-}
-
/**
* A wrapper around the HTML5 session history service that allows the browser
* back/forward controls to work within the manager
@@ -365,14 +306,10 @@ var HTML5History = {
back() {
window.history.back();
- gViewController.updateCommand("cmd_back");
- gViewController.updateCommand("cmd_forward");
},
forward() {
window.history.forward();
- gViewController.updateCommand("cmd_back");
- gViewController.updateCommand("cmd_forward");
},
pushState(aState) {
@@ -393,8 +330,6 @@ var HTML5History = {
}
window.addEventListener("popstate", onStatePopped, true);
window.history.back();
- gViewController.updateCommand("cmd_back");
- gViewController.updateCommand("cmd_forward");
},
};
@@ -424,8 +359,6 @@ var FakeHistory = {
this.pos--;
gViewController.updateState(this.states[this.pos]);
- gViewController.updateCommand("cmd_back");
- gViewController.updateCommand("cmd_forward");
},
forward() {
@@ -435,8 +368,6 @@ var FakeHistory = {
this.pos++;
gViewController.updateState(this.states[this.pos]);
- gViewController.updateCommand("cmd_back");
- gViewController.updateCommand("cmd_forward");
},
pushState(aState) {
@@ -458,8 +389,6 @@ var FakeHistory = {
this.pos--;
gViewController.updateState(this.states[this.pos]);
- gViewController.updateCommand("cmd_back");
- gViewController.updateCommand("cmd_forward");
},
};
@@ -518,7 +447,6 @@ var gEventManager = {
AddonManager.addAddonListener(this);
this.refreshGlobalWarning();
- this.refreshAutoUpdateDefault();
},
shutdown() {
@@ -622,24 +550,6 @@ var gEventManager = {
page.removeAttribute("warning");
},
- refreshAutoUpdateDefault() {
- var updateEnabled = AddonManager.updateEnabled;
- var autoUpdateDefault = AddonManager.autoUpdateDefault;
-
- // The checkbox needs to reflect that both prefs need to be true
- // for updates to be checked for and applied automatically
- document
- .getElementById("utils-autoUpdateDefault")
- .setAttribute("checked", updateEnabled && autoUpdateDefault);
-
- document.getElementById(
- "utils-resetAddonUpdatesToAutomatic"
- ).hidden = !autoUpdateDefault;
- document.getElementById(
- "utils-resetAddonUpdatesToManual"
- ).hidden = autoUpdateDefault;
- },
-
onCompatibilityModeChanged() {
this.refreshGlobalWarning();
},
@@ -647,10 +557,6 @@ var gEventManager = {
onCheckUpdateSecurityChanged() {
this.refreshGlobalWarning();
},
-
- onUpdateModeChanged() {
- this.refreshAutoUpdateDefault();
- },
};
var gViewController = {
@@ -667,13 +573,11 @@ var gViewController = {
viewChangeCallback: null,
initialViewSelected: false,
lastHistoryIndex: -1,
- backButton: null,
initialize() {
this.viewPort = document.getElementById("view-port");
this.headeredViews = document.getElementById("headered-views");
this.headeredViewsDeck = document.getElementById("headered-views-content");
- this.backButton = document.getElementById("go-back");
this.viewObjects.shortcuts = htmlView("shortcuts");
this.viewObjects.list = htmlView("list");
@@ -867,25 +771,6 @@ var gViewController = {
recordViewTelemetry(view.param);
- let headingName = document.getElementById("heading-name");
- let headingLabel;
- if (view.type == "discover") {
- headingLabel = gStrings.ext.formatStringFromName("listHeading.discover", [
- gStrings.brandShortName,
- ]);
- } else {
- try {
- headingLabel = gStrings.ext.GetStringFromName(
- `listHeading.${view.param}`
- );
- } catch (e) {
- // Some views don't have a label, like the updates view.
- headingLabel = "";
- }
- }
- headingName.textContent = headingLabel;
- setSearchLabel(view.param);
-
if (aViewId == aPreviousView) {
this.currentViewObj.refresh(
view.param,
@@ -896,8 +781,6 @@ var gViewController = {
this.currentViewObj.show(view.param, ++this.currentViewRequest, aState);
}
- this.backButton.hidden = this.currentViewObj.isRoot || !gHistory.canGoBack;
-
this.initialViewSelected = true;
},
@@ -921,31 +804,6 @@ var gViewController = {
},
commands: {
- cmd_back: {
- isEnabled() {
- return gHistory.canGoBack;
- },
- doCommand() {
- gHistory.back();
- },
- },
-
- cmd_forward: {
- isEnabled() {
- return gHistory.canGoForward;
- },
- doCommand() {
- gHistory.forward();
- },
- },
-
- cmd_focusSearch: {
- isEnabled: () => true,
- doCommand() {
- gHeader.focusSearchBox();
- },
- },
-
cmd_enableCheckCompatibility: {
isEnabled() {
return true;
@@ -963,258 +821,6 @@ var gViewController = {
AddonManager.checkUpdateSecurity = true;
},
},
-
- cmd_toggleAutoUpdateDefault: {
- isEnabled() {
- return true;
- },
- doCommand() {
- if (!AddonManager.updateEnabled || !AddonManager.autoUpdateDefault) {
- // One or both of the prefs is false, i.e. the checkbox is not checked.
- // Now toggle both to true. If the user wants us to auto-update
- // add-ons, we also need to auto-check for updates.
- AddonManager.updateEnabled = true;
- AddonManager.autoUpdateDefault = true;
- } else {
- // Both prefs are true, i.e. the checkbox is checked.
- // Toggle the auto pref to false, but don't touch the enabled check.
- AddonManager.autoUpdateDefault = false;
- }
-
- recordSetUpdatePolicyTelemetry();
- },
- },
-
- cmd_resetAddonAutoUpdate: {
- isEnabled() {
- return true;
- },
- async doCommand() {
- let aAddonList = await AddonManager.getAllAddons();
- for (let addon of aAddonList) {
- if ("applyBackgroundUpdates" in addon) {
- addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
- }
- }
- recordActionTelemetry({ action: "resetUpdatePolicy" });
- },
- },
-
- cmd_goToDiscoverPane: {
- isEnabled() {
- return gDiscoverView.enabled;
- },
- doCommand() {
- gViewController.loadView("addons://discover/");
- },
- },
-
- cmd_goToRecentUpdates: {
- isEnabled() {
- return true;
- },
- doCommand() {
- gViewController.loadView("addons://updates/recent");
- },
- },
-
- cmd_goToAvailableUpdates: {
- isEnabled() {
- return true;
- },
- doCommand() {
- gViewController.loadView("addons://updates/available");
- },
- },
-
- cmd_findAllUpdates: {
- inProgress: false,
- isEnabled() {
- return !this.inProgress;
- },
- async doCommand() {
- this.inProgress = true;
- gViewController.updateCommand("cmd_findAllUpdates");
- document.getElementById("updates-noneFound").hidden = true;
- document.getElementById("updates-progress").hidden = false;
- document.getElementById("updates-manualUpdatesFound-btn").hidden = true;
-
- var pendingChecks = 0;
- var numUpdated = 0;
- var numManualUpdates = 0;
-
- let updateStatus = () => {
- if (pendingChecks > 0) {
- return;
- }
-
- this.inProgress = false;
- gViewController.updateCommand("cmd_findAllUpdates");
- document.getElementById("updates-progress").hidden = true;
- gUpdatesView.maybeRefresh();
-
- Services.obs.notifyObservers(null, "EM-update-check-finished");
-
- if (numManualUpdates > 0 && numUpdated == 0) {
- document.getElementById(
- "updates-manualUpdatesFound-btn"
- ).hidden = false;
- return;
- }
-
- if (numUpdated == 0) {
- document.getElementById("updates-noneFound").hidden = false;
- return;
- }
-
- document.getElementById("updates-installed").hidden = false;
- };
-
- var updateInstallListener = {
- onDownloadFailed() {
- pendingChecks--;
- updateStatus();
- },
- onInstallCancelled() {
- pendingChecks--;
- updateStatus();
- },
- onInstallFailed() {
- pendingChecks--;
- updateStatus();
- },
- onInstallEnded(aInstall, aAddon) {
- pendingChecks--;
- numUpdated++;
- updateStatus();
- },
- };
-
- var updateCheckListener = {
- onUpdateAvailable(aAddon, aInstall) {
- gEventManager.delegateAddonEvent("onUpdateAvailable", [
- aAddon,
- aInstall,
- ]);
- attachUpdateHandler(aInstall);
- if (AddonManager.shouldAutoUpdate(aAddon)) {
- aInstall.addListener(updateInstallListener);
- aInstall.install();
- } else {
- pendingChecks--;
- numManualUpdates++;
- updateStatus();
- }
- },
- onNoUpdateAvailable(aAddon) {
- pendingChecks--;
- updateStatus();
- },
- onUpdateFinished(aAddon, aError) {
- gEventManager.delegateAddonEvent("onUpdateFinished", [
- aAddon,
- aError,
- ]);
- },
- };
-
- let aAddonList = await AddonManager.getAddonsByTypes(null);
- for (let addon of aAddonList) {
- if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) {
- pendingChecks++;
- addon.findUpdates(
- updateCheckListener,
- AddonManager.UPDATE_WHEN_USER_REQUESTED
- );
- }
- }
-
- recordActionTelemetry({ action: "checkForUpdates" });
-
- if (pendingChecks == 0) {
- updateStatus();
- }
- },
- },
-
- cmd_installFromFile: {
- isEnabled() {
- return XPINSTALL_ENABLED;
- },
- doCommand() {
- const nsIFilePicker = Ci.nsIFilePicker;
- var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
- fp.init(
- window,
- gStrings.ext.GetStringFromName("installFromFile.dialogTitle"),
- nsIFilePicker.modeOpenMultiple
- );
- try {
- fp.appendFilter(
- gStrings.ext.GetStringFromName("installFromFile.filterName"),
- "*.xpi;*.jar;*.zip"
- );
- fp.appendFilters(nsIFilePicker.filterAll);
- } catch (e) {}
-
- fp.open(async result => {
- if (result != nsIFilePicker.returnOK) {
- return;
- }
-
- let installTelemetryInfo = {
- source: "about:addons",
- method: "install-from-file",
- };
-
- let browser = getBrowserElement();
- for (let file of fp.files) {
- let install = await AddonManager.getInstallForFile(
- file,
- null,
- installTelemetryInfo
- );
- AddonManager.installAddonFromAOM(
- browser,
- document.documentURIObject,
- install
- );
- recordActionTelemetry({
- action: "installFromFile",
- value: install.installId,
- });
- }
- });
- },
- },
-
- cmd_debugAddons: {
- isEnabled() {
- return true;
- },
- doCommand() {
- let mainWindow = window.windowRoot.ownerGlobal;
- recordLinkTelemetry("about:debugging");
- if ("switchToTabHavingURI" in mainWindow) {
- mainWindow.switchToTabHavingURI(
- `about:debugging#/runtime/this-firefox`,
- true,
- {
- triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
- }
- );
- }
- },
- },
-
- cmd_showShortcuts: {
- isEnabled() {
- return true;
- },
- doCommand() {
- gViewController.loadView("addons://shortcuts/shortcuts");
- },
- },
},
supportsCommand(aCommand) {
@@ -1581,85 +1187,6 @@ var gCategories = {
// categories are in the XUL markup.
gCategories._defineCustomElement();
-var gHeader = {
- _search: null,
- _dest: "",
-
- initialize() {
- this._search = document.getElementById("header-search");
-
- this._search.addEventListener("command", function(aEvent) {
- var query = aEvent.target.value;
- if (!query.length) {
- return;
- }
-
- let url = AddonRepository.getSearchURL(query);
-
- let browser = getBrowserElement();
- let chromewin = browser.ownerGlobal;
- chromewin.openLinkIn(url, "tab", {
- fromChrome: true,
- triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
- {}
- ),
- });
-
- recordLinkTelemetry("search");
- });
- },
-
- focusSearchBox() {
- this._search.focus();
- },
-
- onKeyPress(aEvent) {
- if (String.fromCharCode(aEvent.charCode) == "/") {
- this.focusSearchBox();
- }
- },
-
- get shouldShowNavButtons() {
- var docshellItem = window.docShell;
-
- // If there is no outer frame then make the buttons visible
- if (docshellItem.rootTreeItem == docshellItem) {
- return true;
- }
-
- var outerWin = docshellItem.rootTreeItem.domWindow;
- var outerDoc = outerWin.document;
- var node = outerDoc.getElementById("back-button");
- // If the outer frame has no back-button then make the buttons visible
- if (!node) {
- return true;
- }
-
- // If the back-button or any of its parents are hidden then make the buttons
- // visible
- while (node != outerDoc) {
- var style = outerWin.getComputedStyle(node);
- if (style.display == "none") {
- return true;
- }
- if (style.visibility != "visible") {
- return true;
- }
- node = node.parentNode;
- }
-
- return false;
- },
-
- get searchQuery() {
- return this._search.value;
- },
-
- set searchQuery(aQuery) {
- this._search.value = aQuery;
- },
-};
-
var gDiscoverView = {
node: null,
enabled: true,
diff --git a/toolkit/mozapps/extensions/content/extensions.xul b/toolkit/mozapps/extensions/content/extensions.xul
index e40367331c8f..8fdad1bb38ed 100644
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -13,15 +13,11 @@
@@ -32,7 +28,6 @@
-
@@ -65,30 +60,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -172,75 +147,6 @@