Bug 1285493: Add-on Manager should not be closed when an add-on with options_ui is disabled. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 19 Jul 2016 19:44:04 -0700
changeset 389795 9483680c19b6c36eba377f45d4fda34d5d9db241
parent 389550 5a91e5b49be3c1ba401b057e90c92d7488e3647d
child 525853 ec577dcb1f4783548d6a56465d024fd0f46f1aae
push id23514
push usermaglione.k@gmail.com
push dateWed, 20 Jul 2016 02:44:43 +0000
reviewersaswan
bugs1285493
milestone50.0a1
Bug 1285493: Add-on Manager should not be closed when an add-on with options_ui is disabled. r?aswan MozReview-Commit-ID: 8oRK8uPphDf
browser/components/extensions/ext-tabs.js
browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
toolkit/components/extensions/Extension.jsm
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -37,73 +37,75 @@ function getSender(context, target, send
     // The message came from an ExtensionContext. In that case, it should
     // include a tabId property (which is filled in by the page-open
     // listener below).
     sender.tab = TabManager.convert(context.extension, TabManager.getTab(sender.tabId));
     delete sender.tabId;
   }
 }
 
-// WeakMap[ExtensionContext -> {tab, parentWindow}]
-var pageDataMap = new WeakMap();
+function getDocShellOwner(docShell) {
+  let browser = docShell.chromeEventHandler;
+
+  let browserWindow = browser.ownerGlobal;
+
+  let {gBrowser} = browserWindow;
+  if (gBrowser) {
+    let tab = gBrowser.getTabForBrowser(browser);
+
+    return {browserWindow, tab};
+  }
+
+  return {};
+}
 
 /* eslint-disable mozilla/balanced-listeners */
 // This listener fires whenever an extension page opens in a tab
 // (either initiated by the extension or the user). Its job is to fill
 // in some tab-specific details and keep data around about the
 // ExtensionContext.
-extensions.on("page-load", (type, page, params, sender, delegate) => {
+extensions.on("page-load", (type, context, params, sender, delegate) => {
   if (params.type == "tab" || params.type == "popup") {
-    let browser = params.docShell.chromeEventHandler;
+    let {browserWindow, tab} = getDocShellOwner(params.docShell);
 
-    let parentWindow = browser.ownerGlobal;
-    page.windowId = WindowManager.getId(parentWindow);
-
-    let tab = parentWindow.gBrowser.getTabForBrowser(browser);
+    // FIXME: Handle tabs being moved between windows.
+    context.windowId = WindowManager.getId(browserWindow);
     if (tab) {
       sender.tabId = TabManager.getId(tab);
-      page.tabId = TabManager.getId(tab);
+      context.tabId = TabManager.getId(tab);
     }
-
-    pageDataMap.set(page, {tab, parentWindow});
   }
 
   delegate.getSender = getSender;
 });
 
-extensions.on("page-unload", (type, page) => {
-  pageDataMap.delete(page);
-});
-
-extensions.on("page-shutdown", (type, page) => {
-  if (pageDataMap.has(page)) {
-    let {tab, parentWindow} = pageDataMap.get(page);
-    pageDataMap.delete(page);
-
+extensions.on("page-shutdown", (type, context) => {
+  if (context.type == "tab") {
+    let {browserWindow, tab} = getDocShellOwner(context.docShell);
     if (tab) {
-      parentWindow.gBrowser.removeTab(tab);
+      browserWindow.gBrowser.removeTab(tab);
     }
   }
 });
 
 extensions.on("fill-browser-data", (type, browser, data, result) => {
   let tabId = TabManager.getBrowserId(browser);
   if (tabId == -1) {
     result.cancel = true;
     return;
   }
 
   data.tabId = tabId;
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 global.currentWindow = function(context) {
-  let pageData = pageDataMap.get(context);
-  if (pageData) {
-    return pageData.parentWindow;
+  let {browserWindow} = getDocShellOwner(context.docShell);
+  if (browserWindow) {
+    return browserWindow;
   }
   return WindowManager.topWindow;
 };
 
 let tabListener = {
   init() {
     if (this.initialized) {
       return;
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -252,8 +252,71 @@ add_task(function* test_options_no_manif
         browser.test.notifyFail("options-no-manifest");
       });
     },
   });
 
   yield extension.awaitFinish("options-no-manifest");
   yield extension.unload();
 });
+
+add_task(function* test_inline_options_uninstall() {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+  let extension = yield loadExtension({
+    manifest: {
+      "options_ui": {
+        "page": "options.html",
+      },
+    },
+
+    background: function() {
+      let _optionsPromise;
+      let awaitOptions = () => {
+        browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
+
+        return new Promise(resolve => {
+          _optionsPromise = {resolve};
+        });
+      };
+
+      browser.runtime.onMessage.addListener((msg, sender) => {
+        if (msg == "options.html") {
+          if (_optionsPromise) {
+            _optionsPromise.resolve(sender.tab);
+            _optionsPromise = null;
+          } else {
+            browser.test.fail("Saw unexpected options page load");
+          }
+        }
+      });
+
+      let firstTab;
+      browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
+        firstTab = tabs[0].id;
+
+        browser.test.log("Open options page. Expect fresh load.");
+        return Promise.all([
+          browser.runtime.openOptionsPage(),
+          awaitOptions(),
+        ]);
+      }).then(([, tab]) => {
+        browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
+        browser.test.assertTrue(tab.active, "Tab is active");
+        browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
+
+        browser.test.sendMessage("options-ui-open");
+      }).catch(error => {
+        browser.test.fail(`Error: ${error} :: ${error.stack}`);
+      });
+    },
+  });
+
+  yield extension.awaitMessage("options-ui-open");
+  yield extension.unload();
+
+  is(gBrowser.selectedBrowser.currentURI.spec, "about:addons",
+     "Add-on manager tab should still be open");
+
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -273,16 +273,21 @@ ExtensionContext = class extends BaseCon
     let filter = {extensionId: extension.id};
     this.messenger = new Messenger(this, [Services.mm, Services.ppmm], sender, filter, delegate);
 
     if (this.externallyVisible) {
       this.extension.views.add(this);
     }
   }
 
+  get docShell() {
+    return this.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+               .getInterface(Ci.nsIDocShell);
+  }
+
   get cloneScope() {
     return this.contentWindow;
   }
 
   get principal() {
     return this.contentWindow.document.nodePrincipal;
   }