Bug 1276025: Stop using injectInDocShell to tag docShells with types. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 02 Jun 2016 20:53:41 -0700
changeset 374976 b25a85b454839906ecc3431eb0fb71d31411c693
parent 374975 40ef1d1fcc1570cfea6f493ea768882461856553
child 522725 00b4267337bb12f8e7c43a539d2fab6722970891
push id20127
push usermaglione.k@gmail.com
push dateFri, 03 Jun 2016 04:10:23 +0000
reviewersaswan
bugs1276025
milestone49.0a1
Bug 1276025: Stop using injectInDocShell to tag docShells with types. r?aswan MozReview-Commit-ID: 7h5PI2birY4
browser/components/extensions/ext-tabs.js
browser/components/extensions/ext-utils.js
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_incognito_popup.js
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ext-backgroundPage.js
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -52,19 +52,18 @@ var pageDataMap = new WeakMap();
 // ExtensionContext.
 extensions.on("page-load", (type, page, params, sender, delegate) => {
   if (params.type == "tab" || params.type == "popup") {
     let browser = params.docShell.chromeEventHandler;
 
     let parentWindow = browser.ownerDocument.defaultView;
     page.windowId = WindowManager.getId(parentWindow);
 
-    let tab = null;
-    if (params.type == "tab") {
-      tab = parentWindow.gBrowser.getTabForBrowser(browser);
+    let tab = parentWindow.gBrowser.getTabForBrowser(browser);
+    if (tab) {
       sender.tabId = TabManager.getId(tab);
       page.tabId = TabManager.getId(tab);
     }
 
     pageDataMap.set(page, {tab, parentWindow});
   }
 
   delegate.getSender = getSender;
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -186,22 +186,20 @@ class BasePopup {
     this.browserReady.then(() => {
       this.browser.removeEventListener("DOMWindowCreated", this, true);
       this.browser.removeEventListener("load", this, true);
       this.browser.removeEventListener("DOMTitleChanged", this, true);
       this.browser.removeEventListener("DOMWindowClose", this, true);
 
       this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
 
-      this.context.unload();
       this.browser.remove();
 
       this.browser = null;
       this.viewNode = null;
-      this.context = null;
     });
   }
 
   // Returns the name of the event fired on `viewNode` when the popup is being
   // destroyed. This must be implemented by every subclass.
   get DESTROY_EVENT() {
     throw new Error("Not implemented");
   }
@@ -247,16 +245,17 @@ class BasePopup {
     }
   }
 
   createBrowser(viewNode, popupURI) {
     let document = viewNode.ownerDocument;
     this.browser = document.createElementNS(XUL_NS, "browser");
     this.browser.setAttribute("type", "content");
     this.browser.setAttribute("disableglobalhistory", "true");
+    this.browser.setAttribute("webextension-view-type", "popup");
 
     // Note: When using noautohide panels, the popup manager will add width and
     // height attributes to the panel, breaking our resize code, if the browser
     // starts out smaller than 30px by 10px. This isn't an issue now, but it
     // will be if and when we popup debugging.
 
     // This overrides the content's preferred size when displayed in a
     // fixed-size, slide-in panel.
@@ -275,25 +274,17 @@ class BasePopup {
       this.browser.addEventListener("load", loadListener, true);
     }).then(() => {
       let {contentWindow} = this.browser;
 
       contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindowUtils)
                    .allowScriptsToClose();
 
-      this.context = new ExtensionContext(this.extension, {
-        type: "popup",
-        contentWindow,
-        uri: popupURI,
-        docShell: this.browser.docShell,
-      });
-
-      GlobalManager.injectInDocShell(this.browser.docShell, this.extension, this.context);
-      this.browser.setAttribute("src", this.context.uri.spec);
+      this.browser.setAttribute("src", popupURI.spec);
 
       this.browser.addEventListener("DOMWindowCreated", this, true);
       this.browser.addEventListener("load", this, true);
       this.browser.addEventListener("DOMTitleChanged", this, true);
       this.browser.addEventListener("DOMWindowClose", this, true);
     });
   }
 
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -25,16 +25,17 @@ support-files =
 [browser_ext_commands_execute_page_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_contextMenus.js]
 [browser_ext_currentWindow.js]
 [browser_ext_getViews.js]
 [browser_ext_history.js]
