Bug 1324255 make webextension panels focused when opened, r?Gijs,rpl
MozReview-Commit-ID: mhjMWA7ss5
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -73,16 +73,17 @@ class BasePopup {
extension.callOnClose(this);
this.contentReady = new Promise(resolve => {
this._resolveContentReady = resolve;
});
this.viewNode.addEventListener(this.DESTROY_EVENT, this);
+ this.panel.addEventListener("popuppositioned", this, {once: true, capture: true});
this.browser = null;
this.browserLoaded = new Promise((resolve, reject) => {
this.browserLoadedDeferred = {resolve, reject};
});
this.browserReady = this.createBrowser(viewNode, popupURL);
BasePopup.instances.get(this.window).set(extension, this);
@@ -206,16 +207,21 @@ class BasePopup {
handleEvent(event) {
switch (event.type) {
case this.DESTROY_EVENT:
if (!this.destroyed) {
this.destroy();
}
break;
+ case "popuppositioned":
+ if (!this.destroyed) {
+ this.browser.messageManager.sendAsyncMessage("Extension:GrabFocus", {});
+ }
+ break;
}
}
createBrowser(viewNode, popupURL = null) {
let document = viewNode.ownerDocument;
let stack = document.createElementNS(XUL_NS, "stack");
stack.setAttribute("class", "webextension-popup-stack");
@@ -433,16 +439,17 @@ class ViewPopup extends BasePopup {
* browser was destroyed before it was fully loaded, and the popup
* should be closed, or `true` otherwise.
*/
async attach(viewNode) {
this.viewNode = viewNode;
this.viewNode.addEventListener(this.DESTROY_EVENT, this);
this.viewNode.setAttribute("closemenu", "none");
+ this.panel.addEventListener("popuppositioned", this, {once: true, capture: true});
if (this.extension.remote) {
this.panel.setAttribute("remote", "true");
}
// Wait until the browser element is fully initialized, and give it at least
// a short grace period to finish loading its initial content, if necessary.
//
// In practice, the browser that was created by the mousdown handler should
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -108,16 +108,17 @@ skip-if = (os == 'win' && ccov) # Bug 14
[browser_ext_pageAction_popup_resize.js]
[browser_ext_pageAction_show_matches.js]
[browser_ext_pageAction_simple.js]
[browser_ext_pageAction_telemetry.js]
[browser_ext_pageAction_title.js]
[browser_ext_popup_api_injection.js]
[browser_ext_popup_background.js]
[browser_ext_popup_corners.js]
+[browser_ext_popup_focus.js]
[browser_ext_popup_sendMessage.js]
[browser_ext_popup_shutdown.js]
[browser_ext_runtime_openOptionsPage.js]
[browser_ext_runtime_openOptionsPage_uninstall.js]
[browser_ext_runtime_setUninstallURL.js]
[browser_ext_sessions_forgetClosedTab.js]
[browser_ext_sessions_forgetClosedWindow.js]
[browser_ext_sessions_getRecentlyClosed.js]
--- a/browser/components/extensions/test/browser/browser_ext_currentWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -99,39 +99,35 @@ add_task(async function() {
extension.sendMessage(kind + "-check-current1");
is((await extension.awaitMessage("result")), winId, `${name} is on top (check 1) [${kind}]`);
extension.sendMessage(kind + "-check-current2");
is((await extension.awaitMessage("result")), winId, `${name} is on top (check 2) [${kind}]`);
extension.sendMessage(kind + "-check-current3");
is((await extension.awaitMessage("result")), winId, `${name} is on top (check 3) [${kind}]`);
}
- await focusWindow(win1);
- await checkWindow("background", winId1, "win1");
- await focusWindow(win2);
- await checkWindow("background", winId2, "win2");
-
async function triggerPopup(win, callback) {
await clickBrowserAction(extension, win);
await awaitExtensionPanel(extension, win);
await extension.awaitMessage("popup-ready");
await callback();
closeBrowserAction(extension, win);
}
- // Set focus to some other window.
- await focusWindow(window);
-
+ await focusWindow(win1);
+ await checkWindow("background", winId1, "win1");
await triggerPopup(win1, async function() {
await checkWindow("popup", winId1, "win1");
});
+ await focusWindow(win2);
+ await checkWindow("background", winId2, "win2");
await triggerPopup(win2, async function() {
await checkWindow("popup", winId2, "win2");
});
async function triggerPage(winId, name) {
extension.sendMessage("background-open-page", winId);
await extension.awaitMessage("page-ready");
await checkWindow("page", winId, name);
--- a/browser/components/extensions/test/browser/browser_ext_getViews.js
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -143,32 +143,28 @@ add_task(async function() {
let tabId2 = await openTab(winId2);
await checkViews("background", 2, 0, 0);
await checkViewsWithFilter({windowId: winId2}, 1);
await checkViewsWithFilter({tabId: tabId2}, 1);
async function triggerPopup(win, callback) {
+ // Window needs focus to open popups.
+ await focusWindow(win);
await clickBrowserAction(extension, win);
await awaitExtensionPanel(extension, win);
await extension.awaitMessage("popup-ready");
await callback();
closeBrowserAction(extension, win);
}
- // The popup occasionally closes prematurely if we open it immediately here.
- // I'm not sure what causes it to close (it's something internal, and seems to
- // be focus-related, but it's not caused by JS calling hidePopup), but even a
- // short timeout seems to consistently fix it.
- await new Promise(resolve => win1.setTimeout(resolve, 10));
-
await triggerPopup(win1, async function() {
await checkViews("background", 2, 1, 0);
await checkViews("popup", 2, 1, 1);
await checkViewsWithFilter({windowId: winId1}, 2);
await checkViewsWithFilter({type: "popup", tabId: -1}, 1);
});
await triggerPopup(win2, async function() {
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_popup_focus.js
@@ -0,0 +1,73 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const DUMMY_PAGE = "http://example.com/browser/browser/components/extensions/test/browser/file_dummy.html";
+
+add_task(async function testPageActionFocus() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "page_action": {
+ "default_popup": "popup.html",
+ "show_matches": ["<all_urls>"],
+ },
+ },
+ files: {
+ "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8">
+ <script src="popup.js"></script>
+ </head><body>
+ </body></html>
+ `,
+ "popup.js": function() {
+ window.addEventListener("focus", (event) => {
+ browser.test.assertEq(true, document.hasFocus(), "document should be focused");
+ browser.test.notifyPass("focused");
+ }, {once: true});
+ },
+ },
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE);
+
+ await extension.startup();
+ let finish = extension.awaitFinish("focused");
+ clickPageAction(extension);
+ await finish;
+
+ let panelId = `${makeWidgetId(extension.id)}-panel`;
+ let panel = document.getElementById(panelId);
+ panel.hidePopup();
+ await BrowserTestUtils.removeTab(tab);
+ await extension.unload();
+});
+
+add_task(async function testBrowserActionFocus() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {"default_popup": "popup.html"},
+ },
+ files: {
+ "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8">
+ <script src="popup.js"></script>
+ </head><body>
+ </body></html>
+ `,
+ "popup.js": function() {
+ window.addEventListener("focus", (event) => {
+ browser.test.assertEq(true, document.hasFocus(), "document should be focused");
+ browser.test.notifyPass("focused");
+ }, {once: true});
+ },
+ },
+ });
+ await extension.startup();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE);
+ let finish = extension.awaitFinish("focused");
+ clickBrowserAction(extension);
+ await finish;
+
+ await closeBrowserAction(extension);
+
+ await BrowserTestUtils.removeTab(tab);
+ await extension.unload();
+});
--- a/toolkit/components/extensions/ext-browser-content.js
+++ b/toolkit/components/extensions/ext-browser-content.js
@@ -109,16 +109,20 @@ const BrowserListener = {
receiveMessage({name, data}) {
if (name === "Extension:InitBrowser") {
this.init(data);
} else if (name === "Extension:UnblockParser") {
if (this.unblockParser) {
this.unblockParser();
this.blockingPromise = null;
}
+ } else if (name === "Extension:GrabFocus") {
+ content.window.requestAnimationFrame(() => {
+ Services.focus.focusedWindow = content.window;
+ });
}
},
loadStylesheets() {
let winUtils = getWinUtils(content);
for (let url of this.stylesheets) {
winUtils.addSheet(ExtensionCommon.stylesheetMap.get(url), winUtils.AGENT_SHEET);
@@ -299,16 +303,17 @@ const BrowserListener = {
}
sendAsyncMessage("Extension:BrowserResized", result);
},
};
addMessageListener("Extension:InitBrowser", BrowserListener);
addMessageListener("Extension:UnblockParser", BrowserListener);
+addMessageListener("Extension:GrabFocus", BrowserListener);
var WebBrowserChrome = {
onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
// isAppTab is the value for the docShell that received the click. We're
// handling this in the top-level frame and want traversal behavior to
// match the value for this frame rather than any subframe, so we pass
// through the docShell.isAppTab value rather than what we were handed.
return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, docShell.isAppTab);