зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1755763
- Update browserAction.openPopup API r=geckoview-reviewers,robwu,owlish
To align with other browsers, the user gesture requirement has been removed and there is now an optional first argument which can be used to provide a windowId. Differential Revision: https://phabricator.services.mozilla.com/D139796
This commit is contained in:
Родитель
3cfcd785c8
Коммит
b7782d615a
|
@ -36,7 +36,7 @@ ChromeUtils.defineModuleGetter(
|
|||
"resource://gre/modules/ExtensionPermissions.jsm"
|
||||
);
|
||||
|
||||
var { DefaultWeakMap } = ExtensionUtils;
|
||||
var { DefaultWeakMap, ExtensionError } = ExtensionUtils;
|
||||
|
||||
var { ExtensionParent } = ChromeUtils.import(
|
||||
"resource://gre/modules/ExtensionParent.jsm"
|
||||
|
@ -256,6 +256,9 @@ this.browserAction = class extends ExtensionAPIPersistent {
|
|||
button: event.button || 0,
|
||||
modifiers: clickModifiersFromEvent(event),
|
||||
};
|
||||
// The openPopupWithoutUserInteraction flag may be set by openPopup.
|
||||
this.openPopupWithoutUserInteraction =
|
||||
event.detail?.openPopupWithoutUserInteraction === true;
|
||||
},
|
||||
|
||||
onViewShowing: async event => {
|
||||
|
@ -269,7 +272,10 @@ this.browserAction = class extends ExtensionAPIPersistent {
|
|||
let tabbrowser = document.defaultView.gBrowser;
|
||||
|
||||
let tab = tabbrowser.selectedTab;
|
||||
let popupURL = this.action.triggerClickOrPopup(tab, this.lastClickInfo);
|
||||
|
||||
let popupURL = !this.openPopupWithoutUserInteraction
|
||||
? this.action.triggerClickOrPopup(tab, this.lastClickInfo)
|
||||
: this.action.getPopupUrl(tab);
|
||||
|
||||
if (popupURL) {
|
||||
try {
|
||||
|
@ -326,6 +332,51 @@ this.browserAction = class extends ExtensionAPIPersistent {
|
|||
this.widget = widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popup. The caller is expected to check if a popup is set before
|
||||
* this is called.
|
||||
*
|
||||
* @param {Window} window Window to show the popup for
|
||||
* @param {boolean} openPopupWithoutUserInteraction
|
||||
* If the popup was opened without user interaction
|
||||
*/
|
||||
async openPopup(window, openPopupWithoutUserInteraction = false) {
|
||||
const widgetForWindow = this.widget.forWindow(window);
|
||||
|
||||
if (!widgetForWindow.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to focus hidden or minimized windows (both for the API, and to
|
||||
// avoid an issue where showing the popup in a non-focused window
|
||||
// immediately triggers a popuphidden event)
|
||||
window.focus();
|
||||
|
||||
if (widgetForWindow.node.open) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL) {
|
||||
await window.document.getElementById("nav-bar").overflowable.show();
|
||||
}
|
||||
|
||||
// This should already have been checked by callers, but acts as an
|
||||
// an additional safeguard. It also makes sure we don't dispatch a click
|
||||
// if the URL is removed while waiting for the overflow to show above.
|
||||
if (!this.action.getPopupUrl(window.gBrowser.selectedTab)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const event = new window.CustomEvent("command", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: {
|
||||
openPopupWithoutUserInteraction,
|
||||
},
|
||||
});
|
||||
widgetForWindow.node.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers this browser action for the given window, with the same effects as
|
||||
* if it were clicked by a user.
|
||||
|
@ -335,34 +386,21 @@ this.browserAction = class extends ExtensionAPIPersistent {
|
|||
*
|
||||
* @param {Window} window
|
||||
*/
|
||||
async triggerAction(window) {
|
||||
triggerAction(window) {
|
||||
let popup = ViewPopup.for(this.extension, window);
|
||||
if (!this.pendingPopup && popup) {
|
||||
popup.closePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
let widget = this.widget.forWindow(window);
|
||||
let tab = window.gBrowser.selectedTab;
|
||||
|
||||
if (!widget.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
let popupUrl = this.action.triggerClickOrPopup(tab, {
|
||||
button: 0,
|
||||
modifiers: [],
|
||||
});
|
||||
if (popupUrl) {
|
||||
if (this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL) {
|
||||
await window.document.getElementById("nav-bar").overflowable.show();
|
||||
}
|
||||
|
||||
let event = new window.CustomEvent("command", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
widget.node.dispatchEvent(event);
|
||||
this.openPopup(window);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -696,9 +734,27 @@ this.browserAction = class extends ExtensionAPIPersistent {
|
|||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
openPopup: () => {
|
||||
let window = windowTracker.topWindow;
|
||||
this.triggerAction(window);
|
||||
openPopup: async options => {
|
||||
const isHandlingUserInput =
|
||||
context.callContextData?.isHandlingUserInput;
|
||||
|
||||
if (
|
||||
!Services.prefs.getBoolPref(
|
||||
"extensions.openPopupWithoutUserGesture.enabled"
|
||||
) &&
|
||||
!isHandlingUserInput
|
||||
) {
|
||||
throw new ExtensionError("openPopup requires a user gesture");
|
||||
}
|
||||
|
||||
const window =
|
||||
typeof options?.windowId === "number"
|
||||
? windowTracker.getWindow(options.windowId, context)
|
||||
: windowTracker.getTopNormalWindow(context);
|
||||
|
||||
if (this.action.getPopupUrl(window.gBrowser.selectedTab, true)) {
|
||||
await this.openPopup(window, !isHandlingUserInput);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -119,11 +119,21 @@ async function test_clickData({ manifest_version, persistent }) {
|
|||
|
||||
async function test_clickData_reset({ manifest_version }) {
|
||||
const action = manifest_version < 3 ? "browser_action" : "action";
|
||||
const browser_action_command =
|
||||
manifest_version < 3 ? "_execute_browser_action" : "_execute_action";
|
||||
const browser_action_key = "j";
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
manifest_version,
|
||||
[action]: {},
|
||||
page_action: {},
|
||||
commands: {
|
||||
[browser_action_command]: {
|
||||
suggested_key: {
|
||||
default: "Alt+Shift+J",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
async background() {
|
||||
|
@ -132,10 +142,7 @@ async function test_clickData_reset({ manifest_version }) {
|
|||
}
|
||||
|
||||
function onPageActionClicked(tab, info) {
|
||||
// openPopup requires user interaction, such as a page action click.
|
||||
// NOTE: this triggers the browserAction onClicked event as a side-effect
|
||||
// of triggering the browserAction popup through browserAction.openPopup.
|
||||
browser.browserAction.openPopup();
|
||||
browser.test.sendMessage("open-popup");
|
||||
}
|
||||
|
||||
const { manifest_version } = browser.runtime.getManifest();
|
||||
|
@ -195,10 +202,17 @@ async function test_clickData_reset({ manifest_version }) {
|
|||
// spawned again to handle the action onClicked event.
|
||||
await extension.awaitMessage("ready");
|
||||
} else {
|
||||
extension.onMessage("open-popup", () => {
|
||||
EventUtils.synthesizeKey(browser_action_key, {
|
||||
altKey: true,
|
||||
shiftKey: true,
|
||||
});
|
||||
});
|
||||
|
||||
// pageAction should only be available in MV2 extensions.
|
||||
await clickPageAction(extension);
|
||||
|
||||
// NOTE: the pageAction event listener then triggers browserAction.onClicked
|
||||
// as a side effect of calling browserAction.openPopup.
|
||||
assertInfoReset(await extension.awaitMessage("onClick"));
|
||||
}
|
||||
|
||||
|
|
|
@ -15,11 +15,6 @@ add_task(async function test_openPopup_requires_user_interaction() {
|
|||
"pageAction.openPopup may only be called from a user input handler",
|
||||
"The error is informative."
|
||||
);
|
||||
await browser.test.assertRejects(
|
||||
browser.browserAction.openPopup(),
|
||||
"browserAction.openPopup may only be called from a user input handler",
|
||||
"The error is informative."
|
||||
);
|
||||
await browser.test.assertRejects(
|
||||
browser.sidebarAction.open(),
|
||||
"sidebarAction.open may only be called from a user input handler",
|
||||
|
@ -64,7 +59,6 @@ add_task(async function test_openPopup_requires_user_interaction() {
|
|||
"tab.html": `
|
||||
<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"></head><body>
|
||||
<button id="openBrowserAction">openBrowserAction</button>
|
||||
<button id="openPageAction">openPageAction</button>
|
||||
<button id="openSidebarAction">openSidebarAction</button>
|
||||
<button id="closeSidebarAction">closeSidebarAction</button>
|
||||
|
@ -79,13 +73,6 @@ add_task(async function test_openPopup_requires_user_interaction() {
|
|||
</body></html>
|
||||
`,
|
||||
"tab.js": function() {
|
||||
document.getElementById("openBrowserAction").addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
browser.browserAction.openPopup();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
document.getElementById("openPageAction").addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
|
@ -133,23 +120,9 @@ add_task(async function test_openPopup_requires_user_interaction() {
|
|||
return open;
|
||||
}
|
||||
|
||||
function testActiveTab(extension, expected) {
|
||||
let ext = WebExtensionPolicy.getByID(extension.id).extension;
|
||||
is(
|
||||
ext.tabManager.hasActiveTabPermission(gBrowser.selectedTab),
|
||||
expected,
|
||||
"activeTab permission is correct"
|
||||
);
|
||||
}
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
await click("#openBrowserAction");
|
||||
testActiveTab(extension, false);
|
||||
closeBrowserAction(extension);
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
await click("#openPageAction");
|
||||
closePageAction(extension);
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
@ -174,17 +147,4 @@ add_task(async function test_openPopup_requires_user_interaction() {
|
|||
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
await extension.unload();
|
||||
|
||||
extensionData.manifest.permissions = ["activeTab"];
|
||||
extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
await click("#openBrowserAction");
|
||||
testActiveTab(extension, true);
|
||||
closeBrowserAction(extension);
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
await extension.unload();
|
||||
});
|
||||
|
|
|
@ -50,9 +50,10 @@ class BrowserAction extends BrowserActionBase {
|
|||
});
|
||||
}
|
||||
|
||||
openPopup() {
|
||||
const tab = tabTracker.activeTab;
|
||||
const popupUri = this.triggerClickOrPopup(tab);
|
||||
openPopup(tab, openPopupWithoutUserInteraction = false) {
|
||||
const popupUri = openPopupWithoutUserInteraction
|
||||
? this.getPopupUrl(tab)
|
||||
: this.triggerClickOrPopup(tab);
|
||||
const actionObject = this.getContextData(tab);
|
||||
const action = this.helper.extractProperties(actionObject);
|
||||
this.helper.sendRequest(tab.id, {
|
||||
|
@ -151,8 +152,35 @@ this.browserAction = class extends ExtensionAPIPersistent {
|
|||
extensionApi: this,
|
||||
}).api(),
|
||||
|
||||
openPopup: function() {
|
||||
action.openPopup();
|
||||
openPopup: options => {
|
||||
const isHandlingUserInput =
|
||||
context.callContextData?.isHandlingUserInput;
|
||||
|
||||
if (
|
||||
!Services.prefs.getBoolPref(
|
||||
"extensions.openPopupWithoutUserGesture.enabled"
|
||||
) &&
|
||||
!isHandlingUserInput
|
||||
) {
|
||||
throw new ExtensionError("openPopup requires a user gesture");
|
||||
}
|
||||
|
||||
const currentWindow = windowTracker.getCurrentWindow(context);
|
||||
|
||||
const window =
|
||||
typeof options?.windowId === "number"
|
||||
? windowTracker.getWindow(options.windowId, context)
|
||||
: currentWindow;
|
||||
|
||||
if (window !== currentWindow) {
|
||||
throw new ExtensionError(
|
||||
"Only the current window is supported on Android."
|
||||
);
|
||||
}
|
||||
|
||||
if (this.action.getPopupUrl(window.tab, true)) {
|
||||
action.openPopup(window.tab, !isHandlingUserInput);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1974,6 +1974,12 @@ pref("extensions.manifestV2.actionsPopupURLRestricted", false);
|
|||
#else
|
||||
pref("extensions.unifiedExtensions.enabled", false);
|
||||
#endif
|
||||
// Whether to enable the updated openPopup API.
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("extensions.openPopupWithoutUserGesture.enabled", true);
|
||||
#else
|
||||
pref("extensions.openPopupWithoutUserGesture.enabled", false);
|
||||
#endif
|
||||
|
||||
// Modifier key prefs: default to Windows settings,
|
||||
// menu access key = alt, accelerator key = control.
|
||||
|
|
|
@ -185,14 +185,25 @@ class PanelActionBase {
|
|||
*
|
||||
* @param {XULElement} tab
|
||||
* The tab the popup refers to.
|
||||
* @param {boolean} strict
|
||||
* If errors should be thrown if a URL is not available.
|
||||
* @returns {string}
|
||||
* The popup URL if a popup is present, undefined otherwise.
|
||||
*/
|
||||
getPopupUrl(tab) {
|
||||
getPopupUrl(tab, strict = false) {
|
||||
if (!this.isShownForTab(tab)) {
|
||||
if (strict) {
|
||||
throw new ExtensionError("Popup is disabled");
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
let popupUrl = this.getProperty(tab, "popup");
|
||||
|
||||
if (strict && !popupUrl) {
|
||||
throw new ExtensionError("No popup URL is set");
|
||||
}
|
||||
|
||||
return popupUrl;
|
||||
}
|
||||
|
||||
|
|
|
@ -469,10 +469,24 @@
|
|||
{
|
||||
"name": "openPopup",
|
||||
"type": "function",
|
||||
"requireUserInput": true,
|
||||
"description": "Opens the extension popup window in the active window.",
|
||||
"description": "Opens the extension popup window in the specified window.",
|
||||
"async": true,
|
||||
"parameters": []
|
||||
"parameters": [
|
||||
{
|
||||
"name": "options",
|
||||
"optional": true,
|
||||
"type": "object",
|
||||
"description": "An object with information about the popup to open.",
|
||||
"properties": {
|
||||
"windowId": {
|
||||
"type": "integer",
|
||||
"minimum": -2,
|
||||
"optional": true,
|
||||
"description": "Defaults to the $(topic:current-window)[current window]."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
|
|
|
@ -87,6 +87,12 @@ skip-if = toolkit == 'android' || tsan # near-permafail after landing bug 127005
|
|||
[test_ext_background_canvas.html]
|
||||
[test_ext_background_page.html]
|
||||
skip-if = (toolkit == 'android') # android doesn't have devtools
|
||||
[test_ext_browserAction_openPopup.html]
|
||||
[test_ext_browserAction_openPopup_incognito_window.html]
|
||||
skip-if = os == "android" # cannot open private windows - bug 1372178
|
||||
[test_ext_browserAction_openPopup_windowId.html]
|
||||
skip-if = os == "android" # only the current window is supported - bug 1795956
|
||||
[test_ext_browserAction_openPopup_without_pref.html]
|
||||
[test_ext_browsingData_indexedDB.html]
|
||||
[test_ext_browsingData_localStorage.html]
|
||||
[test_ext_browsingData_pluginData.html]
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>action.openPopup Test</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script type="text/javascript" src="head.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: {
|
||||
id: "open-popup@tests.mozilla.org",
|
||||
}
|
||||
},
|
||||
browser_action: {
|
||||
default_popup: "popup.html",
|
||||
},
|
||||
permissions: ["activeTab"]
|
||||
},
|
||||
|
||||
useAddonManager: "android-only",
|
||||
};
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["extensions.openPopupWithoutUserGesture.enabled", true],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
async function testActiveTabPermissions(withHandlingUserInput) {
|
||||
const background = async function(withHandlingUserInput) {
|
||||
let tabPromise;
|
||||
let tabLoadedPromise = new Promise(resolve => {
|
||||
// Wait for the tab to actually finish loading (bug 1589734)
|
||||
browser.tabs.onUpdated.addListener(async (id, { status }) => {
|
||||
if (id === (await tabPromise).id && status === "complete") {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
tabPromise = browser.tabs.create({ url: "https://www.example.com" });
|
||||
tabLoadedPromise.then(() => {
|
||||
// Once the popup opens, check if we have activeTab permission
|
||||
browser.runtime.onMessage.addListener(async msg => {
|
||||
if (msg === "popup-open") {
|
||||
let tabs = await browser.tabs.query({});
|
||||
|
||||
browser.test.assertEq(
|
||||
withHandlingUserInput ? 1 : 0,
|
||||
tabs.filter((t) => typeof t.url !== "undefined").length,
|
||||
"active tab permission only granted with user input"
|
||||
);
|
||||
|
||||
await browser.tabs.remove((await tabPromise).id);
|
||||
browser.test.sendMessage("activeTabsChecked");
|
||||
}
|
||||
});
|
||||
|
||||
if (withHandlingUserInput) {
|
||||
browser.test.withHandlingUserInput(() => {
|
||||
browser.browserAction.openPopup();
|
||||
});
|
||||
} else {
|
||||
browser.browserAction.openPopup();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
...extensionData,
|
||||
|
||||
background: `(${background})(${withHandlingUserInput})`,
|
||||
|
||||
files: {
|
||||
"popup.html": `<!DOCTYPE html><meta charset="utf-8"><script src="popup.js"><\/script>`,
|
||||
async "popup.js"() {
|
||||
browser.runtime.sendMessage("popup-open");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("activeTabsChecked");
|
||||
await extension.unload();
|
||||
}
|
||||
|
||||
add_task(async function test_browserAction_openPopup_activeTab() {
|
||||
await testActiveTabPermissions(true);
|
||||
});
|
||||
|
||||
add_task(async function test_browserAction_openPopup_non_activeTab() {
|
||||
await testActiveTabPermissions(false);
|
||||
});
|
||||
|
||||
add_task(async function test_browserAction_openPopup_invalid_states() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
...extensionData,
|
||||
|
||||
background: async function() {
|
||||
await browser.browserAction.setPopup({ popup: "" })
|
||||
await browser.test.assertRejects(
|
||||
browser.browserAction.openPopup(),
|
||||
"No popup URL is set",
|
||||
"Should throw when no URL is set"
|
||||
);
|
||||
|
||||
await browser.browserAction.disable()
|
||||
await browser.test.assertRejects(
|
||||
browser.browserAction.openPopup(),
|
||||
"Popup is disabled",
|
||||
"Should throw when disabled"
|
||||
);
|
||||
|
||||
browser.test.notifyPass("invalidStates");
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("invalidStates");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_browserAction_openPopup_no_click_event() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
...extensionData,
|
||||
|
||||
background: async function() {
|
||||
let clicks = 0;
|
||||
|
||||
browser.browserAction.onClicked.addListener(() => {
|
||||
clicks++;
|
||||
});
|
||||
|
||||
// Test with popup set
|
||||
await browser.browserAction.openPopup();
|
||||
browser.test.sendMessage("close-popup");
|
||||
|
||||
browser.test.onMessage.addListener(async (msg) => {
|
||||
if (msg === "popup-closed") {
|
||||
// Test without popup
|
||||
await browser.browserAction.setPopup({ popup: "" });
|
||||
|
||||
await browser.test.assertRejects(
|
||||
browser.browserAction.openPopup(),
|
||||
"No popup URL is set",
|
||||
"Should throw when no URL is set"
|
||||
);
|
||||
|
||||
// We expect the last call to be a no-op, so there isn't really anything
|
||||
// to wait on. Instead, check that no clicks are registered after waiting
|
||||
// for a sufficient amount of time.
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
setTimeout(() => {
|
||||
browser.test.assertEq(0, clicks, "onClicked should not be called");
|
||||
browser.test.notifyPass("noClick");
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
extension.onMessage("close-popup", async () => {
|
||||
await AppTestDelegate.closeBrowserAction(window, extension);
|
||||
extension.sendMessage("popup-closed");
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("noClick");
|
||||
await extension.unload();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,129 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>action.openPopup Incognito Test</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script type="text/javascript" src="head.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: {
|
||||
id: "open-popup@tests.mozilla.org",
|
||||
}
|
||||
},
|
||||
browser_action: {
|
||||
default_popup: "popup.html",
|
||||
},
|
||||
permissions: ["activeTab"]
|
||||
},
|
||||
|
||||
useAddonManager: "android-only",
|
||||
};
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["extensions.openPopupWithoutUserGesture.enabled", true],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
async function getIncognitoWindow() {
|
||||
// Since events will be limited based on incognito, we need a
|
||||
// spanning extension to get the tab id so we can test access failure.
|
||||
|
||||
let windowWatcher = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
permissions: ["tabs"],
|
||||
},
|
||||
background: function() {
|
||||
browser.windows.create({ incognito: true }).then(({ id: windowId }) => {
|
||||
browser.test.onMessage.addListener(async data => {
|
||||
if (data === "close") {
|
||||
await browser.windows.remove(windowId);
|
||||
browser.test.sendMessage("window-closed");
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("window-id", windowId);
|
||||
});
|
||||
},
|
||||
incognitoOverride: "spanning",
|
||||
});
|
||||
|
||||
await windowWatcher.startup();
|
||||
let windowId = await windowWatcher.awaitMessage("window-id");
|
||||
|
||||
return {
|
||||
windowId,
|
||||
close: async () => {
|
||||
windowWatcher.sendMessage("close");
|
||||
await windowWatcher.awaitMessage("window-closed");
|
||||
await windowWatcher.unload();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
add_task(async function test_browserAction_openPopup_incognito_window() {
|
||||
if (AppConstants.platform == "android") {
|
||||
// TODO bug 1372178: Cannot open private windows from an extension.
|
||||
todo(false, "Cannot open private windows on Android");
|
||||
return;
|
||||
}
|
||||
|
||||
async function testWithIncognitoOverride(incognitoOverride) {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
...extensionData,
|
||||
|
||||
incognitoOverride,
|
||||
|
||||
background: async function() {
|
||||
browser.test.onMessage.addListener(async ({ windowId, incognitoOverride }) => {
|
||||
const openPromise = browser.browserAction.openPopup({ windowId });
|
||||
|
||||
if (incognitoOverride === "not_allowed") {
|
||||
await browser.test.assertRejects(
|
||||
openPromise,
|
||||
/Invalid window ID/,
|
||||
"Should prevent open popup call for incognito window"
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
browser.test.assertEq(await openPromise, undefined, "openPopup resolved");
|
||||
} catch (e) {
|
||||
browser.test.fail(`Unexpected error: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
browser.test.sendMessage("incognitoWindow");
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
let incognitoWindow = await getIncognitoWindow();
|
||||
await extension.sendMessage({ windowId: incognitoWindow.windowId, incognitoOverride });
|
||||
|
||||
await extension.awaitMessage("incognitoWindow");
|
||||
await extension.unload();
|
||||
|
||||
await incognitoWindow.close();
|
||||
}
|
||||
|
||||
for (const override of ["spanning", "not_allowed"]) {
|
||||
await testWithIncognitoOverride(override);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,150 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>action.openPopup Window ID Test</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script type="text/javascript" src="head.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: {
|
||||
id: "open-popup@tests.mozilla.org",
|
||||
}
|
||||
},
|
||||
browser_action: {
|
||||
default_popup: "popup.html",
|
||||
},
|
||||
permissions: ["activeTab"]
|
||||
},
|
||||
|
||||
useAddonManager: "android-only",
|
||||
};
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["extensions.openPopupWithoutUserGesture.enabled", true],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
async function testWithWindowState(state) {
|
||||
const background = async function(state) {
|
||||
const originalWindow = await browser.windows.getCurrent();
|
||||
|
||||
let newWindowPromise;
|
||||
const tabLoadedPromise = new Promise(resolve => {
|
||||
browser.tabs.onUpdated.addListener(async (id, { status }, tab) => {
|
||||
if (tab.windowId === (await newWindowPromise).id && status === "complete") {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
newWindowPromise = browser.windows.create({ url: "tab.html" });
|
||||
|
||||
tabLoadedPromise.then(async () => {
|
||||
const windowId = (await newWindowPromise).id;
|
||||
|
||||
switch (state) {
|
||||
case "inactive":
|
||||
const focusChangePromise = new Promise(resolve => {
|
||||
browser.windows.onFocusChanged.addListener((focusedWindowId) => {
|
||||
if (focusedWindowId === originalWindow.id) {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
});
|
||||
await browser.windows.update(originalWindow.id, { focused: true });
|
||||
await focusChangePromise;
|
||||
break;
|
||||
case "minimized":
|
||||
await browser.windows.update(windowId, { state: "minimized" });
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid state: ${state}`);
|
||||
}
|
||||
|
||||
await browser.browserAction.openPopup({ windowId });
|
||||
});
|
||||
};
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
...extensionData,
|
||||
|
||||
background: `(${background})(${JSON.stringify(state)})`,
|
||||
|
||||
files: {
|
||||
"tab.html": "<!DOCTYPE html>",
|
||||
"popup.html": `<!DOCTYPE html><meta charset="utf-8"><script src="popup.js"><\/script>`,
|
||||
"popup.js"() {
|
||||
// Small timeout to ensure the popup doesn't immediately close, which can
|
||||
// happen when focus moves between windows
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
setTimeout(async () => {
|
||||
let windows = await browser.windows.getAll();
|
||||
let highestWindowIdIsFocused = Math.max(...windows.map((w) => w.id))
|
||||
=== windows.find((w) => w.focused).id;
|
||||
|
||||
browser.test.assertEq(true, highestWindowIdIsFocused, "new window is focused");
|
||||
|
||||
browser.windows.remove(windows[windows.length - 1].id);
|
||||
browser.test.sendMessage("window");
|
||||
}, 1000);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("window");
|
||||
await extension.unload();
|
||||
}
|
||||
|
||||
add_task(async function test_browserAction_openPopup_window_inactive() {
|
||||
if (AppConstants.platform == "linux") {
|
||||
// TODO bug 1798334: Currently unreliable on linux
|
||||
todo(false, "Unreliable on linux");
|
||||
return;
|
||||
}
|
||||
await testWithWindowState("inactive");
|
||||
});
|
||||
|
||||
add_task(async function test_browserAction_openPopup_window_minimized() {
|
||||
if (AppConstants.platform == "linux") {
|
||||
// TODO bug 1798334: Currently unreliable on linux
|
||||
todo(false, "Unreliable on linux");
|
||||
return;
|
||||
}
|
||||
await testWithWindowState("minimized");
|
||||
});
|
||||
|
||||
add_task(async function test_browserAction_openPopup_invalid_window() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
...extensionData,
|
||||
|
||||
background: async function() {
|
||||
await browser.test.assertRejects(
|
||||
browser.browserAction.openPopup({ windowId: Number.MAX_SAFE_INTEGER }),
|
||||
/Invalid window ID/,
|
||||
"Should throw for invalid window ID"
|
||||
);
|
||||
browser.test.notifyPass("invalidWindow");
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("invalidWindow");
|
||||
await extension.unload();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>action.openPopup Preference Test</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script type="text/javascript" src="head.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: {
|
||||
id: "open-popup@tests.mozilla.org",
|
||||
}
|
||||
},
|
||||
browser_action: {
|
||||
default_popup: "popup.html",
|
||||
}
|
||||
},
|
||||
|
||||
useAddonManager: "android-only",
|
||||
};
|
||||
|
||||
add_task(async function test_browserAction_openPopup_without_pref() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["extensions.openPopupWithoutUserGesture.enabled", false],
|
||||
],
|
||||
});
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
...extensionData,
|
||||
|
||||
background: async function() {
|
||||
await browser.test.assertRejects(
|
||||
browser.browserAction.openPopup(),
|
||||
"openPopup requires a user gesture",
|
||||
"Should throw when preference is unset"
|
||||
);
|
||||
|
||||
browser.test.notifyPass("withoutPref");
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("withoutPref");
|
||||
await extension.unload();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -40,9 +40,7 @@ async function test_query(query) {
|
|||
browser.runtime.sendMessage({ tab, query });
|
||||
}
|
||||
});
|
||||
browser.test.withHandlingUserInput(() =>
|
||||
browser.browserAction.openPopup()
|
||||
);
|
||||
browser.browserAction.openPopup();
|
||||
},
|
||||
|
||||
files: {
|
||||
|
|
Загрузка…
Ссылка в новой задаче