+[browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
 [browser_ext_pageAction_popup.js]
 [browser_ext_pageAction_simple.js]
 [browser_ext_popup_api_injection.js]
 [browser_ext_runtime_openOptionsPage.js]
 [browser_ext_runtime_setUninstallURL.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_incognito_popup.js
@@ -0,0 +1,107 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testIncognitoPopup() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+      "browser_action": {
+        "default_popup": "popup.html",
+      },
+      "page_action": {
+        "default_popup": "popup.html",
+      },
+    },
+
+    background() {
+      let resolveMessage;
+      browser.runtime.onMessage.addListener(msg => {
+        if (resolveMessage && msg.message == "popup-details") {
+          resolveMessage(msg);
+        }
+      });
+
+      let awaitPopup = windowId => {
+        return new Promise(resolve => {
+          resolveMessage = resolve;
+        }).then(msg => {
+          browser.test.assertEq(windowId, msg.windowId, "Got popup message from correct window");
+          return msg;
+        });
+      };
+
+      let testWindow = window => {
+        return browser.tabs.query({active: true, windowId: window.id}).then(([tab]) => {
+          return browser.pageAction.show(tab.id);
+        }).then(() => {
+          browser.test.sendMessage("click-pageAction");
+
+          return awaitPopup(window.id);
+        }).then(msg => {
+          browser.test.assertEq(window.incognito, msg.incognito, "Correct incognito status in pageAction popup");
+
+          browser.test.sendMessage("click-browserAction");
+
+          return awaitPopup(window.id);
+        }).then(msg => {
+          browser.test.assertEq(window.incognito, msg.incognito, "Correct incognito status in browserAction popup");
+        });
+      };
+
+      const URL = "http://example.com/incognito";
+      let windowReady = new Promise(resolve => {
+        browser.tabs.onUpdated.addListener(function listener(tabId, changed, tab) {
+          if (changed.status == "complete" && tab.url == URL) {
+            browser.tabs.onUpdated.removeListener(listener);
+            resolve();
+          }
+        });
+      });
+
+      browser.windows.getCurrent().then(window => {
+        return testWindow(window);
+      }).then(() => {
+        return browser.windows.create({incognito: true, url: URL});
+      }).then(window => {
+        return windowReady.then(() => {
+          return testWindow(window);
+        }).then(() => {
+          return browser.windows.remove(window.id);
+        });
+      }).then(() => {
+        browser.test.notifyPass("incognito");
+      }).catch(error => {
+        browser.test.fail(`Error: ${error} :: ${error.stack}`);
+        browser.test.notifyFail("incognito");
+      });
+    },
+
+    files: {
+      "popup.html": '<html><head><meta charset="utf-8"><script src="popup.js"></script></head></html>',
+
+      "popup.js": function() {
+        browser.windows.getCurrent().then(win => {
+          browser.runtime.sendMessage({
+            message: "popup-details",
+            windowId: win.id,
+            incognito: browser.extension.inIncognitoContext,
+          });
+          window.close();
+        });
+      },
+    },
+  });
+
+  extension.onMessage("click-browserAction", () => {
+    clickBrowserAction(extension, Services.wm.getMostRecentWindow("navigator:browser"));
+  });
+
+  extension.onMessage("click-pageAction", () => {
+    clickPageAction(extension, Services.wm.getMostRecentWindow("navigator:browser"));
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("incognito");
+  yield extension.unload();
+});
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -526,20 +526,16 @@ GlobalManager = {
 
     this.extensionMap.delete(extension.id);
   },
 
   getExtension(extensionId) {
     return this.extensionMap.get(extensionId);
   },
 
-  injectInDocShell(docShell, extension, context) {
-    this.docShells.set(docShell, {extension, context});
-  },
-
   injectInObject(extension, context, defaultCallback, dest, namespaces = null) {
     let api = Management.generateAPIs(extension, context, Management.apis, namespaces);
     injectAPI(api, dest);
 
     let schemaApi = Management.generateAPIs(extension, context, Management.schemaApis, namespaces);
 
     // Add in any extra API namespaces which do not have implementations
     // outside of their schema file.
@@ -628,48 +624,46 @@ GlobalManager = {
     }
 
     // We don't inject privileged APIs if the addonId is null
     // or doesn't exist.
     if (!this.extensionMap.has(id)) {
       return;
     }
 
-    let docShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                                .getInterface(Ci.nsIWebNavigation)
-                                .QueryInterface(Ci.nsIDocShellTreeItem)
-                                .sameTypeRootTreeItem
-                                .QueryInterface(Ci.nsIDocShell);
 
-    if (this.docShells.has(docShell)) {
-      let {extension, context} = this.docShells.get(docShell);
-      if (context && extension.id == id) {
-        inject(extension, context);
-      }
-      return;
-    }
+    let docShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIDocShell);
 
