Bug 1330369 - Part 2 - Add browser_style support to ext-sidebar.js r=kmag
authorMatthew Wein <mwein@mozilla.com>
Thu, 25 May 2017 19:46:59 -0400
changeset 409112 a766850786e747f8e1495992b07c8212c1ab3306
parent 409111 163ef155b600a49c1799275d415eda74bdf5d3d2
child 409113 8092c26b85d4273e0797638df567601e250808e0
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1330369
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1330369 - Part 2 - Add browser_style support to ext-sidebar.js r=kmag MozReview-Commit-ID: AIGuVv98HGR
browser/base/content/webext-panels.js
browser/components/extensions/ext-sidebarAction.js
browser/components/extensions/schemas/sidebar_action.json
toolkit/components/extensions/ext-browser-content.js
--- a/browser/base/content/webext-panels.js
+++ b/browser/base/content/webext-panels.js
@@ -7,22 +7,22 @@
 /* import-globals-from browser.js */
 /* import-globals-from nsContextMenu.js */
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
                                   "resource://gre/modules/ExtensionParent.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
+  extensionStylesheets,
   promiseEvent,
 } = ExtensionUtils;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
-
 function getBrowser(sidebar) {
   let browser = document.getElementById("webext-panels-browser");
   if (browser) {
     return Promise.resolve(browser);
   }
 
   browser = document.createElementNS(XUL_NS, "browser");
   browser.setAttribute("id", "webext-panels-browser");
@@ -49,25 +49,35 @@ function getBrowser(sidebar) {
   } else {
     readyPromise = Promise.resolve();
   }
   document.documentElement.appendChild(browser);
 
   return readyPromise.then(() => {
     browser.messageManager.loadFrameScript("chrome://browser/content/content.js", false);
     ExtensionParent.apiManager.emit("extension-browser-inserted", browser);
+
+    if (sidebar.browserStyle) {
+      browser.messageManager.loadFrameScript(
+        "chrome://extensions/content/ext-browser-content.js", false);
+
+      browser.messageManager.sendAsyncMessage("Extension:InitBrowser", {
+        stylesheets: extensionStylesheets,
+      });
+    }
     return browser;
   });
 }
 
 function loadWebPanel() {
   let sidebarURI = new URL(location);
   let sidebar = {
     uri: sidebarURI.searchParams.get("panel"),
     remote: sidebarURI.searchParams.get("remote"),
+    browserStyle: sidebarURI.searchParams.get("browser-style"),
   };
   getBrowser(sidebar).then(browser => {
     browser.loadURI(sidebar.uri);
   });
 }
 
 function load() {
   this.loadWebPanel();
--- a/browser/components/extensions/ext-sidebarAction.js
+++ b/browser/components/extensions/ext-sidebarAction.js
@@ -38,16 +38,20 @@ this.sidebarAction = class extends Exten
     let options = extension.manifest.sidebar_action;
 
     // Add the extension to the sidebar menu.  The sidebar widget will copy
     // from that when it is viewed, so we shouldn't need to update that.
     let widgetId = makeWidgetId(extension.id);
     this.id = `${widgetId}-sidebar-action`;
     this.menuId = `menu_${this.id}`;
 
+    // We default browser_style to true because this is a new API and
+    // we therefore don't need to worry about breaking existing add-ons.
+    this.browserStyle = options.browser_style || options.browser_style === null;
+
     this.defaults = {
       enabled: true,
       title: options.default_title || extension.name,
       icon: IconDetails.normalize({path: options.default_icon}, extension),
       panel: options.default_panel || "",
     };
 
     this.tabContext = new TabContext(tab => Object.create(this.defaults),
@@ -115,34 +119,42 @@ this.sidebarAction = class extends Exten
       let widget = CustomizableUI.getWidget("sidebar-button");
       if (!widget.areaType) {
         CustomizableUI.addWidgetToArea("sidebar-button", CustomizableUI.AREA_NAVBAR, 0);
       }
     }
   }
 
   sidebarUrl(panel) {
+    let url = `${sidebarURL}?panel=${encodeURIComponent(panel)}`;
+
     if (this.extension.remote) {
-      return `${sidebarURL}?remote=1&panel=${encodeURIComponent(panel)}`;
+      url += "&remote=1";
     }
-    return `${sidebarURL}?&panel=${encodeURIComponent(panel)}`;
+
+    if (this.browserStyle) {
+      url += "&browser-style=1";
+    }
+
+    return url;
   }
 
   createMenuItem(window, details) {
     let {document} = window;
 
     // Use of the broadcaster allows browser-sidebar.js to properly manage the
     // checkmarks in the menus.
     let broadcaster = document.createElementNS(XUL_NS, "broadcaster");
     broadcaster.setAttribute("id", this.id);
     broadcaster.setAttribute("autoCheck", "false");
     broadcaster.setAttribute("type", "checkbox");
     broadcaster.setAttribute("group", "sidebar");
     broadcaster.setAttribute("label", details.title);
     broadcaster.setAttribute("sidebarurl", this.sidebarUrl(details.panel));
+
     // oncommand gets attached to menuitem, so we use the observes attribute to
     // get the command id we pass to SidebarUI.
     broadcaster.setAttribute("oncommand", "SidebarUI.toggle(this.getAttribute('observes'))");
 
     let menuitem = document.createElementNS(XUL_NS, "menuitem");
     menuitem.setAttribute("id", this.menuId);
     menuitem.setAttribute("observes", this.id);
     menuitem.setAttribute("class", "menuitem-iconic webextension-menuitem");
--- a/browser/components/extensions/schemas/sidebar_action.json
+++ b/browser/components/extensions/schemas/sidebar_action.json
@@ -17,16 +17,20 @@
                 "type": "string",
                 "optional": true,
                 "preprocess": "localize"
               },
               "default_icon": {
                 "$ref": "IconPath",
                 "optional": true
               },
