summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/test/browser/browser_ext_menus_targetElement.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/extensions/test/browser/browser_ext_menus_targetElement.js')
-rw-r--r--browser/components/extensions/test/browser/browser_ext_menus_targetElement.js326
1 files changed, 326 insertions, 0 deletions
diff --git a/browser/components/extensions/test/browser/browser_ext_menus_targetElement.js b/browser/components/extensions/test/browser/browser_ext_menus_targetElement.js
new file mode 100644
index 0000000000..abfbf26a05
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_menus_targetElement.js
@@ -0,0 +1,326 @@
+/* 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";
+
+// Loads an extension that records menu visibility events in the current tab.
+// The returned extension has two helper functions "openContextMenu" and
+// "checkIsValid" that are used to verify the behavior of targetElementId.
+async function loadExtensionAndTab() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+ gBrowser.selectedTab = tab;
+
+ function contentScript() {
+ browser.test.onMessage.addListener(
+ (msg, targetElementId, expectedSelector, description) => {
+ browser.test.assertEq("checkIsValid", msg, "Expected message");
+
+ let expected = expectedSelector
+ ? document.querySelector(expectedSelector)
+ : null;
+ let elem = browser.menus.getTargetElement(targetElementId);
+ browser.test.assertEq(expected, elem, description);
+ browser.test.sendMessage("checkIsValidDone");
+ }
+ );
+ }
+
+ async function background() {
+ browser.menus.onShown.addListener(async (info, tab) => {
+ browser.test.sendMessage("onShownMenu", info.targetElementId);
+ });
+ await browser.tabs.executeScript({ file: "contentScript.js" });
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["menus", "http://mochi.test/*"],
+ },
+ background,
+ files: {
+ "contentScript.js": contentScript,
+ },
+ });
+
+ extension.openAndCloseMenu = async selector => {
+ await openContextMenu(selector);
+ let targetElementId = await extension.awaitMessage("onShownMenu");
+ await closeContextMenu();
+ return targetElementId;
+ };
+
+ extension.checkIsValid = async (
+ targetElementId,
+ expectedSelector,
+ description
+ ) => {
+ extension.sendMessage(
+ "checkIsValid",
+ targetElementId,
+ expectedSelector,
+ description
+ );
+ await extension.awaitMessage("checkIsValidDone");
+ };
+
+ await extension.startup();
+ await extension.awaitMessage("ready");
+ return { extension, tab };
+}
+
+// Tests that info.targetElementId is only available with the right permissions.
+add_task(async function required_permission() {
+ let { extension, tab } = await loadExtensionAndTab();
+
+ // Load another extension to verify that the permission from the first
+ // extension does not enable the "targetElementId" parameter.
+ function background() {
+ browser.contextMenus.onShown.addListener((info, tab) => {
+ browser.test.assertEq(
+ undefined,
+ info.targetElementId,
+ "targetElementId requires permission"
+ );
+ browser.test.sendMessage("onShown");
+ });
+ browser.contextMenus.onClicked.addListener(async info => {
+ browser.test.assertEq(
+ undefined,
+ info.targetElementId,
+ "targetElementId requires permission"
+ );
+ const code = `
+ browser.test.assertEq(undefined, browser.menus, "menus API requires permission in content script");
+ browser.test.assertEq(undefined, browser.contextMenus, "contextMenus API not available in content script.");
+ `;
+ await browser.tabs.executeScript({ code });
+ browser.test.sendMessage("onClicked");
+ });
+ browser.contextMenus.create({ title: "menu for page" }, () => {
+ browser.test.sendMessage("ready");
+ });
+ }
+ let extension2 = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["contextMenus", "http://mochi.test/*"],
+ },
+ background,
+ });
+ await extension2.startup();
+ await extension2.awaitMessage("ready");
+
+ let menu = await openContextMenu();
+ await extension.awaitMessage("onShownMenu");
+ let menuItem = menu.getElementsByAttribute("label", "menu for page")[0];
+ await closeExtensionContextMenu(menuItem);
+
+ await extension2.awaitMessage("onShown");
+ await extension2.awaitMessage("onClicked");
+ await extension2.unload();
+
+ await extension.unload();
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Tests that the basic functionality works as expected.
+add_task(async function getTargetElement_in_page() {
+ let { extension, tab } = await loadExtensionAndTab();
+
+ for (let selector of ["#img1", "#link1", "#password"]) {
+ let targetElementId = await extension.openAndCloseMenu(selector);
+ ok(
+ Number.isInteger(targetElementId),
+ `targetElementId (${targetElementId}) should be an integer for ${selector}`
+ );
+
+ await extension.checkIsValid(
+ targetElementId,
+ selector,
+ `Expected target to match ${selector}`
+ );
+ }
+
+ await extension.unload();
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function getTargetElement_in_frame() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+ gBrowser.selectedTab = tab;
+
+ async function background() {
+ let targetElementId;
+ browser.menus.onShown.addListener(async (info, tab) => {
+ browser.test.assertTrue(
+ info.frameUrl.endsWith("context_frame.html"),
+ `Expected frame ${info.frameUrl}`
+ );
+ targetElementId = info.targetElementId;
+ let elem = browser.menus.getTargetElement(targetElementId);
+ browser.test.assertEq(
+ null,
+ elem,
+ "should not find page element in extension's background"
+ );
+
+ await browser.tabs.executeScript(tab.id, {
+ code: `{
+ let elem = browser.menus.getTargetElement(${targetElementId});
+ browser.test.assertEq(null, elem, "should not find element from different frame");
+ }`,
+ });
+
+ await browser.tabs.executeScript(tab.id, {
+ frameId: info.frameId,
+ code: `{
+ let elem = browser.menus.getTargetElement(${targetElementId});
+ browser.test.assertEq(document.body, elem, "should find the target element in the frame");
+ }`,
+ });
+ browser.test.sendMessage("pageAndFrameChecked");
+ });
+
+ browser.menus.onClicked.addListener(info => {
+ browser.test.assertEq(
+ targetElementId,
+ info.targetElementId,
+ "targetElementId in onClicked must match onShown."
+ );
+ browser.test.sendMessage("onClickedChecked");
+ });
+
+ browser.menus.create(
+ { title: "menu for frame", contexts: ["frame"] },
+ () => {
+ browser.test.sendMessage("ready");
+ }
+ );
+ }
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["menus", "http://mochi.test/*"],
+ },
+ background,
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("ready");
+
+ let menu = await openContextMenuInFrame();
+ await extension.awaitMessage("pageAndFrameChecked");
+ let menuItem = menu.getElementsByAttribute("label", "menu for frame")[0];
+ await closeExtensionContextMenu(menuItem);
+ await extension.awaitMessage("onClickedChecked");
+
+ await extension.unload();
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Test that getTargetElement does not return a detached element.
+add_task(async function getTargetElement_after_removing_element() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+
+ function background() {
+ function contentScript(targetElementId) {
+ let expectedElem = document.getElementById("edit-me");
+ let { nextElementSibling } = expectedElem;
+
+ let elem = browser.menus.getTargetElement(targetElementId);
+ browser.test.assertEq(
+ expectedElem,
+ elem,
+ "Expected target element before element removal"
+ );
+
+ expectedElem.remove();
+ elem = browser.menus.getTargetElement(targetElementId);
+ browser.test.assertEq(
+ null,
+ elem,
+ "Expected no target element after element removal."
+ );
+
+ nextElementSibling.insertAdjacentElement("beforebegin", expectedElem);
+ elem = browser.menus.getTargetElement(targetElementId);
+ browser.test.assertEq(
+ expectedElem,
+ elem,
+ "Expected target element after element restoration."
+ );
+ }
+ browser.menus.onClicked.addListener(async (info, tab) => {
+ const code = `(${contentScript})(${info.targetElementId})`;
+ browser.test.log(code);
+ await browser.tabs.executeScript(tab.id, { code });
+ browser.test.sendMessage("checkedRemovedElement");
+ });
+ browser.menus.create({ title: "some menu item" }, () => {
+ browser.test.sendMessage("ready");
+ });
+ }
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["menus", "http://mochi.test/*"],
+ },
+ background,
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("ready");
+ let menu = await openContextMenu("#edit-me");
+ let menuItem = menu.getElementsByAttribute("label", "some menu item")[0];
+ await closeExtensionContextMenu(menuItem);
+ await extension.awaitMessage("checkedRemovedElement");
+ await extension.unload();
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Tests whether targetElementId expires after opening a new menu.
+add_task(async function expireTargetElement() {
+ let { extension, tab } = await loadExtensionAndTab();
+
+ // Open the menu once to get the first element ID.
+ let targetElementId = await extension.openAndCloseMenu("#longtext");
+
+ // Open another menu. The previous ID should expire.
+ await extension.openAndCloseMenu("#longtext");
+ await extension.checkIsValid(
+ targetElementId,
+ null,
+ `Expected initial target ID to expire after opening another menu`
+ );
+
+ await extension.unload();
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Tests whether targetElementId of different tabs are independent.
+add_task(async function independentMenusInDifferentTabs() {
+ let { extension, tab } = await loadExtensionAndTab();
+
+ let targetElementId = await extension.openAndCloseMenu("#longtext");
+
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE + "?");
+ gBrowser.selectedTab = tab2;
+
+ let targetElementId2 = await extension.openAndCloseMenu("#editabletext");
+
+ await extension.checkIsValid(
+ targetElementId2,
+ null,
+ "targetElementId from different tab should not resolve."
+ );
+ await extension.checkIsValid(
+ targetElementId,
+ "#longtext",
+ "Expected getTargetElement to work after closing a menu in another tab."
+ );
+
+ await extension.unload();
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(tab2);
+});