/* -*- 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"; // Loaded both as a background script and a tab page. function testScript() { let page = location.pathname.includes("tab.html") ? "tab" : "background"; let clickCounts = { old: 0, new: 0, }; browser.contextMenus.onClicked.addListener(() => { // Async to give other onclick handlers a chance to fire. setTimeout(() => { browser.test.sendMessage("onClicked-fired", page); }); }); browser.test.onMessage.addListener((toPage, msg) => { if (toPage !== page) { return; } browser.test.log(`Received ${msg} for ${toPage}`); if (msg == "get-click-counts") { browser.test.sendMessage("click-counts", clickCounts); } else if (msg == "clear-click-counts") { clickCounts.old = clickCounts.new = 0; browser.test.sendMessage("next"); } else if (msg == "create-with-onclick") { browser.contextMenus.create( { id: "iden", title: "tifier", onclick() { ++clickCounts.old; browser.test.log( `onclick fired for original onclick property in ${page}` ); }, }, () => browser.test.sendMessage("next") ); } else if (msg == "create-without-onclick") { browser.contextMenus.create( { id: "iden", title: "tifier", }, () => browser.test.sendMessage("next") ); } else if (msg == "update-without-onclick") { browser.contextMenus.update( "iden", { enabled: true, // Already enabled, so this does nothing. }, () => browser.test.sendMessage("next") ); } else if (msg == "update-with-onclick") { browser.contextMenus.update( "iden", { onclick() { ++clickCounts.new; browser.test.log( `onclick fired for updated onclick property in ${page}` ); }, }, () => browser.test.sendMessage("next") ); } else if (msg == "remove") { browser.contextMenus.remove("iden", () => browser.test.sendMessage("next") ); } else if (msg == "removeAll") { browser.contextMenus.removeAll(() => browser.test.sendMessage("next")); } }); if (page == "background") { browser.test.log("Opening tab.html"); browser.tabs.create({ url: "tab.html", active: false, // To not interfere with the context menu tests. }); } else { // Sanity check - the pages must be in the same process. let pages = browser.extension.getViews(); browser.test.assertTrue( pages.includes(window), "Expected this tab to be an extension view" ); pages = pages.filter(w => w !== window); browser.test.assertEq( pages[0], browser.extension.getBackgroundPage(), "Expected the other page to be a background page" ); browser.test.sendMessage("tab.html ready"); } } add_task(async function () { let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); gBrowser.selectedTab = tab1; let extension = ExtensionTestUtils.loadExtension({ manifest: { permissions: ["contextMenus"], }, background: testScript, files: { "tab.html": ``, "tab.js": testScript, }, }); await extension.startup(); await extension.awaitMessage("tab.html ready"); async function clickContextMenu() { // Using openContextMenu instead of openExtensionContextMenu because the // test extension has only one context menu item. let extensionMenuRoot = await openContextMenu(); let items = extensionMenuRoot.getElementsByAttribute("label", "tifier"); is(items.length, 1, "Expected one context menu item"); await closeExtensionContextMenu(items[0]); // One of them is "tab", the other is "background". info(`onClicked from: ${await extension.awaitMessage("onClicked-fired")}`); info(`onClicked from: ${await extension.awaitMessage("onClicked-fired")}`); } function getCounts(page) { extension.sendMessage(page, "get-click-counts"); return extension.awaitMessage("click-counts"); } async function resetCounts() { extension.sendMessage("tab", "clear-click-counts"); extension.sendMessage("background", "clear-click-counts"); await extension.awaitMessage("next"); await extension.awaitMessage("next"); } // During this test, at most one "onclick" attribute is expected at any time. for (let pageOne of ["background", "tab"]) { for (let pageTwo of ["background", "tab"]) { info(`Testing with menu created by ${pageOne} and updated by ${pageTwo}`); extension.sendMessage(pageOne, "create-with-onclick"); await extension.awaitMessage("next"); // Test that update without onclick attribute does not clear the existing // onclick handler. extension.sendMessage(pageTwo, "update-without-onclick"); await extension.awaitMessage("next"); await clickContextMenu(); let clickCounts = await getCounts(pageOne); is( clickCounts.old, 1, `Original onclick should still be present in ${pageOne}` ); is(clickCounts.new, 0, `Not expecting any new handlers in ${pageOne}`); if (pageOne !== pageTwo) { clickCounts = await getCounts(pageTwo); is(clickCounts.old, 0, `Not expecting any handlers in ${pageTwo}`); is(clickCounts.new, 0, `Not expecting any new handlers in ${pageTwo}`); } await resetCounts(); // Test that update with onclick handler in a different page clears the // existing handler and activates the new onclick handler. extension.sendMessage(pageTwo, "update-with-onclick"); await extension.awaitMessage("next"); await clickContextMenu(); clickCounts = await getCounts(pageOne); is(clickCounts.old, 0, `Original onclick should be gone from ${pageOne}`); if (pageOne !== pageTwo) { is( clickCounts.new, 0, `Still not expecting new handlers in ${pageOne}` ); } clickCounts = await getCounts(pageTwo); if (pageOne !== pageTwo) { is(clickCounts.old, 0, `Not expecting an old onclick in ${pageTwo}`); } is(clickCounts.new, 1, `New onclick should be triggered in ${pageTwo}`); await resetCounts(); // Test that updating the handler (different again from the last `update` // call, but the same as the `create` call) clears the existing handler // and activates the new onclick handler. extension.sendMessage(pageOne, "update-with-onclick"); await extension.awaitMessage("next"); await clickContextMenu(); clickCounts = await getCounts(pageOne); is(clickCounts.new, 1, `onclick should be triggered in ${pageOne}`); if (pageOne !== pageTwo) { clickCounts = await getCounts(pageTwo); is(clickCounts.new, 0, `onclick should be gone from ${pageTwo}`); } await resetCounts(); // Test that removing the context menu and recreating it with the same ID // (in a different context) does not leave behind any onclick handlers. extension.sendMessage(pageTwo, "remove"); await extension.awaitMessage("next"); extension.sendMessage(pageTwo, "create-without-onclick"); await extension.awaitMessage("next"); await clickContextMenu(); clickCounts = await getCounts(pageOne); is(clickCounts.new, 0, `Did not expect any click handlers in ${pageOne}`); if (pageOne !== pageTwo) { clickCounts = await getCounts(pageTwo); is( clickCounts.new, 0, `Did not expect any click handlers in ${pageTwo}` ); } await resetCounts(); // Remove context menu for the next iteration of the test. And just to get // more coverage, let's use removeAll instead of remove. extension.sendMessage(pageOne, "removeAll"); await extension.awaitMessage("next"); } } await extension.unload(); BrowserTestUtils.removeTab(tab1); }); add_task(async function test_onclick_modifiers() { const manifest = { permissions: ["contextMenus"], }; function background() { function onclick(info) { browser.test.sendMessage("click", info); } browser.contextMenus.create( { contexts: ["all"], title: "modify", onclick }, () => { browser.test.sendMessage("ready"); } ); } const extension = ExtensionTestUtils.loadExtension({ manifest, background }); const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE); await extension.startup(); await extension.awaitMessage("ready"); async function click(modifiers = {}) { const menu = await openContextMenu(); const items = menu.getElementsByAttribute("label", "modify"); is(items.length, 1, "Got exactly one context menu item"); await closeExtensionContextMenu(items[0], modifiers); return extension.awaitMessage("click"); } const plain = await click(); is(plain.modifiers.length, 0, "modifiers array empty with a plain click"); const shift = await click({ shiftKey: true }); is(shift.modifiers.join(), "Shift", "Correct modifier: Shift"); const ctrl = await click({ ctrlKey: true }); if (AppConstants.platform !== "macosx") { is(ctrl.modifiers.join(), "Ctrl", "Correct modifier: Ctrl"); } else { is( ctrl.modifiers.sort().join(), "Ctrl,MacCtrl", "Correct modifier: Ctrl (and MacCtrl)" ); const meta = await click({ metaKey: true }); is(meta.modifiers.join(), "Command", "Correct modifier: Command"); } const altShift = await click({ altKey: true, shiftKey: true }); is( altShift.modifiers.sort().join(), "Alt,Shift", "Correct modifiers: Shift+Alt" ); BrowserTestUtils.removeTab(tab); await extension.unload(); });