Bug 1285493: Add-on Manager should not be closed when an add-on with options_ui is disabled. r?aswan
MozReview-Commit-ID: 8oRK8uPphDf
--- 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;
}