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_contextMenus.js | 854 +++++++++++++++++++++ 1 file changed, 854 insertions(+) create mode 100644 browser/components/extensions/test/browser/browser_ext_contextMenus.js (limited to 'browser/components/extensions/test/browser/browser_ext_contextMenus.js') diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus.js b/browser/components/extensions/test/browser/browser_ext_contextMenus.js new file mode 100644 index 0000000000..c816c89f82 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js @@ -0,0 +1,854 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/places/tests/browser/head.js", + this +); +/* globals withSidebarTree, synthesizeClickOnSelectedTreeCell, promiseLibrary, promiseLibraryClosed + */ + +const PAGE = + "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html"; + +add_task(async function () { + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + + gBrowser.selectedTab = tab1; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["contextMenus"], + }, + + background: function () { + browser.test.assertEq( + browser.contextMenus.ContextType.TAB, + "tab", + "ContextType is available" + ); + browser.contextMenus.create({ + id: "clickme-image", + title: "Click me!", + contexts: ["image"], + }); + browser.contextMenus.create( + { + id: "clickme-page", + title: "Click me!", + contexts: ["page"], + }, + () => { + browser.test.sendMessage("ready"); + } + ); + }, + }); + + await extension.startup(); + await extension.awaitMessage("ready"); + + let contentAreaContextMenu = await openContextMenu("#img1"); + let item = contentAreaContextMenu.getElementsByAttribute( + "label", + "Click me!" + ); + is(item.length, 1, "contextMenu item for image was found"); + await closeContextMenu(); + + contentAreaContextMenu = await openContextMenu("body"); + item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!"); + is(item.length, 1, "contextMenu item for page was found"); + await closeContextMenu(); + + await extension.unload(); + + BrowserTestUtils.removeTab(tab1); +}); + +add_task(async function () { + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + + gBrowser.selectedTab = tab1; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["contextMenus"], + }, + + background: async function () { + browser.test.onMessage.addListener(msg => { + if (msg == "removeall") { + browser.contextMenus.removeAll(); + browser.test.sendMessage("removed"); + } + }); + + // A generic onclick callback function. + function genericOnClick(info, tab) { + browser.test.sendMessage("onclick", { info, tab }); + } + + browser.contextMenus.onClicked.addListener((info, tab) => { + browser.test.sendMessage("browser.contextMenus.onClicked", { + info, + tab, + }); + }); + + browser.contextMenus.create({ + contexts: ["all"], + type: "separator", + }); + + let contexts = [ + "page", + "link", + "selection", + "image", + "editable", + "password", + ]; + for (let i = 0; i < contexts.length; i++) { + let context = contexts[i]; + let title = context; + browser.contextMenus.create({ + title: title, + contexts: [context], + id: "ext-" + context, + onclick: genericOnClick, + }); + if (context == "selection") { + browser.contextMenus.update("ext-selection", { + title: "selection is: '%s'", + onclick: genericOnClick, + }); + } + } + + let parent = browser.contextMenus.create({ + title: "parent", + }); + browser.contextMenus.create({ + title: "child1", + parentId: parent, + onclick: genericOnClick, + }); + let child2 = browser.contextMenus.create({ + title: "child2", + parentId: parent, + onclick: genericOnClick, + }); + + let parentToDel = browser.contextMenus.create({ + title: "parentToDel", + }); + browser.contextMenus.create({ + title: "child1", + parentId: parentToDel, + onclick: genericOnClick, + }); + browser.contextMenus.create({ + title: "child2", + parentId: parentToDel, + onclick: genericOnClick, + }); + browser.contextMenus.remove(parentToDel); + + browser.contextMenus.create({ + title: "Without onclick property", + id: "ext-without-onclick", + }); + + await browser.test.assertRejects( + browser.contextMenus.update(parent, { parentId: child2 }), + /cannot be an ancestor/, + "Should not be able to reparent an item as descendent of itself" + ); + + browser.test.sendMessage("contextmenus"); + }, + }); + + await extension.startup(); + await extension.awaitMessage("contextmenus"); + + let expectedClickInfo = { + menuItemId: "ext-image", + mediaType: "image", + srcUrl: + "http://mochi.test:8888/browser/browser/components/extensions/test/browser/ctxmenu-image.png", + pageUrl: PAGE, + editable: false, + }; + + function checkClickInfo(result) { + for (let i of Object.keys(expectedClickInfo)) { + is( + result.info[i], + expectedClickInfo[i], + "click info " + + i + + " expected to be: " + + expectedClickInfo[i] + + " but was: " + + result.info[i] + ); + } + is( + expectedClickInfo.pageSrc, + result.tab.url, + "click info page source is the right tab" + ); + } + + let extensionMenuRoot = await openExtensionContextMenu(); + + // Check some menu items + let items = extensionMenuRoot.getElementsByAttribute("label", "image"); + is(items.length, 1, "contextMenu item for image was found (context=image)"); + let image = items[0]; + + items = extensionMenuRoot.getElementsByAttribute("label", "selection-edited"); + is( + items.length, + 0, + "contextMenu item for selection was not found (context=image)" + ); + + items = extensionMenuRoot.getElementsByAttribute("label", "parentToDel"); + is( + items.length, + 0, + "contextMenu item for removed parent was not found (context=image)" + ); + + items = extensionMenuRoot.getElementsByAttribute("label", "parent"); + is(items.length, 1, "contextMenu item for parent was found (context=image)"); + + is( + items[0].menupopup.children.length, + 2, + "child items for parent were found (context=image)" + ); + + // Click on ext-image item and check the click results + await closeExtensionContextMenu(image); + + let result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + + // Test "link" context and OnClick data property. + extensionMenuRoot = await openExtensionContextMenu("[href=some-link]"); + + // Click on ext-link and check the click results + items = extensionMenuRoot.getElementsByAttribute("label", "link"); + is(items.length, 1, "contextMenu item for parent was found (context=link)"); + let link = items[0]; + + expectedClickInfo = { + menuItemId: "ext-link", + linkUrl: + "http://mochi.test:8888/browser/browser/components/extensions/test/browser/some-link", + linkText: "Some link", + pageUrl: PAGE, + editable: false, + }; + + await closeExtensionContextMenu(link); + + result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + + // Test "editable" context and OnClick data property. + extensionMenuRoot = await openExtensionContextMenu("#edit-me"); + + // Check some menu items. + items = extensionMenuRoot.getElementsByAttribute("label", "editable"); + is( + items.length, + 1, + "contextMenu item for text input element was found (context=editable)" + ); + let editable = items[0]; + + // Click on ext-editable item and check the click results. + await closeExtensionContextMenu(editable); + + expectedClickInfo = { + menuItemId: "ext-editable", + pageUrl: PAGE, + editable: true, + }; + + result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + + extensionMenuRoot = await openExtensionContextMenu("#readonly-text"); + + // Check some menu items. + items = extensionMenuRoot.getElementsByAttribute("label", "editable"); + is( + items.length, + 0, + "contextMenu item for text input element was not found (context=editable fails for readonly items)" + ); + + // Hide the popup "manually" because there's nothing to click. + await closeContextMenu(); + + // Test "editable" context on type=tel and type=number items, and OnClick data property. + extensionMenuRoot = await openExtensionContextMenu("#call-me-maybe"); + + // Check some menu items. + items = extensionMenuRoot.getElementsByAttribute("label", "editable"); + is( + items.length, + 1, + "contextMenu item for text input element was found (context=editable)" + ); + editable = items[0]; + + // Click on ext-editable item and check the click results. + await closeExtensionContextMenu(editable); + + expectedClickInfo = { + menuItemId: "ext-editable", + pageUrl: PAGE, + editable: true, + }; + + result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + + extensionMenuRoot = await openExtensionContextMenu("#number-input"); + + // Check some menu items. + items = extensionMenuRoot.getElementsByAttribute("label", "editable"); + is( + items.length, + 1, + "contextMenu item for text input element was found (context=editable)" + ); + editable = items[0]; + + // Click on ext-editable item and check the click results. + await closeExtensionContextMenu(editable); + + expectedClickInfo = { + menuItemId: "ext-editable", + pageUrl: PAGE, + editable: true, + }; + + result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + + extensionMenuRoot = await openExtensionContextMenu("#password"); + items = extensionMenuRoot.getElementsByAttribute("label", "password"); + is( + items.length, + 1, + "contextMenu item for password input element was found (context=password)" + ); + let password = items[0]; + await closeExtensionContextMenu(password); + expectedClickInfo = { + menuItemId: "ext-password", + pageUrl: PAGE, + editable: true, + }; + + result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + + extensionMenuRoot = await openExtensionContextMenu("#noneditablepassword"); + items = extensionMenuRoot.getElementsByAttribute("label", "password"); + is( + items.length, + 1, + "contextMenu item for password input element was found (context=password)" + ); + password = items[0]; + await closeExtensionContextMenu(password); + expectedClickInfo.editable = false; + result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + + // Select some text + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function (arg) { + let doc = content.document; + let range = doc.createRange(); + let selection = content.getSelection(); + selection.removeAllRanges(); + let textNode = doc.getElementById("img1").previousSibling; + range.setStart(textNode, 0); + range.setEnd(textNode, 100); + selection.addRange(range); + }); + + // Bring up context menu again + extensionMenuRoot = await openExtensionContextMenu(); + + // Check some menu items + items = extensionMenuRoot.getElementsByAttribute( + "label", + "Without onclick property" + ); + is(items.length, 1, "contextMenu item was found (context=page)"); + + await closeExtensionContextMenu(items[0]); + + expectedClickInfo = { + menuItemId: "ext-without-onclick", + pageUrl: PAGE, + }; + + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + + // Bring up context menu again + extensionMenuRoot = await openExtensionContextMenu(); + + // Check some menu items + items = extensionMenuRoot.getElementsByAttribute( + "label", + "selection is: 'just some text 12345678901234567890123456789012\u2026'" + ); + is( + items.length, + 1, + "contextMenu item for selection was found (context=selection)" + ); + let selectionItem = items[0]; + + items = extensionMenuRoot.getElementsByAttribute("label", "selection"); + is( + items.length, + 0, + "contextMenu item label update worked (context=selection)" + ); + + await closeExtensionContextMenu(selectionItem); + + expectedClickInfo = { + menuItemId: "ext-selection", + pageUrl: PAGE, + selectionText: + " just some text 1234567890123456789012345678901234567890123456789012345678901234567890123456789012", + }; + + result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + + // Select a lot of text + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function (arg) { + let doc = content.document; + let range = doc.createRange(); + let selection = content.getSelection(); + selection.removeAllRanges(); + let textNode = doc.getElementById("longtext").firstChild; + range.setStart(textNode, 0); + range.setEnd(textNode, textNode.length); + selection.addRange(range); + }); + + // Bring up context menu again + extensionMenuRoot = await openExtensionContextMenu("#longtext"); + + // Check some menu items + items = extensionMenuRoot.getElementsByAttribute( + "label", + "selection is: 'Sed ut perspiciatis unde omnis iste natus error\u2026'" + ); + is( + items.length, + 1, + `contextMenu item for longtext selection was found (context=selection)` + ); + await closeExtensionContextMenu(items[0]); + + expectedClickInfo = { + menuItemId: "ext-selection", + pageUrl: PAGE, + }; + + result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + ok( + result.info.selectionText.endsWith("quo voluptas nulla pariatur?"), + "long text selection worked" + ); + + // Select a lot of text, excercise the editable element code path in + // the Browser:GetSelection handler. + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function (arg) { + let doc = content.document; + let node = doc.getElementById("editabletext"); + // content.js handleContentContextMenu fails intermittently without focus. + node.focus(); + node.selectionStart = 0; + node.selectionEnd = 844; + }); + + // Bring up context menu again + extensionMenuRoot = await openExtensionContextMenu("#editabletext"); + + // Check some menu items + items = extensionMenuRoot.getElementsByAttribute("label", "editable"); + is( + items.length, + 1, + "contextMenu item for text input element was found (context=editable)" + ); + await closeExtensionContextMenu(items[0]); + + expectedClickInfo = { + menuItemId: "ext-editable", + editable: true, + pageUrl: PAGE, + }; + + result = await extension.awaitMessage("onclick"); + checkClickInfo(result); + result = await extension.awaitMessage("browser.contextMenus.onClicked"); + checkClickInfo(result); + ok( + result.info.selectionText.endsWith( + "perferendis doloribus asperiores repellat." + ), + "long text selection worked" + ); + + extension.sendMessage("removeall"); + await extension.awaitMessage("removed"); + + let contentAreaContextMenu = await openContextMenu("#img1"); + items = contentAreaContextMenu.getElementsByAttribute( + "ext-type", + "top-level-menu" + ); + is(items.length, 0, "top level item was not found (after removeAll()"); + await closeContextMenu(); + + await extension.unload(); + BrowserTestUtils.removeTab(tab1); +}); + +add_task(async function testRemoveAllWithTwoExtensions() { + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + const manifest = { permissions: ["contextMenus"] }; + + const first = ExtensionTestUtils.loadExtension({ + manifest, + background() { + browser.contextMenus.create({ title: "alpha", contexts: ["all"] }); + + browser.contextMenus.onClicked.addListener(() => { + browser.contextMenus.removeAll(); + }); + browser.test.onMessage.addListener(msg => { + if (msg == "ping") { + browser.test.sendMessage("pong-alpha"); + return; + } + browser.contextMenus.create({ title: "gamma", contexts: ["all"] }); + }); + }, + }); + + const second = ExtensionTestUtils.loadExtension({ + manifest, + background() { + browser.contextMenus.create({ title: "beta", contexts: ["all"] }); + + browser.contextMenus.onClicked.addListener(() => { + browser.contextMenus.removeAll(); + }); + + browser.test.onMessage.addListener(() => { + browser.test.sendMessage("pong-beta"); + }); + }, + }); + + await first.startup(); + await second.startup(); + + async function confirmMenuItems(...items) { + // Round-trip to extension to make sure that the context menu state has been + // updated by the async contextMenus.create / contextMenus.removeAll calls. + first.sendMessage("ping"); + second.sendMessage("ping"); + await first.awaitMessage("pong-alpha"); + await second.awaitMessage("pong-beta"); + + const menu = await openContextMenu(); + for (const id of ["alpha", "beta", "gamma"]) { + const expected = items.includes(id); + const found = menu.getElementsByAttribute("label", id); + is( + !!found.length, + expected, + `menu item ${id} ${expected ? "" : "not "}found` + ); + } + // Return the first menu item, we need to click it. + return menu.getElementsByAttribute("label", items[0])[0]; + } + + // Confirm alpha, beta exist; click alpha to remove it. + const alpha = await confirmMenuItems("alpha", "beta"); + await closeExtensionContextMenu(alpha); + + // Confirm only beta exists. + await confirmMenuItems("beta"); + await closeContextMenu(); + + // Create gamma, confirm, click. + first.sendMessage("create"); + const beta = await confirmMenuItems("beta", "gamma"); + await closeExtensionContextMenu(beta); + + // Confirm only gamma is left. + await confirmMenuItems("gamma"); + await closeContextMenu(); + + await first.unload(); + await second.unload(); + BrowserTestUtils.removeTab(tab); +}); + +function bookmarkContextMenuExtension() { + return ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["contextMenus", "bookmarks", "activeTab"], + }, + async background() { + const url = "https://example.com/"; + const title = "Example"; + let newBookmark = await browser.bookmarks.create({ + url, + title, + parentId: "toolbar_____", + }); + browser.contextMenus.onClicked.addListener(async (info, tab) => { + browser.test.assertEq( + undefined, + tab, + "click event in bookmarks menu is not associated with any tab" + ); + browser.test.assertEq( + newBookmark.id, + info.bookmarkId, + "Bookmark ID matches" + ); + + await browser.test.assertRejects( + browser.tabs.executeScript({ code: "'some code';" }), + /Missing host permission for the tab/, + "Content script should not run, activeTab should not be granted to bookmark menu events" + ); + + let [bookmark] = await browser.bookmarks.get(info.bookmarkId); + browser.test.assertEq(title, bookmark.title, "Bookmark title matches"); + browser.test.assertEq(url, bookmark.url, "Bookmark url matches"); + browser.test.assertFalse( + info.hasOwnProperty("pageUrl"), + "Context menu does not expose pageUrl" + ); + await browser.bookmarks.remove(info.bookmarkId); + browser.test.sendMessage("test-finish"); + }); + browser.contextMenus.create( + { + title: "Get bookmark", + contexts: ["bookmark"], + }, + () => { + browser.test.sendMessage("bookmark-created", newBookmark.id); + } + ); + }, + }); +} + +add_task(async function test_bookmark_contextmenu() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + + await toggleBookmarksToolbar(true); + + const extension = bookmarkContextMenuExtension(); + + await extension.startup(); + await extension.awaitMessage("bookmark-created"); + let menu = await openChromeContextMenu( + "placesContext", + "#PersonalToolbar .bookmark-item:last-child" + ); + + let menuItem = menu.getElementsByAttribute("label", "Get bookmark")[0]; + closeChromeContextMenu("placesContext", menuItem); + + await extension.awaitMessage("test-finish"); + await extension.unload(); + await toggleBookmarksToolbar(false); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_bookmark_sidebar_contextmenu() { + await withSidebarTree("bookmarks", async tree => { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + + let extension = bookmarkContextMenuExtension(); + await extension.startup(); + let bookmarkGuid = await extension.awaitMessage("bookmark-created"); + + let sidebar = window.SidebarUI.browser; + let menu = sidebar.contentDocument.getElementById("placesContext"); + tree.selectItems([bookmarkGuid]); + let shown = BrowserTestUtils.waitForEvent(menu, "popupshown"); + synthesizeClickOnSelectedTreeCell(tree, { type: "contextmenu" }); + await shown; + + let menuItem = menu.getElementsByAttribute("label", "Get bookmark")[0]; + closeChromeContextMenu("placesContext", menuItem, sidebar.contentWindow); + await extension.awaitMessage("test-finish"); + await extension.unload(); + + BrowserTestUtils.removeTab(tab); + }); +}); + +function bookmarkFolderContextMenuExtension() { + return ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["contextMenus", "bookmarks"], + }, + async background() { + const title = "Example"; + let newBookmark = await browser.bookmarks.create({ + title, + parentId: "toolbar_____", + }); + await new Promise(resolve => + browser.contextMenus.create( + { + title: "Get bookmark", + contexts: ["bookmark"], + }, + resolve + ) + ); + browser.contextMenus.onClicked.addListener(async info => { + browser.test.assertEq( + newBookmark.id, + info.bookmarkId, + "Bookmark ID matches" + ); + + let [bookmark] = await browser.bookmarks.get(info.bookmarkId); + browser.test.assertEq(title, bookmark.title, "Bookmark title matches"); + browser.test.assertFalse( + info.hasOwnProperty("pageUrl"), + "Context menu does not expose pageUrl" + ); + await browser.bookmarks.remove(info.bookmarkId); + browser.test.sendMessage("test-finish"); + }); + browser.test.sendMessage("bookmark-created", newBookmark.id); + }, + }); +} + +add_task(async function test_organizer_contextmenu() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); + let library = await promiseLibrary("BookmarksToolbar"); + + let menu = library.document.getElementById("placesContext"); + let mainTree = library.document.getElementById("placeContent"); + let leftTree = library.document.getElementById("placesList"); + + let tests = [ + [mainTree, bookmarkContextMenuExtension], + [mainTree, bookmarkFolderContextMenuExtension], + [leftTree, bookmarkFolderContextMenuExtension], + ]; + + for (let [tree, makeExtension] of tests) { + let extension = makeExtension(); + await extension.startup(); + let bookmarkGuid = await extension.awaitMessage("bookmark-created"); + + tree.selectItems([bookmarkGuid]); + let shown = BrowserTestUtils.waitForEvent(menu, "popupshown"); + synthesizeClickOnSelectedTreeCell(tree, { type: "contextmenu" }); + await shown; + + let menuItem = menu.getElementsByAttribute("label", "Get bookmark")[0]; + closeChromeContextMenu("placesContext", menuItem, library); + await extension.awaitMessage("test-finish"); + await extension.unload(); + } + + await promiseLibraryClosed(library); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_bookmark_context_requires_permission() { + await toggleBookmarksToolbar(true); + + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["contextMenus"], + }, + background() { + browser.contextMenus.create( + { + title: "Get bookmark", + contexts: ["bookmark"], + }, + () => { + browser.test.sendMessage("bookmark-created"); + } + ); + }, + }); + await extension.startup(); + await extension.awaitMessage("bookmark-created"); + let menu = await openChromeContextMenu( + "placesContext", + "#PersonalToolbar .bookmark-item:last-child" + ); + + Assert.equal( + menu.getElementsByAttribute("label", "Get bookmark").length, + 0, + "bookmark context menu not created with `bookmarks` permission." + ); + + closeChromeContextMenu("placesContext"); + + await extension.unload(); + await toggleBookmarksToolbar(false); +}); -- cgit v1.2.3