Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
Services.scriptloader.loadSubScript(
this
);
/* globals withSidebarTree, synthesizeClickOnSelectedTreeCell, promiseLibrary, promiseLibraryClosed
*/
const PAGE =
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:
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:
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 () {
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 () {
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 () {
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);
});