-    let extension = this.extensionMap.get(id);
-    let uri = contentWindow.document.documentURIObject;
-    let incognito = PrivateBrowsingUtils.isContentWindowPrivate(contentWindow);
+    let parentDocument = docShell.parent.QueryInterface(Ci.nsIDocShell)
+                                 .contentViewer.DOMDocument;
 
     let browser = docShell.chromeEventHandler;
+    if (contentWindow.frameElement &&
+        Services.scriptSecurityManager.isSystemPrincipal(parentDocument.nodePrincipal)) {
+      browser = contentWindow.frameElement;
+    }
 
     let type = "tab";
     if (browser instanceof Ci.nsIDOMElement) {
       if (browser.hasAttribute("webextension-view-type")) {
         type = browser.getAttribute("webextension-view-type");
       } else if (browser.classList.contains("inline-options-browser")) {
         // Options pages are currently displayed inline, but in Chrome
         // and in our UI mock-ups for a later milestone, they're
         // pop-ups.
         type = "popup";
       }
     }
 
+
+    let extension = this.extensionMap.get(id);
+    let uri = contentWindow.document.documentURIObject;
+    let incognito = PrivateBrowsingUtils.isContentWindowPrivate(contentWindow);
+
     let context = new ExtensionContext(extension, {type, contentWindow, uri, docShell, incognito});
     inject(extension, context);
 
     let eventHandler = docShell.chromeEventHandler;
     let listener = event => {
       if (event.target != docShell.contentViewer.DOMDocument) {
         return;
       }
--- a/toolkit/components/extensions/ext-backgroundPage.js
+++ b/toolkit/components/extensions/ext-backgroundPage.js
@@ -33,44 +33,40 @@ BackgroundPage.prototype = {
       url = this.extension.baseURI.resolve("_blank.html");
     }
 
     if (!this.extension.isExtensionURL(url)) {
       this.extension.manifestError("Background page must be a file within the extension");
       url = this.extension.baseURI.resolve("_blank.html");
     }
 
-    let uri = Services.io.newURI(url, null, null);
+    let system = Services.scriptSecurityManager.getSystemPrincipal();
 
-    let system = Services.scriptSecurityManager.getSystemPrincipal();
-    let interfaceRequestor = chromeWebNav.QueryInterface(Ci.nsIInterfaceRequestor);
-    let chromeShell = interfaceRequestor.getInterface(Ci.nsIDocShell);
+    let chromeShell = chromeWebNav.QueryInterface(Ci.nsIInterfaceRequestor)
+                                  .getInterface(Ci.nsIDocShell);
     chromeShell.createAboutBlankContentViewer(system);
 
     let chromeDoc = chromeWebNav.document;
     const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     let browser = chromeDoc.createElementNS(XUL_NS, "browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
+    browser.setAttribute("webextension-view-type", "background");
     chromeDoc.body.appendChild(browser);
 
     let frameLoader = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
     let docShell = frameLoader.docShell;
 
-    this.context = new ExtensionContext(this.extension, {type: "background", docShell, uri});
-    GlobalManager.injectInDocShell(docShell, this.extension, this.context);
-
     let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     this.webNav = webNav;
 
     webNav.loadURI(url, 0, null, null, null);
 
     let window = webNav.document.defaultView;
     this.contentWindow = window;
-    this.context.contentWindow = window;
 
     if (this.extension.addonData.instanceID) {
       AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID)
                   .then(addon => addon.setDebugGlobal(window));
     }
 
     // TODO: Right now we run onStartup after the background page
     // finishes. See if this is what Chrome does.
@@ -87,17 +83,17 @@ BackgroundPage.prototype = {
             require("devtools/client/framework/devtools-browser");
             let hudservice = require("devtools/client/webconsole/hudservice");
             hudservice.toggleBrowserConsole().catch(Cu.reportError);
           } else {
             // the Browser Console was already open
             consoleWindow.focus();
           }
 
-          this.context.contentWindow.console.warn("alert() is not supported in background windows; please use console.log instead.");
+          this.contentWindow.console.warn("alert() is not supported in background windows; please use console.log instead.");
 
           alertDisplayedWarning = true;
         }
 
         window.console.log(text);
       };
       Components.utils.exportFunction(alertOverwrite, window, {
         defineAs: "alert",