Bug 1288901 - Destroy ExtensionContext at inner window destruction instead of unload
MozReview-Commit-ID: 4JI7PpAj9xd
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -755,25 +755,27 @@ GlobalManager = {
let incognito = PrivateBrowsingUtils.isContentWindowPrivate(contentWindow);
let context = new ExtensionContext(extension, {type, contentWindow, uri, docShell, incognito});
inject(extension, context);
if (type == "background") {
this._initializeBackgroundPage(contentWindow);
}
- let eventHandler = docShell.chromeEventHandler;
- let listener = event => {
- if (event.target != docShell.contentViewer.DOMDocument) {
- return;
+ let innerWindowID = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+
+ let onUnload = subject => {
+ let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (windowId == innerWindowID) {
+ Services.obs.removeObserver(onUnload, "inner-window-destroyed");
+ context.unload();
}
- eventHandler.removeEventListener("unload", listener, true);
- context.unload();
};
- eventHandler.addEventListener("unload", listener, true);
+ Services.obs.addObserver(onUnload, "inner-window-destroyed", false);
},
_initializeBackgroundPage(contentWindow) {
// Override the `alert()` method inside background windows;
// we alias it to console.log().
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1203394
let alertDisplayedWarning = false;
let alertOverwrite = text => {
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -75,16 +75,18 @@ skip-if = (os == 'android' || buildapp =
skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
[test_ext_storage_content.html]
[test_ext_storage_tab.html]
skip-if = os == 'android' # Android does not currently support tabs.
[test_ext_cookies.html]
[test_ext_background_api_injection.html]
[test_ext_background_generated_url.html]
[test_ext_background_teardown.html]
+[test_ext_tab_teardown.html]
+skip-if = (os == 'android') # Android does not support tabs API. Bug 1260250
[test_ext_i18n.html]
skip-if = (os == 'android') # Bug 1258975 on android.
[test_ext_web_accessible_resources.html]
skip-if = (os == 'android') # Bug 1258975 on android.
[test_ext_webrequest.html]
skip-if = (os == 'android' || buildapp == 'b2g') # webrequest api uninplemented (bug 1199504). Bug 1258975 on android.
[test_ext_webnavigation.html]
skip-if = (os == 'android' || buildapp == 'b2g') # needs TabManager which is not yet implemented. Bug 1258975 on android.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_tab_teardown.html
@@ -0,0 +1,168 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for extension tab teardown</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+
+<script>
+"use strict";
+
+// Test for tabs opened using tabs.create and window.open
+function* runTabReloadAndCloseTest({extension, isInitiallyBlankUrl}) {
+ let chromeScript = SpecialPowers.loadChromeScript(
+ SimpleTest.getTestFileURL("file_teardown_test.js"));
+ yield chromeScript.promiseOneMessage("chromescript-startup");
+ function* getContextEvents() {
+ chromeScript.sendAsyncMessage("get-context-events");
+ let contextEvents = yield chromeScript.promiseOneMessage("context-events");
+ dump(JSON.stringify(contextEvents));
+ return contextEvents.filter(event => event.extensionId == extension.id);
+ }
+
+ extension.sendMessage("open extension page");
+ let extensionPageUrl = yield extension.awaitMessage("extension page loaded");
+
+ let contextEvents = yield* getContextEvents();
+ is(contextEvents.length, 1, "ExtensionContext change for opening a tab");
+ is(contextEvents[0].eventType, "load", "create ExtensionContext for tab");
+ is(contextEvents[0].url, extensionPageUrl,
+ "ExtensionContext URL after tab creation should be tab URL");
+
+ extension.sendMessage("reload extension page");
+ let extensionPageUrl2 = yield extension.awaitMessage("extension page loaded");
+
+ // When the tab is opened with window.open, the initial URL happens to be
+ // about:blank.
+ if (isInitiallyBlankUrl) {
+ is(extensionPageUrl, "about:blank",
+ "The tab URL before reload should be about:blank");
+ isnot(extensionPageUrl, extensionPageUrl2,
+ "After a page reload the tab URL shouldn't be blank.");
+ } else {
+ is(extensionPageUrl, extensionPageUrl2,
+ "The tab's URL is expected to not change after a page reload");
+ }
+
+ contextEvents = yield* getContextEvents();
+ is(contextEvents.length, 2, "ExtensionContext change after tab reload");
+ is(contextEvents[0].eventType, "unload", "unload old ExtensionContext");
+ is(contextEvents[0].url, extensionPageUrl,
+ "ExtensionContext URL before reload should be tab URL");
+ is(contextEvents[1].eventType, "load", "create new ExtensionContext for tab");
+ is(contextEvents[1].url, extensionPageUrl2,
+ "ExtensionContext URL after reload should be tab URL");
+
+ extension.sendMessage("close extension page");
+ yield extension.awaitMessage("closed extension page");
+
+ contextEvents = yield* getContextEvents();
+ is(contextEvents.length, 1, "ExtensionContext after closing tab");
+ is(contextEvents[0].eventType, "unload", "unload tab's ExtensionContext");
+ is(contextEvents[0].url, extensionPageUrl2,
+ "ExtensionContext URL at closing tab should be tab URL");
+
+ chromeScript.sendAsyncMessage("cleanup");
+ chromeScript.destroy();
+ yield extension.unload();
+}
+
+add_task(function* test_extension_page_tabs_create_reload_and_close() {
+ function backgroundScript() {
+ let tabId;
+ browser.test.onMessage.addListener(msg => {
+ if (msg === "open extension page") {
+ chrome.tabs.create({url: "page.html"}, tab => {
+ tabId = tab.id;
+ });
+ } else if (msg === "reload extension page") {
+ chrome.tabs.reload(tabId);
+ } else if (msg === "close extension page") {
+ chrome.tabs.remove(tabId, () => {
+ browser.test.sendMessage("closed extension page");
+ });
+ }
+ });
+ }
+
+ function pageScript() {
+ browser.test.sendMessage("extension page loaded", document.URL);
+ }
+
+ let extensionData = {
+ background: `(${backgroundScript})();`,
+ files: {
+ "page.html": `<!DOCTYPE html><meta charset="utf-8"><script src="page.js"><\/script>`,
+ "page.js": `(${pageScript})();`,
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ yield extension.startup();
+
+ yield* runTabReloadAndCloseTest({extension});
+});
+
+add_task(function* test_extension_page_window_open_reload_and_close() {
+ // This tests whether a context that is opened via window.open is properly
+ // disposed when the tab closes.
+ // The background page cannot use window.open (bugzil.la/1282021), so we open
+ // another extension page that manages the window.open-tab for testing.
+ function backgroundScript() {
+ chrome.tabs.create({url: "window.open.html"});
+ }
+
+ function windowOpenScript() {
+ let win;
+ browser.test.onMessage.addListener(msg => {
+ if (msg === "open extension page") {
+ win = window.open("page.html");
+ } else if (msg === "reload extension page") {
+ win.location.reload();
+ } else if (msg === "close extension page") {
+ browser.tabs.onRemoved.addListener(function listener() {
+ browser.tabs.onRemoved.removeListener(listener);
+ browser.test.sendMessage("closed extension page");
+ });
+ win.close();
+ }
+ });
+ browser.test.sendMessage("setup-intermediate-tab");
+ }
+
+ function pageScript() {
+ if (performance.navigation.type === 0) { // TYPE_NAVIGATION
+ // The ExtensionContext URL happens to be "about:blank" when the page is
+ // loaded via window.open().
+ browser.test.sendMessage("extension page loaded", "about:blank");
+ } else if (performance.navigation.type === 1) { // TYPE_RELOAD
+ browser.test.sendMessage("extension page loaded", document.URL);
+ } else {
+ browser.test.notifyFail(
+ "Unexpected navigation type: " + performance.navigation.type);
+ }
+ }
+
+ let extensionData = {
+ background: `(${backgroundScript})();`,
+ files: {
+ "page.html": `<!DOCTYPE html><meta charset="utf-8"><script src="page.js"><\/script>`,
+ "page.js": `(${pageScript})();`,
+ "window.open.html": `<!DOCTYPE html><meta charset="utf-8"><script src="window.open.js"><\/script>`,
+ "window.open.js": `(${windowOpenScript})();`,
+ },
+ };
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ yield extension.startup();
+ yield extension.awaitMessage("setup-intermediate-tab");
+ yield* runTabReloadAndCloseTest({extension, isInitiallyBlankUrl: true});
+});
+</script>
+
+</body>
+</html>