From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../test/browser/browser_ext_menus_refresh.js | 438 +++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 browser/components/extensions/test/browser/browser_ext_menus_refresh.js (limited to 'browser/components/extensions/test/browser/browser_ext_menus_refresh.js') diff --git a/browser/components/extensions/test/browser/browser_ext_menus_refresh.js b/browser/components/extensions/test/browser/browser_ext_menus_refresh.js new file mode 100644 index 0000000000..61e33483f1 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_menus_refresh.js @@ -0,0 +1,438 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const PAGE = + "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html"; + +// Load an extension that has the "menus" permission. The returned Extension +// instance has a `callMenuApi` method to easily call a browser.menus method +// and wait for its result. It also emits the "onShown fired" message whenever +// the menus.onShown event is fired. +// The `getXULElementByMenuId` method returns the XUL element that corresponds +// to the menu item ID from the browser.menus API (if existent, null otherwise). +function loadExtensionWithMenusApi() { + async function background() { + function shownHandler() { + browser.test.sendMessage("onShown fired"); + } + + browser.menus.onShown.addListener(shownHandler); + browser.test.onMessage.addListener((method, ...params) => { + let result; + if (method === "* remove onShown listener") { + browser.menus.onShown.removeListener(shownHandler); + result = Promise.resolve(); + } else if (method === "create") { + result = new Promise(resolve => { + browser.menus.create(params[0], resolve); + }); + } else { + result = browser.menus[method](...params); + } + result.then(() => { + browser.test.sendMessage(`${method}-result`); + }); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + browser_action: { + default_area: "navbar", + }, + permissions: ["menus"], + }, + }); + + extension.callMenuApi = async function (method, ...params) { + info(`Calling ${method}(${JSON.stringify(params)})`); + extension.sendMessage(method, ...params); + return extension.awaitMessage(`${method}-result`); + }; + + extension.removeOnShownListener = async function () { + extension.callMenuApi("* remove onShown listener"); + }; + + extension.getXULElementByMenuId = id => { + // Same implementation as elementId getter in ext-menus.js + if (typeof id != "number") { + id = `_${id}`; + } + let xulId = `${makeWidgetId(extension.id)}-menuitem-${id}`; + return document.getElementById(xulId); + }; + + return extension; +} + +// Tests whether browser.menus.refresh works as expected with respect to the +// menu items that are added/updated/removed before/during/after opening a menu: +// - browser.refresh before a menu is shown should not have any effect. +// - browser.refresh while a menu is shown should update the menu. +// - browser.refresh after a menu is hidden should not have any effect. +async function testRefreshMenusWhileVisible({ + contexts, + doOpenMenu, + doCloseMenu, +}) { + let extension = loadExtensionWithMenusApi(); + await extension.startup(); + await extension.callMenuApi("create", { + id: "abc", + title: "first", + contexts, + }); + let elem = extension.getXULElementByMenuId("abc"); + is(elem, null, "Menu item should not be visible"); + + // Refresh before a menu is shown - should be noop. + await extension.callMenuApi("refresh"); + elem = extension.getXULElementByMenuId("abc"); + is(elem, null, "Menu item should still not be visible"); + + // Open menu and expect menu to be rendered. + await doOpenMenu(extension); + elem = extension.getXULElementByMenuId("abc"); + is(elem.getAttribute("label"), "first", "expected label"); + + await extension.awaitMessage("onShown fired"); + + // Add new menus, but don't expect them to be rendered yet. + await extension.callMenuApi("update", "abc", { title: "updated first" }); + await extension.callMenuApi("create", { + id: "def", + title: "second", + contexts, + }); + + elem = extension.getXULElementByMenuId("abc"); + is(elem.getAttribute("label"), "first", "expected unchanged label"); + elem = extension.getXULElementByMenuId("def"); + is(elem, null, "Second menu item should not be visible"); + + // Refresh while a menu is shown - should be updated. + await extension.callMenuApi("refresh"); + + elem = extension.getXULElementByMenuId("abc"); + is(elem.getAttribute("label"), "updated first", "expected updated label"); + elem = extension.getXULElementByMenuId("def"); + is(elem.getAttribute("label"), "second", "expected second label"); + + // Update the two menu items again. + await extension.callMenuApi("update", "abc", { enabled: false }); + await extension.callMenuApi("update", "def", { enabled: false }); + await extension.callMenuApi("refresh"); + elem = extension.getXULElementByMenuId("abc"); + is(elem.getAttribute("disabled"), "true", "1st menu item should be disabled"); + elem = extension.getXULElementByMenuId("def"); + is(elem.getAttribute("disabled"), "true", "2nd menu item should be disabled"); + + // Remove one. + await extension.callMenuApi("remove", "abc"); + await extension.callMenuApi("refresh"); + elem = extension.getXULElementByMenuId("def"); + is(elem.getAttribute("label"), "second", "other menu item should exist"); + elem = extension.getXULElementByMenuId("abc"); + is(elem, null, "removed menu item should be gone"); + + // Remove the last one. + await extension.callMenuApi("removeAll"); + await extension.callMenuApi("refresh"); + elem = extension.getXULElementByMenuId("def"); + is(elem, null, "all menu items should be gone"); + + // At this point all menu items have been removed. Create a new menu item so + // we can confirm that browser.menus.refresh() does not render the menu item + // after the menu has been hidden. + await extension.callMenuApi("create", { + // The menu item with ID "abc" was removed before, so re-using the ID should + // not cause any issues: + id: "abc", + title: "re-used", + contexts, + }); + await extension.callMenuApi("refresh"); + elem = extension.getXULElementByMenuId("abc"); + is(elem.getAttribute("label"), "re-used", "menu item should be created"); + + await doCloseMenu(); + + elem = extension.getXULElementByMenuId("abc"); + is(elem, null, "menu item must be gone"); + + // Refresh after menu was hidden - should be noop. + await extension.callMenuApi("refresh"); + elem = extension.getXULElementByMenuId("abc"); + is(elem, null, "menu item must still be gone"); + + await extension.unload(); +} + +// Check that one extension calling refresh() doesn't interfere with others. +// When expectOtherItems == false, the other extension's menu items should not +// show at all (e.g. for browserAction). +async function testRefreshOther({ + contexts, + doOpenMenu, + doCloseMenu, + expectOtherItems, +}) { + let extension = loadExtensionWithMenusApi(); + let other_extension = loadExtensionWithMenusApi(); + await extension.startup(); + await other_extension.startup(); + + await extension.callMenuApi("create", { + id: "action_item", + title: "visible menu item", + contexts: contexts, + }); + + await other_extension.callMenuApi("create", { + id: "action_item", + title: "other menu item", + contexts: contexts, + }); + + await doOpenMenu(extension); + await extension.awaitMessage("onShown fired"); + if (expectOtherItems) { + await other_extension.awaitMessage("onShown fired"); + } + + let elem = extension.getXULElementByMenuId("action_item"); + is(elem.getAttribute("label"), "visible menu item", "extension menu shown"); + elem = other_extension.getXULElementByMenuId("action_item"); + if (expectOtherItems) { + is( + elem.getAttribute("label"), + "other menu item", + "other extension's menu is also shown" + ); + } else { + is(elem, null, "other extension's menu should be hidden"); + } + + await extension.callMenuApi("update", "action_item", { title: "changed" }); + await other_extension.callMenuApi("update", "action_item", { title: "foo" }); + await other_extension.callMenuApi("refresh"); + + // refreshing the menu of an unrelated extension should not affect the menu + // of another extension. + elem = extension.getXULElementByMenuId("action_item"); + is(elem.getAttribute("label"), "visible menu item", "extension menu shown"); + elem = other_extension.getXULElementByMenuId("action_item"); + if (expectOtherItems) { + is(elem.getAttribute("label"), "foo", "other extension's item is updated"); + } else { + is(elem, null, "other extension's menu should still be hidden"); + } + + await doCloseMenu(); + await extension.unload(); + await other_extension.unload(); +} + +add_task(async function refresh_menus_with_browser_action() { + const args = { + contexts: ["browser_action"], + async doOpenMenu(extension) { + await openActionContextMenu(extension, "browser"); + }, + async doCloseMenu() { + await closeActionContextMenu(); + }, + }; + await testRefreshMenusWhileVisible(args); + args.expectOtherItems = false; + await testRefreshOther(args); +}); + +add_task(async function refresh_menus_with_tab() { + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + const args = { + contexts: ["tab"], + async doOpenMenu() { + await openTabContextMenu(); + }, + async doCloseMenu() { + await closeTabContextMenu(); + }, + }; + await testRefreshMenusWhileVisible(args); + args.expectOtherItems = true; + await testRefreshOther(args); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function refresh_menus_with_tools_menu() { + const args = { + contexts: ["tools_menu"], + async doOpenMenu() { + await openToolsMenu(); + }, + async doCloseMenu() { + await closeToolsMenu(); + }, + }; + await testRefreshMenusWhileVisible(args); + args.expectOtherItems = true; + await testRefreshOther(args); +}); + +add_task(async function refresh_menus_with_page() { + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + const args = { + contexts: ["page"], + async doOpenMenu() { + await openContextMenu("body"); + }, + async doCloseMenu() { + await closeExtensionContextMenu(); + }, + }; + await testRefreshMenusWhileVisible(args); + args.expectOtherItems = true; + await testRefreshOther(args); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function refresh_without_menus_at_onShown() { + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + let extension = loadExtensionWithMenusApi(); + await extension.startup(); + + const doOpenMenu = () => openContextMenu("body"); + const doCloseMenu = () => closeExtensionContextMenu(); + + await doOpenMenu(); + await extension.awaitMessage("onShown fired"); + await extension.callMenuApi("create", { + id: "too late", + title: "created after shown", + }); + await extension.callMenuApi("refresh"); + let elem = extension.getXULElementByMenuId("too late"); + is( + elem.getAttribute("label"), + "created after shown", + "extension without visible menu items can add new items" + ); + + await extension.callMenuApi("update", "too late", { title: "the menu item" }); + await extension.callMenuApi("refresh"); + elem = extension.getXULElementByMenuId("too late"); + is(elem.getAttribute("label"), "the menu item", "label should change"); + + // The previously created menu item should be visible if the menu is closed + // and re-opened. + await doCloseMenu(); + await doOpenMenu(); + await extension.awaitMessage("onShown fired"); + elem = extension.getXULElementByMenuId("too late"); + is(elem.getAttribute("label"), "the menu item", "previously registered item"); + await doCloseMenu(); + + await extension.unload(); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function refresh_without_onShown() { + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + let extension = loadExtensionWithMenusApi(); + await extension.startup(); + await extension.removeOnShownListener(); + + const doOpenMenu = () => openContextMenu("body"); + const doCloseMenu = () => closeExtensionContextMenu(); + + await doOpenMenu(); + await extension.callMenuApi("create", { + id: "too late", + title: "created after shown", + }); + + is( + extension.getXULElementByMenuId("too late"), + null, + "item created after shown is not visible before refresh" + ); + + await extension.callMenuApi("refresh"); + let elem = extension.getXULElementByMenuId("too late"); + is( + elem.getAttribute("label"), + "created after shown", + "refresh updates the menu even without onShown" + ); + + await doCloseMenu(); + await extension.unload(); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function refresh_menus_during_navigation() { + const tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + PAGE + "?1" + ); + let extension = loadExtensionWithMenusApi(); + await extension.startup(); + + await extension.callMenuApi("create", { + id: "item1", + title: "item1", + contexts: ["browser_action"], + documentUrlPatterns: ["*://*/*?1*"], + }); + + await extension.callMenuApi("create", { + id: "item2", + title: "item2", + contexts: ["browser_action"], + documentUrlPatterns: ["*://*/*?2*"], + }); + + await openActionContextMenu(extension, "browser"); + await extension.awaitMessage("onShown fired"); + + let elem = extension.getXULElementByMenuId("item1"); + is(elem.getAttribute("label"), "item1", "menu item 1 should be shown"); + elem = extension.getXULElementByMenuId("item2"); + is(elem, null, "menu item 2 should be hidden"); + + BrowserTestUtils.loadURIString(tab.linkedBrowser, PAGE + "?2"); + await BrowserTestUtils.browserStopped(tab.linkedBrowser); + + await extension.callMenuApi("refresh"); + + // The menu items in a context menu are based on the context at the time of + // opening the menu. Menus are not updated if the context changes, e.g. as a + // result of navigation events after the menu was shown. + // So when refresh() is called during the onShown event, then the original + // URL (before navigation) should be used to determine whether to show a + // URL-specific menu item, and NOT the current URL (after navigation). + elem = extension.getXULElementByMenuId("item1"); + is(elem.getAttribute("label"), "item1", "menu item 1 should still be shown"); + elem = extension.getXULElementByMenuId("item2"); + is(elem, null, "menu item 2 should still be hidden"); + + await closeActionContextMenu(); + await openActionContextMenu(extension, "browser"); + await extension.awaitMessage("onShown fired"); + + // Now after closing and re-opening the menu, the latest contextual info + // should be used. + elem = extension.getXULElementByMenuId("item1"); + is(elem, null, "menu item 1 should be hidden"); + elem = extension.getXULElementByMenuId("item2"); + is(elem.getAttribute("label"), "item2", "menu item 2 should be shown"); + + await closeActionContextMenu(); + await extension.unload(); + BrowserTestUtils.removeTab(tab); +}); -- cgit v1.2.3