+              "browser_style": {
+                "type": "boolean",
+                "optional": true
+              },
               "default_panel": {
                 "type": "string",
                 "format": "strictRelativeUrl",
                 "preprocess": "localize"
               }
             },
             "optional": true
           }
--- a/toolkit/components/extensions/ext-browser-content.js
+++ b/toolkit/components/extensions/ext-browser-content.js
@@ -66,46 +66,52 @@ const BrowserListener = {
   init({allowScriptsToClose, blockParser, fixedWidth, maxHeight, maxWidth, stylesheets, isInline}) {
     this.fixedWidth = fixedWidth;
     this.stylesheets = stylesheets || [];
 
     this.isInline = isInline;
     this.maxWidth = maxWidth;
     this.maxHeight = maxHeight;
 
+    this.blockParser = blockParser;
+    this.needsResize = fixedWidth || maxHeight || maxWidth;
+
     this.oldBackground = null;
 
     if (allowScriptsToClose) {
       getWinUtils(content).allowScriptsToClose();
     }
 
     // Force external links to open in tabs.
     docShell.isAppTab = true;
 
-    if (blockParser) {
+    if (this.blockParser) {
       this.blockingPromise = new Promise(resolve => {
         this.unblockParser = resolve;
       });
+      addEventListener("DOMDocElementInserted", this, true);
     }
 
+    addEventListener("load", this, true);
     addEventListener("DOMWindowCreated", this, true);
-    addEventListener("load", this, true);
     addEventListener("DOMContentLoaded", this, true);
     addEventListener("DOMWindowClose", this, true);
     addEventListener("MozScrolledAreaChanged", this, true);
-    addEventListener("DOMDocElementInserted", this, true);
   },
 
   destroy() {
+    if (this.blockParser) {
+      removeEventListener("DOMDocElementInserted", this, true);
+    }
+
+    removeEventListener("load", this, true);
     removeEventListener("DOMWindowCreated", this, true);
-    removeEventListener("load", this, true);
     removeEventListener("DOMContentLoaded", this, true);
     removeEventListener("DOMWindowClose", this, true);
     removeEventListener("MozScrolledAreaChanged", this, true);
-    removeEventListener("DOMDocElementInserted", this, true);
   },
 
   receiveMessage({name, data}) {
     if (name === "Extension:InitBrowser") {
       this.init(data);
     } else if (name === "Extension:UnblockParser") {
       if (this.unblockParser) {
         this.unblockParser();
@@ -142,17 +148,20 @@ const BrowserListener = {
 
           sendAsyncMessage("Extension:DOMWindowClose");
         }
         break;
 
       case "DOMContentLoaded":
         if (event.target === content.document) {
           sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href});
-          this.handleDOMChange(true);
+
+          if (this.needsResize) {
+            this.handleDOMChange(true);
+          }
         }
         break;
 
       case "load":
         if (event.target.contentWindow === content) {
           // For about:addons inline <browser>s, we currently receive a load
           // event on the <browser> element, but no load or DOMContentLoaded
           // events from the content window.
@@ -162,16 +171,20 @@ const BrowserListener = {
           if (this.isInline) {
             this.loadStylesheets();
           }
           sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href});
         } else if (event.target !== content.document) {
           break;
         }
 
+        if (!this.needsResize) {
+          break;
+        }
+
         // We use a capturing listener, so we get this event earlier than any
         // load listeners in the content page. Resizing after a timeout ensures
         // that we calculate the size after the entire event cycle has completed
         // (unless someone spins the event loop, anyway), and hopefully after
         // the content has made any modifications.
         Promise.resolve().then(() => {
           this.handleDOMChange(true);
         });
@@ -182,17 +195,19 @@ const BrowserListener = {
             attributes: true,
             characterData: true,
             childList: true,
             subtree: true,
           });
         break;
 
       case "MozScrolledAreaChanged":
-        this.handleDOMChange();
+        if (this.needsResize) {
+          this.handleDOMChange();
+        }
         break;
     }
   },
 
   // Resizes the browser to match the preferred size of the content (debounced).
   handleDOMChange(ignoreThrottling = false) {
     if (ignoreThrottling && this.resizeTimeout) {
       clearTimeout(this.resizeTimeout);