Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* 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 =
// 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 => {
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 => {
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");
if (targetElementId === targetElementId2) {
// targetElementId is only guaranteed to be unique within a tab, so odds are
// that by unlucky coincidence, that the discovered ID accidentally overlaps
// with the actual ID.
info(`Got same targetElementId ${targetElementId}, retrying for a new one`);
targetElementId2 = await extension.openAndCloseMenu("#editabletext");
}
Assert.notEqual(targetElementId, targetElementId2, "targetElementId differ");
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);
});