mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-06 11:18:19 +02:00
605 lines
20 KiB
JavaScript
605 lines
20 KiB
JavaScript
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set sts=2 sw=2 et tw=80: */
|
|
"use strict";
|
|
|
|
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.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].childNodes[0].childNodes.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 ContentTask.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 ContentTask.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 ContentTask.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);
|
|
});
|
|
|
|
add_task(async function test_bookmark_contextmenu() {
|
|
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
|
|
|
|
const bookmarksToolbar = document.getElementById("PersonalToolbar");
|
|
setToolbarVisibility(bookmarksToolbar, true);
|
|
|
|
const extension = 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");
|
|
});
|
|
},
|
|
});
|
|
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();
|
|
setToolbarVisibility(bookmarksToolbar, false);
|
|
|
|
BrowserTestUtils.removeTab(tab);
|
|
});
|
|
|
|
add_task(async function test_bookmark_context_requires_permission() {
|
|
const bookmarksToolbar = document.getElementById("PersonalToolbar");
|
|
setToolbarVisibility(bookmarksToolbar, 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();
|
|
setToolbarVisibility(bookmarksToolbar, false);
|
|
});
|