Bug 1362224: Cache per-tab icon data for browserAction/pageAction. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 05 May 2017 12:27:49 -0700
changeset 573466 c8757cca78007663677c8440cb805c3c066f430d
parent 573465 60175653dc7b3966cf7df0e4a02dc7305051e839
child 573467 016d9cf4c5a803dede3c38ddc9fb893b7c0978c3
child 573489 e1fcbbdd1bde8875990f38812ea45bc89f859c75
push id57395
push usermaglione.k@gmail.com
push dateFri, 05 May 2017 19:29:20 +0000
reviewersaswan
bugs1362224
milestone55.0a1
Bug 1362224: Cache per-tab icon data for browserAction/pageAction. r?aswan MozReview-Commit-ID: JifAtY36gKA
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-pageAction.js
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -14,16 +14,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
                                    "@mozilla.org/inspector/dom-utils;1",
                                    "inIDOMUtils");
 
 Cu.import("resource://gre/modules/EventEmitter.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
+  DefaultWeakMap,
   IconDetails,
 } = ExtensionUtils;
 
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 function isAncestorOrSelf(target, node) {
@@ -52,16 +53,18 @@ this.browserAction = class extends Exten
     return browserActionMap.get(extension);
   }
 
   onManifestEntry(entryName) {
     let {extension} = this;
 
     let options = extension.manifest.browser_action;
 
+    this.iconData = new DefaultWeakMap(icons => this.getIconData(icons));
+
     let widgetId = makeWidgetId(extension.id);
     this.id = `${widgetId}-browser-action`;
     this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
     this.widget = null;
 
     this.pendingPopup = null;
     this.pendingPopupTimeout = null;
 
@@ -387,43 +390,56 @@ this.browserAction = class extends Exten
     if (badgeNode) {
       let color = tabData.badgeBackgroundColor;
       if (color) {
         color = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3] / 255})`;
       }
       badgeNode.style.backgroundColor = color || "";
     }
 
+    let {style, legacy} = this.iconData.get(tabData.icon);
     const LEGACY_CLASS = "toolbarbutton-legacy-addon";
-    node.classList.remove(LEGACY_CLASS);
+    if (legacy) {
+      node.classList.add(LEGACY_CLASS);
+    } else {
+      node.classList.remove(LEGACY_CLASS);
+    }
 
+    node.setAttribute("style", style);
+  }
+
+  getIconData(icons) {
     let baseSize = 16;
-    let {icon, size} = IconDetails.getPreferredIcon(tabData.icon, this.extension, baseSize);
+    let {icon, size} = IconDetails.getPreferredIcon(icons, this.extension, baseSize);
+
+    let legacy = false;
 
     // If the best available icon size is not divisible by 16, check if we have
     // an 18px icon to fall back to, and trim off the padding instead.
     if (size % 16 && !icon.endsWith(".svg")) {
-      let result = IconDetails.getPreferredIcon(tabData.icon, this.extension, 18);
+      let result = IconDetails.getPreferredIcon(icons, this.extension, 18);
 
       if (result.size % 18 == 0) {
         baseSize = 18;
         icon = result.icon;
-        node.classList.add(LEGACY_CLASS);
+        legacy = true;
       }
     }
 
     let getIcon = size => IconDetails.escapeUrl(
-      IconDetails.getPreferredIcon(tabData.icon, this.extension, size).icon);
+      IconDetails.getPreferredIcon(icons, this.extension, size).icon);
 
-    node.setAttribute("style", `
+    let style = `
       --webextension-menupanel-image: url("${getIcon(32)}");
       --webextension-menupanel-image-2x: url("${getIcon(64)}");
       --webextension-toolbar-image: url("${IconDetails.escapeUrl(icon)}");
       --webextension-toolbar-image-2x: url("${getIcon(baseSize * 2)}");
-    `);
+    `;
+
+    return {style, legacy};
   }
 
   // Update the toolbar button for a given window.
   updateWindow(window) {
     let widget = this.widget.forWindow(window);
     if (widget) {
       let tab = window.gBrowser.selectedTab;
       this.updateButton(widget.node, this.tabContext.get(tab));
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -3,31 +3,34 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "PanelPopup",
                                   "resource:///modules/ExtensionPopups.jsm");
 
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
+  DefaultWeakMap,
   IconDetails,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 let pageActionMap = new WeakMap();
 
 this.pageAction = class extends ExtensionAPI {
   static for(extension) {
     return pageActionMap.get(extension);
   }
 
   onManifestEntry(entryName) {
     let {extension} = this;
     let options = extension.manifest.page_action;
 
+    this.iconData = new DefaultWeakMap(icons => this.getIconData(icons));
+
     this.id = makeWidgetId(extension.id) + "-page-action";
 
     this.tabManager = extension.tabManager;
 
     this.defaults = {
       show: false,
       title: options.default_title || extension.name,
       icon: IconDetails.normalize({path: options.default_icon}, extension),
@@ -107,35 +110,42 @@ this.pageAction = class extends Extensio
     let button = this.getButton(window);
 
     if (tabData.show) {
       // Update the title and icon only if the button is visible.
 
       let title = tabData.title || this.extension.name;
       button.setAttribute("tooltiptext", title);
       button.setAttribute("aria-label", title);
-
-      // These URLs should already be properly escaped, but make doubly sure CSS
-      // string escape characters are escaped here, since they could lead to a
-      // sandbox break.
-      let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
+      button.classList.add("webextension-page-action");
 
-      let getIcon = size => escape(IconDetails.getPreferredIcon(tabData.icon, this.extension, size).icon);
+      let {style} = this.iconData.get(tabData.icon);
 
-      button.setAttribute("style", `
-        --webextension-urlbar-image: url("${getIcon(16)}");
-        --webextension-urlbar-image-2x: url("${getIcon(32)}");
-      `);
-
-      button.classList.add("webextension-page-action");
+      button.setAttribute("style", style);
     }
 
     button.hidden = !tabData.show;
   }
 
+  getIconData(icons) {
+    // These URLs should already be properly escaped, but make doubly sure CSS
+    // string escape characters are escaped here, since they could lead to a
+    // sandbox break.
+    let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
+
+    let getIcon = size => escape(IconDetails.getPreferredIcon(icons, this.extension, size).icon);
+
+    let style = `
+      --webextension-urlbar-image: url("${getIcon(16)}");
+      --webextension-urlbar-image-2x: url("${getIcon(32)}");
+    `;
+
+    return style;
+  }
+
   // Create an |image| node and add it to the |urlbar-icons|
   // container in the given window.
   addButton(window) {
     let document = window.document;
 
     let button = document.createElement("image");
     button.id = this.id;
     button.setAttribute("class", "urlbar-icon");