bug 1301227 - add allowScriptsToClose option to windows.create(), r?kmag
MozReview-Commit-ID: 4p8A1y2FALX
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -925,12 +925,18 @@ var UserContextIdNotifier = {
UserContextIdNotifier.init();
ExtensionContent.init(this);
addEventListener("unload", () => {
ExtensionContent.uninit(this);
RefreshBlocker.uninit();
});
+addMessageListener("AllowScriptsToClose", () => {
+ content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .allowScriptsToClose();
+});
+
addEventListener("MozAfterPaint", function onFirstPaint() {
removeEventListener("MozAfterPaint", onFirstPaint);
sendAsyncMessage("Browser:FirstPaint");
});
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -8,18 +8,23 @@ XPCOMUtils.defineLazyServiceGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
EventManager,
+ promiseObserved,
} = ExtensionUtils;
+function onXULFrameLoaderCreated({target}) {
+ target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
+}
+
extensions.registerSchemaAPI("windows", "addon_parent", context => {
let {extension} = context;
return {
windows: {
onCreated:
new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
fire(WindowManager.convert(extension, window));
}).api(),
@@ -89,16 +94,20 @@ extensions.registerSchemaAPI("windows",
let args = Cc["@mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray);
if (createData.tabId !== null) {
if (createData.url !== null) {
return Promise.reject({message: "`tabId` may not be used in conjunction with `url`"});
}
+ if (createData.allowScriptsToClose) {
+ return Promise.reject({message: "`tabId` may not be used in conjunction with `allowScriptsToClose`"});
+ }
+
let tab = TabManager.getTab(createData.tabId, context);
// Private browsing tabs can only be moved to private browsing
// windows.
let incognito = PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser);
if (createData.incognito !== null && createData.incognito != incognito) {
return Promise.reject({message: "`incognito` property must match the incognito state of tab"});
}
@@ -131,48 +140,52 @@ extensions.registerSchemaAPI("windows",
if (createData.incognito !== null) {
if (createData.incognito) {
features.push("private");
} else {
features.push("non-private");
}
}
+ let {allowScriptsToClose, url} = createData;
+ if (allowScriptsToClose === null) {
+ allowScriptsToClose = typeof url === "string" && url.startsWith("moz-extension://");
+ }
+
let window = Services.ww.openWindow(null, "chrome://browser/content/browser.xul", "_blank",
features.join(","), args);
WindowManager.updateGeometry(window, createData);
// TODO: focused, type
return new Promise(resolve => {
window.addEventListener("load", function listener() {
window.removeEventListener("load", listener);
if (createData.state == "maximized" || createData.state == "normal" ||
(createData.state == "fullscreen" && AppConstants.platform != "macosx")) {
window.document.documentElement.setAttribute("sizemode", createData.state);
} else if (createData.state !== null) {
- // window.minimize() has no useful effect until the window has
- // been shown.
-
- let obs = doc => {
- if (doc === window.document) {
- Services.obs.removeObserver(obs, "document-shown");
- WindowManager.setState(window, createData.state);
- resolve();
- }
- };
- Services.obs.addObserver(obs, "document-shown", false);
- return;
+ // window.minimize() has no effect until the window has been shown.
+ return promiseObserved("document-shown", doc => doc == window.document).then(() => {
+ WindowManager.setState(window, createData.state);
+ resolve();
+ });
}
-
resolve();
});
}).then(() => {
+ if (allowScriptsToClose) {
+ for (let {linkedBrowser} of window.gBrowser.tabs) {
+ onXULFrameLoaderCreated({target: linkedBrowser});
+ linkedBrowser.addEventListener( // eslint-disable-line mozilla/balanced-listeners
+ "XULFrameLoaderCreated", onXULFrameLoaderCreated);
+ }
+ }
return WindowManager.convert(extension, window);
});
},
update: function(windowId, updateInfo) {
if (updateInfo.state !== null && updateInfo.state != "normal") {
if (updateInfo.left !== null || updateInfo.top !== null ||
updateInfo.width !== null || updateInfo.height !== null) {
--- a/browser/components/extensions/schemas/windows.json
+++ b/browser/components/extensions/schemas/windows.json
@@ -323,16 +323,21 @@
"$ref": "CreateType",
"optional": true,
"description": "Specifies what type of browser window to create. The 'panel' and 'detached_panel' types create a popup unless the '--enable-panels' flag is set."
},
"state": {
"$ref": "WindowState",
"optional": true,
"description": "The initial state of the window. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'."
+ },
+ "allowScriptsToClose": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Allow scripts to close the window."
}
},
"optional": true
},
{
"type": "function",
"name": "callback",
"optional": true,
--- a/browser/components/extensions/test/browser/browser_ext_windows_create.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create.js
@@ -160,8 +160,65 @@ add_task(function* testWindowCreateParam
},
});
yield extension.startup();
yield extension.awaitFinish("window-create-params");
yield extension.unload();
});
+// Tests allowScriptsToClose option
+add_task(function* test_allowScriptsToClose() {
+ const files = {
+ "dummy.html": "<meta charset=utf-8><script src=close.js></script>",
+ "close.js": function() {
+ window.close();
+ if (!window.closed) {
+ browser.test.sendMessage("close-failed");
+ }
+ },
+ };
+
+ function background() {
+ browser.test.onMessage.addListener((msg, options) => {
+ function listener(_, {status}, {url}) {
+ if (status == "complete" && url == options.url) {
+ browser.tabs.onUpdated.removeListener(listener);
+ browser.tabs.executeScript({file: "close.js"});
+ }
+ }
+ options.url = browser.runtime.getURL(options.url);
+ browser.windows.create(options);
+ if (msg === "create+execute") {
+ browser.tabs.onUpdated.addListener(listener);
+ }
+ });
+ browser.test.notifyPass();
+ }
+
+ const example = "http://example.com/";
+ const manifest = {permissions: ["tabs", example]};
+
+ const extension = ExtensionTestUtils.loadExtension({files, background, manifest});
+ yield SpecialPowers.pushPrefEnv({set: [["dom.allow_scripts_to_close_windows", false]]});
+
+ yield extension.startup();
+ yield extension.awaitFinish();
+
+ extension.sendMessage("create", {url: "dummy.html"});
+ let win = yield BrowserTestUtils.waitForNewWindow();
+ yield BrowserTestUtils.windowClosed(win);
+ info("script allowed to close the window");
+
+ extension.sendMessage("create+execute", {url: example});
+ win = yield BrowserTestUtils.waitForNewWindow();
+ yield extension.awaitMessage("close-failed");
+ info("script prevented from closing the window");
+ win.close();
+
+ extension.sendMessage("create+execute", {url: example, allowScriptsToClose: true});
+ win = yield BrowserTestUtils.waitForNewWindow();
+ yield BrowserTestUtils.windowClosed(win);
+ info("script allowed to close the window");
+
+ yield SpecialPowers.popPrefEnv();
+ yield extension.unload();
+});