UNPKG

cfx

Version:

programmatically use cfx with node.js

1,757 lines (1,428 loc) 105 kB
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et: */ /* 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'; let { Cc, Ci } = require("chrome"); const { Loader } = require('sdk/test/loader'); const timer = require("sdk/timers"); const { merge } = require("sdk/util/object"); // These should match the same constants in the module. const ITEM_CLASS = "addon-context-menu-item"; const SEPARATOR_CLASS = "addon-context-menu-separator"; const OVERFLOW_THRESH_DEFAULT = 10; const OVERFLOW_THRESH_PREF = "extensions.addon-sdk.context-menu.overflowThreshold"; const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu"; const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup"; const TEST_DOC_URL = module.uri.replace(/\.js$/, ".html"); // Tests that when present the separator is placed before the separator from // the old context-menu module exports.testSeparatorPosition = function (test) { test = new TestHelper(test); let loader = test.newLoader(); // Create the old separator let oldSeparator = test.contextMenuPopup.ownerDocument.createElement("menuseparator"); oldSeparator.id = "jetpack-context-menu-separator"; test.contextMenuPopup.appendChild(oldSeparator); // Create an item. let item = new loader.cm.Item({ label: "item" }); test.showMenu(null, function (popup) { test.assertEqual(test.contextMenuSeparator.nextSibling.nextSibling, oldSeparator, "New separator should appear before the old one"); test.contextMenuPopup.removeChild(oldSeparator); test.done(); }); }; // Destroying items that were previously created should cause them to be absent // from the menu. exports.testConstructDestroy = function (test) { test = new TestHelper(test); let loader = test.newLoader(); // Create an item. let item = new loader.cm.Item({ label: "item" }); test.assertEqual(item.parentMenu, loader.cm.contentContextMenu, "item's parent menu should be correct"); test.showMenu(null, function (popup) { // It should be present when the menu is shown. test.checkMenu([item], [], []); popup.hidePopup(); // Destroy the item. Multiple destroys should be harmless. item.destroy(); item.destroy(); test.showMenu(null, function (popup) { // It should be removed from the menu. test.checkMenu([item], [], [item]); test.done(); }); }); }; // Destroying an item twice should not cause an error. exports.testDestroyTwice = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item" }); item.destroy(); item.destroy(); test.pass("Destroying an item twice should not cause an error."); test.done(); }; // CSS selector contexts should cause their items to be present in the menu // when the menu is invoked on nodes that match the selectors. exports.testSelectorContextMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", data: "item", context: loader.cm.SelectorContext("img") }); test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("image"), function (popup) { test.checkMenu([item], [], []); test.done(); }); }); }; // CSS selector contexts should cause their items to be present in the menu // when the menu is invoked on nodes that have ancestors that match the // selectors. exports.testSelectorAncestorContextMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", data: "item", context: loader.cm.SelectorContext("a[href]") }); test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("span-link"), function (popup) { test.checkMenu([item], [], []); test.done(); }); }); }; // CSS selector contexts should cause their items to be absent from the menu // when the menu is not invoked on nodes that match or have ancestors that // match the selectors. exports.testSelectorContextNoMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", data: "item", context: loader.cm.SelectorContext("img") }); test.showMenu(null, function (popup) { test.checkMenu([item], [item], []); test.done(); }); }; // Page contexts should cause their items to be present in the menu when the // menu is not invoked on an active element. exports.testPageContextMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let items = [ new loader.cm.Item({ label: "item 0" }), new loader.cm.Item({ label: "item 1", context: undefined }), new loader.cm.Item({ label: "item 2", context: loader.cm.PageContext() }), new loader.cm.Item({ label: "item 3", context: [loader.cm.PageContext()] }) ]; test.showMenu(null, function (popup) { test.checkMenu(items, [], []); test.done(); }); }; // Page contexts should cause their items to be absent from the menu when the // menu is invoked on an active element. exports.testPageContextNoMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let items = [ new loader.cm.Item({ label: "item 0" }), new loader.cm.Item({ label: "item 1", context: undefined }), new loader.cm.Item({ label: "item 2", context: loader.cm.PageContext() }), new loader.cm.Item({ label: "item 3", context: [loader.cm.PageContext()] }) ]; test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("image"), function (popup) { test.checkMenu(items, items, []); test.done(); }); }); }; // Selection contexts should cause items to appear when a selection exists. exports.testSelectionContextMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = loader.cm.Item({ label: "item", context: loader.cm.SelectionContext() }); test.withTestDoc(function (window, doc) { window.getSelection().selectAllChildren(doc.body); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.done(); }); }); }; // Selection contexts should cause items to appear when a selection exists in // a text field. exports.testSelectionContextMatchInTextField = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = loader.cm.Item({ label: "item", context: loader.cm.SelectionContext() }); test.withTestDoc(function (window, doc) { let textfield = doc.getElementById("textfield"); textfield.setSelectionRange(0, textfield.value.length); test.showMenu(textfield, function (popup) { test.checkMenu([item], [], []); test.done(); }); }); }; // Selection contexts should not cause items to appear when a selection does // not exist in a text field. exports.testSelectionContextNoMatchInTextField = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = loader.cm.Item({ label: "item", context: loader.cm.SelectionContext() }); test.withTestDoc(function (window, doc) { let textfield = doc.getElementById("textfield"); textfield.setSelectionRange(0, 0); test.showMenu(textfield, function (popup) { test.checkMenu([item], [item], []); test.done(); }); }); }; // Selection contexts should not cause items to appear when a selection does // not exist. exports.testSelectionContextNoMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = loader.cm.Item({ label: "item", context: loader.cm.SelectionContext() }); test.showMenu(null, function (popup) { test.checkMenu([item], [item], []); test.done(); }); }; // Selection contexts should cause items to appear when a selection exists even // for newly opened pages exports.testSelectionContextInNewTab = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = loader.cm.Item({ label: "item", context: loader.cm.SelectionContext() }); test.withTestDoc(function (window, doc) { let link = doc.getElementById("targetlink"); link.click(); test.delayedEventListener(this.tabBrowser, "load", function () { let browser = test.tabBrowser.selectedBrowser; let window = browser.contentWindow; let doc = browser.contentDocument; window.getSelection().selectAllChildren(doc.body); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); popup.hidePopup(); test.tabBrowser.removeTab(test.tabBrowser.selectedTab); test.tabBrowser.selectedTab = test.tab; test.showMenu(null, function (popup) { test.checkMenu([item], [item], []); test.done(); }); }); }, true); }); }; // Selection contexts should work when right clicking a form button exports.testSelectionContextButtonMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = loader.cm.Item({ label: "item", context: loader.cm.SelectionContext() }); test.withTestDoc(function (window, doc) { window.getSelection().selectAllChildren(doc.body); let button = doc.getElementById("button"); test.showMenu(button, function (popup) { test.checkMenu([item], [], []); test.done(); }); }); }; //Selection contexts should work when right clicking a form button exports.testSelectionContextButtonNoMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = loader.cm.Item({ label: "item", context: loader.cm.SelectionContext() }); test.withTestDoc(function (window, doc) { let button = doc.getElementById("button"); test.showMenu(button, function (popup) { test.checkMenu([item], [item], []); test.done(); }); }); }; // URL contexts should cause items to appear on pages that match. exports.testURLContextMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let items = [ loader.cm.Item({ label: "item 0", context: loader.cm.URLContext(TEST_DOC_URL) }), loader.cm.Item({ label: "item 1", context: loader.cm.URLContext([TEST_DOC_URL, "*.bogus.com"]) }), loader.cm.Item({ label: "item 2", context: loader.cm.URLContext([new RegExp(".*\\.html")]) }) ]; test.withTestDoc(function (window, doc) { test.showMenu(null, function (popup) { test.checkMenu(items, [], []); test.done(); }); }); }; // URL contexts should not cause items to appear on pages that do not match. exports.testURLContextNoMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let items = [ loader.cm.Item({ label: "item 0", context: loader.cm.URLContext("*.bogus.com") }), loader.cm.Item({ label: "item 1", context: loader.cm.URLContext(["*.bogus.com", "*.gnarly.com"]) }), loader.cm.Item({ label: "item 2", context: loader.cm.URLContext([new RegExp(".*\\.js")]) }) ]; test.withTestDoc(function (window, doc) { test.showMenu(null, function (popup) { test.checkMenu(items, items, []); test.done(); }); }); }; // Removing a non-matching URL context after its item is created and the page is // loaded should cause the item's content script to be evaluated when the // context menu is next opened. exports.testURLContextRemove = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let shouldBeEvaled = false; let context = loader.cm.URLContext("*.bogus.com"); let item = loader.cm.Item({ label: "item", context: context, contentScript: 'self.postMessage("ok"); self.on("context", function () true);', onMessage: function (msg) { test.assert(shouldBeEvaled, "content script should be evaluated when expected"); test.assertEqual(msg, "ok", "Should have received the right message"); shouldBeEvaled = false; } }); test.withTestDoc(function (window, doc) { test.showMenu(null, function (popup) { test.checkMenu([item], [item], []); item.context.remove(context); shouldBeEvaled = true; test.hideMenu(function () { test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.assert(!shouldBeEvaled, "content script should have been evaluated"); test.hideMenu(function () { // Shouldn't get evaluated again test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.done(); }); }); }); }); }); }); }; // Loading a new page in the same tab should correctly start a new worker for // any content scripts exports.testPageReload = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = loader.cm.Item({ label: "Item", contentScript: "var doc = document; self.on('context', function(node) doc.body.getAttribute('showItem') == 'true');" }); test.withTestDoc(function (window, doc) { // Set a flag on the document that the item uses doc.body.setAttribute("showItem", "true"); test.showMenu(null, function (popup) { // With the attribute true the item should be visible in the menu test.checkMenu([item], [], []); test.hideMenu(function() { let browser = this.tabBrowser.getBrowserForTab(this.tab) test.delayedEventListener(browser, "load", function() { test.delayedEventListener(browser, "load", function() { window = browser.contentWindow; doc = window.document; // Set a flag on the document that the item uses doc.body.setAttribute("showItem", "false"); test.showMenu(null, function (popup) { // In the new document with the attribute false the item should be // hidden, but if the contentScript hasn't been reloaded it will // still see the old value test.checkMenu([item], [item], []); test.done(); }); }, true); browser.loadURI(TEST_DOC_URL, null, null); }, true); // Required to make sure we load a new page in history rather than // just reloading the current page which would unload it browser.loadURI("about:blank", null, null); }); }); }); }; // Closing a page after it's been used with a worker should cause the worker // to be destroyed /*exports.testWorkerDestroy = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let loadExpected = false; let item = loader.cm.Item({ label: "item", contentScript: 'self.postMessage("loaded"); self.on("detach", function () { console.log("saw detach"); self.postMessage("detach") });', onMessage: function (msg) { console.log("Saw " + msg) switch (msg) { case "loaded": test.assert(loadExpected, "Should have seen the load event at the right time"); loadExpected = false; break; case "detach": test.done(); break; } } }); test.withTestDoc(function (window, doc) { loadExpected = true; test.showMenu(null, function (popup) { test.assert(!loadExpected, "Should have seen a message"); test.checkMenu([item], [], []); test.closeTab(); }); }); };*/ // Content contexts that return true should cause their items to be present // in the menu. exports.testContentContextMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function () true);' }); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.done(); }); }; // Content contexts that return false should cause their items to be absent // from the menu. exports.testContentContextNoMatch = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function () false);' }); test.showMenu(null, function (popup) { test.checkMenu([item], [item], []); test.done(); }); }; // Content contexts that return undefined should cause their items to be absent // from the menu. exports.testContentContextUndefined = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function () {});' }); test.showMenu(null, function (popup) { test.checkMenu([item], [item], []); test.done(); }); }; // Content contexts that return an empty string should cause their items to be // absent from the menu and shouldn't wipe the label exports.testContentContextEmptyString = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function () "");' }); test.showMenu(null, function (popup) { test.checkMenu([item], [item], []); test.assertEqual(item.label, "item", "Label should still be correct"); test.done(); }); }; // If any content contexts returns true then their items should be present in // the menu. exports.testMultipleContentContextMatch1 = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function () true); ' + 'self.on("context", function () false);', onMessage: function() { test.fail("Should not have called the second context listener"); } }); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.done(); }); }; // If any content contexts returns true then their items should be present in // the menu. exports.testMultipleContentContextMatch2 = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function () false); ' + 'self.on("context", function () true);' }); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.done(); }); }; // If any content contexts returns a string then their items should be present // in the menu. exports.testMultipleContentContextString1 = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function () "new label"); ' + 'self.on("context", function () false);' }); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.assertEqual(item.label, "new label", "Label should have changed"); test.done(); }); }; // If any content contexts returns a string then their items should be present // in the menu. exports.testMultipleContentContextString2 = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function () false); ' + 'self.on("context", function () "new label");' }); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.assertEqual(item.label, "new label", "Label should have changed"); test.done(); }); }; // If many content contexts returns a string then the first should take effect exports.testMultipleContentContextString3 = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function () "new label 1"); ' + 'self.on("context", function () "new label 2");' }); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.assertEqual(item.label, "new label 1", "Label should have changed"); test.done(); }); }; // Content contexts that return true should cause their items to be present // in the menu when context clicking an active element. exports.testContentContextMatchActiveElement = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let items = [ new loader.cm.Item({ label: "item 1", contentScript: 'self.on("context", function () true);' }), new loader.cm.Item({ label: "item 2", context: undefined, contentScript: 'self.on("context", function () true);' }), // These items will always be hidden by the declarative usage of PageContext new loader.cm.Item({ label: "item 3", context: loader.cm.PageContext(), contentScript: 'self.on("context", function () true);' }), new loader.cm.Item({ label: "item 4", context: [loader.cm.PageContext()], contentScript: 'self.on("context", function () true);' }) ]; test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("image"), function (popup) { test.checkMenu(items, [items[2], items[3]], []); test.done(); }); }); }; // Content contexts that return false should cause their items to be absent // from the menu when context clicking an active element. exports.testContentContextNoMatchActiveElement = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let items = [ new loader.cm.Item({ label: "item 1", contentScript: 'self.on("context", function () false);' }), new loader.cm.Item({ label: "item 2", context: undefined, contentScript: 'self.on("context", function () false);' }), // These items will always be hidden by the declarative usage of PageContext new loader.cm.Item({ label: "item 3", context: loader.cm.PageContext(), contentScript: 'self.on("context", function () false);' }), new loader.cm.Item({ label: "item 4", context: [loader.cm.PageContext()], contentScript: 'self.on("context", function () false);' }) ]; test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("image"), function (popup) { test.checkMenu(items, items, []); test.done(); }); }); }; // Content contexts that return undefined should cause their items to be absent // from the menu when context clicking an active element. exports.testContentContextNoMatchActiveElement = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let items = [ new loader.cm.Item({ label: "item 1", contentScript: 'self.on("context", function () {});' }), new loader.cm.Item({ label: "item 2", context: undefined, contentScript: 'self.on("context", function () {});' }), // These items will always be hidden by the declarative usage of PageContext new loader.cm.Item({ label: "item 3", context: loader.cm.PageContext(), contentScript: 'self.on("context", function () {});' }), new loader.cm.Item({ label: "item 4", context: [loader.cm.PageContext()], contentScript: 'self.on("context", function () {});' }) ]; test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("image"), function (popup) { test.checkMenu(items, items, []); test.done(); }); }); }; // Content contexts that return a string should cause their items to be present // in the menu and the items' labels to be updated. exports.testContentContextMatchString = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "first label", contentScript: 'self.on("context", function () "second label");' }); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.assertEqual(item.label, "second label", "item's label should be updated"); test.done(); }); }; // Ensure that contentScriptFile is working correctly exports.testContentScriptFile = function (test) { test = new TestHelper(test); let loader = test.newLoader(); // Reject remote files test.assertRaises(function() { new loader.cm.Item({ label: "item", contentScriptFile: "http://mozilla.com/context-menu.js" }); }, "The 'contentScriptFile' option must be a local file URL " + "or an array of local file URLs.", "Item throws when contentScriptFile is a remote URL"); // But accept files from data folder let item = new loader.cm.Item({ label: "item", contentScriptFile: require("sdk/self").data.url("test-context-menu.js") }); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); test.done(); }); }; // The args passed to context listeners should be correct. exports.testContentContextArgs = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let callbacks = 0; let item = new loader.cm.Item({ label: "item", contentScript: 'self.on("context", function (node) {' + ' self.postMessage(node.tagName);' + ' return false;' + '});', onMessage: function (tagName) { test.assertEqual(tagName, "HTML", "node should be an HTML element"); if (++callbacks == 2) test.done(); } }); test.showMenu(null, function () { if (++callbacks == 2) test.done(); }); }; // Multiple contexts imply intersection, not union, and content context // listeners should not be called if all declarative contexts are not current. exports.testMultipleContexts = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()], contentScript: 'self.on("context", function () self.postMessage());', onMessage: function () { test.fail("Context listener should not be called"); } }); test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("span-link"), function (popup) { test.checkMenu([item], [item], []); test.done(); }); }); }; // Once a context is removed, it should no longer cause its item to appear. exports.testRemoveContext = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let ctxt = loader.cm.SelectorContext("img"); let item = new loader.cm.Item({ label: "item", context: ctxt }); test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("image"), function (popup) { // The item should be present at first. test.checkMenu([item], [], []); popup.hidePopup(); // Remove the img context and check again. item.context.remove(ctxt); test.showMenu(doc.getElementById("image"), function (popup) { test.checkMenu([item], [item], []); test.done(); }); }); }); }; // Lots of items should overflow into the overflow submenu. exports.testOverflow = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let items = []; for (let i = 0; i < OVERFLOW_THRESH_DEFAULT + 1; i++) { let item = new loader.cm.Item({ label: "item " + i }); items.push(item); } test.showMenu(null, function (popup) { test.checkMenu(items, [], []); test.done(); }); }; // Module unload should cause all items to be removed. exports.testUnload = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item" }); test.showMenu(null, function (popup) { // The menu should contain the item. test.checkMenu([item], [], []); popup.hidePopup(); // Unload the module. loader.unload(); test.showMenu(null, function (popup) { // The item should be removed from the menu. test.checkMenu([item], [], [item]); test.done(); }); }); }; // Using multiple module instances to add items without causing overflow should // work OK. Assumes OVERFLOW_THRESH_DEFAULT >= 2. exports.testMultipleModulesAdd = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); // Use each module to add an item, then unload each module in turn. let item0 = new loader0.cm.Item({ label: "item 0" }); let item1 = new loader1.cm.Item({ label: "item 1" }); test.showMenu(null, function (popup) { // The menu should contain both items. test.checkMenu([item0, item1], [], []); popup.hidePopup(); // Unload the first module. loader0.unload(); test.showMenu(null, function (popup) { // The first item should be removed from the menu. test.checkMenu([item0, item1], [], [item0]); popup.hidePopup(); // Unload the second module. loader1.unload(); test.showMenu(null, function (popup) { // Both items should be removed from the menu. test.checkMenu([item0, item1], [], [item0, item1]); test.done(); }); }); }); }; // Using multiple module instances to add items causing overflow should work OK. exports.testMultipleModulesAddOverflow = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); // Use module 0 to add OVERFLOW_THRESH_DEFAULT items. let items0 = []; for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { let item = new loader0.cm.Item({ label: "item 0 " + i }); items0.push(item); } // Use module 1 to add one item. let item1 = new loader1.cm.Item({ label: "item 1" }); let allItems = items0.concat(item1); test.showMenu(null, function (popup) { // The menu should contain all items in overflow. test.checkMenu(allItems, [], []); popup.hidePopup(); // Unload the first module. loader0.unload(); test.showMenu(null, function (popup) { // The first items should be removed from the menu, which should not // overflow. test.checkMenu(allItems, [], items0); popup.hidePopup(); // Unload the second module. loader1.unload(); test.showMenu(null, function (popup) { // All items should be removed from the menu. test.checkMenu(allItems, [], allItems); test.done(); }); }); }); }; // Using multiple module instances to modify the menu without causing overflow // should work OK. This test creates two loaders and: // loader0 create item -> loader1 create item -> loader0.unload -> // loader1.unload exports.testMultipleModulesDiffContexts1 = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let item0 = new loader0.cm.Item({ label: "item 0", context: loader0.cm.SelectorContext("img") }); let item1 = new loader1.cm.Item({ label: "item 1" }); test.showMenu(null, function (popup) { // The menu should contain item1. test.checkMenu([item0, item1], [item0], []); popup.hidePopup(); // Unload module 0. loader0.unload(); test.showMenu(null, function (popup) { // item0 should be removed from the menu. test.checkMenu([item0, item1], [], [item0]); popup.hidePopup(); // Unload module 1. loader1.unload(); test.showMenu(null, function (popup) { // Both items should be removed from the menu. test.checkMenu([item0, item1], [], [item0, item1]); test.done(); }); }); }); }; // Using multiple module instances to modify the menu without causing overflow // should work OK. This test creates two loaders and: // loader1 create item -> loader0 create item -> loader0.unload -> // loader1.unload exports.testMultipleModulesDiffContexts2 = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let item1 = new loader1.cm.Item({ label: "item 1" }); let item0 = new loader0.cm.Item({ label: "item 0", context: loader0.cm.SelectorContext("img") }); test.showMenu(null, function (popup) { // The menu should contain item1. test.checkMenu([item0, item1], [item0], []); popup.hidePopup(); // Unload module 0. loader0.unload(); test.showMenu(null, function (popup) { // item0 should be removed from the menu. test.checkMenu([item0, item1], [], [item0]); popup.hidePopup(); // Unload module 1. loader1.unload(); test.showMenu(null, function (popup) { // Both items should be removed from the menu. test.checkMenu([item0, item1], [], [item0, item1]); test.done(); }); }); }); }; // Using multiple module instances to modify the menu without causing overflow // should work OK. This test creates two loaders and: // loader0 create item -> loader1 create item -> loader1.unload -> // loader0.unload exports.testMultipleModulesDiffContexts3 = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let item0 = new loader0.cm.Item({ label: "item 0", context: loader0.cm.SelectorContext("img") }); let item1 = new loader1.cm.Item({ label: "item 1" }); test.showMenu(null, function (popup) { // The menu should contain item1. test.checkMenu([item0, item1], [item0], []); popup.hidePopup(); // Unload module 1. loader1.unload(); test.showMenu(null, function (popup) { // item1 should be removed from the menu. test.checkMenu([item0, item1], [item0], [item1]); popup.hidePopup(); // Unload module 0. loader0.unload(); test.showMenu(null, function (popup) { // Both items should be removed from the menu. test.checkMenu([item0, item1], [], [item0, item1]); test.done(); }); }); }); }; // Using multiple module instances to modify the menu without causing overflow // should work OK. This test creates two loaders and: // loader1 create item -> loader0 create item -> loader1.unload -> // loader0.unload exports.testMultipleModulesDiffContexts4 = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let item1 = new loader1.cm.Item({ label: "item 1" }); let item0 = new loader0.cm.Item({ label: "item 0", context: loader0.cm.SelectorContext("img") }); test.showMenu(null, function (popup) { // The menu should contain item1. test.checkMenu([item0, item1], [item0], []); popup.hidePopup(); // Unload module 1. loader1.unload(); test.showMenu(null, function (popup) { // item1 should be removed from the menu. test.checkMenu([item0, item1], [item0], [item1]); popup.hidePopup(); // Unload module 0. loader0.unload(); test.showMenu(null, function (popup) { // Both items should be removed from the menu. test.checkMenu([item0, item1], [], [item0, item1]); test.done(); }); }); }); }; // Test interactions between a loaded module, unloading another module, and the // menu separator and overflow submenu. exports.testMultipleModulesAddRemove = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let item = new loader0.cm.Item({ label: "item" }); test.showMenu(null, function (popup) { // The menu should contain the item. test.checkMenu([item], [], []); popup.hidePopup(); // Remove the item. item.destroy(); test.showMenu(null, function (popup) { // The item should be removed from the menu. test.checkMenu([item], [], [item]); popup.hidePopup(); // Unload module 1. loader1.unload(); test.showMenu(null, function (popup) { // There shouldn't be any errors involving the menu separator or // overflow submenu. test.checkMenu([item], [], [item]); test.done(); }); }); }); }; // Checks that the order of menu items is correct when adding/removing across // multiple modules. All items from a single module should remain in a group exports.testMultipleModulesOrder = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); // Use each module to add an item, then unload each module in turn. let item0 = new loader0.cm.Item({ label: "item 0" }); let item1 = new loader1.cm.Item({ label: "item 1" }); test.showMenu(null, function (popup) { // The menu should contain both items. test.checkMenu([item0, item1], [], []); popup.hidePopup(); let item2 = new loader0.cm.Item({ label: "item 2" }); test.showMenu(null, function (popup) { // The new item should be grouped with the same items from loader0. test.checkMenu([item0, item2, item1], [], []); popup.hidePopup(); let item3 = new loader1.cm.Item({ label: "item 3" }); test.showMenu(null, function (popup) { // Same again test.checkMenu([item0, item2, item1, item3], [], []); test.done(); }); }); }); }; // Checks that the order of menu items is correct when adding/removing across // multiple modules when overflowing. All items from a single module should // remain in a group exports.testMultipleModulesOrderOverflow = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let prefs = loader0.loader.require("sdk/preferences/service"); prefs.set(OVERFLOW_THRESH_PREF, 0); // Use each module to add an item, then unload each module in turn. let item0 = new loader0.cm.Item({ label: "item 0" }); let item1 = new loader1.cm.Item({ label: "item 1" }); test.showMenu(null, function (popup) { // The menu should contain both items. test.checkMenu([item0, item1], [], []); popup.hidePopup(); let item2 = new loader0.cm.Item({ label: "item 2" }); test.showMenu(null, function (popup) { // The new item should be grouped with the same items from loader0. test.checkMenu([item0, item2, item1], [], []); popup.hidePopup(); let item3 = new loader1.cm.Item({ label: "item 3" }); test.showMenu(null, function (popup) { // Same again test.checkMenu([item0, item2, item1, item3], [], []); test.done(); }); }); }); }; // Checks that if a module's items are all hidden then the overflow menu doesn't // get hidden exports.testMultipleModulesOverflowHidden = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let prefs = loader0.loader.require("sdk/preferences/service"); prefs.set(OVERFLOW_THRESH_PREF, 0); // Use each module to add an item, then unload each module in turn. let item0 = new loader0.cm.Item({ label: "item 0" }); let item1 = new loader1.cm.Item({ label: "item 1", context: loader1.cm.SelectorContext("a") }); test.showMenu(null, function (popup) { // One should be hidden test.checkMenu([item0, item1], [item1], []); test.done(); }); }; // Checks that if a module's items are all hidden then the overflow menu doesn't // get hidden (reverse order to above) exports.testMultipleModulesOverflowHidden2 = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let prefs = loader0.loader.require("sdk/preferences/service"); prefs.set(OVERFLOW_THRESH_PREF, 0); // Use each module to add an item, then unload each module in turn. let item0 = new loader0.cm.Item({ label: "item 0", context: loader0.cm.SelectorContext("a") }); let item1 = new loader1.cm.Item({ label: "item 1" }); test.showMenu(null, function (popup) { // One should be hidden test.checkMenu([item0, item1], [item0], []); test.done(); }); }; // Checks that we don't overflow if there are more items than the overflow // threshold but not all of them are visible exports.testOverflowIgnoresHidden = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let prefs = loader.loader.require("sdk/preferences/service"); prefs.set(OVERFLOW_THRESH_PREF, 2); let allItems = [ new loader.cm.Item({ label: "item 0" }), new loader.cm.Item({ label: "item 1" }), new loader.cm.Item({ label: "item 2", context: loader.cm.SelectorContext("a") }) ]; test.showMenu(null, function (popup) { // One should be hidden test.checkMenu(allItems, [allItems[2]], []); test.done(); }); }; // Checks that we don't overflow if there are more items than the overflow // threshold but not all of them are visible exports.testOverflowIgnoresHiddenMultipleModules1 = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let prefs = loader0.loader.require("sdk/preferences/service"); prefs.set(OVERFLOW_THRESH_PREF, 2); let allItems = [ new loader0.cm.Item({ label: "item 0" }), new loader0.cm.Item({ label: "item 1" }), new loader1.cm.Item({ label: "item 2", context: loader1.cm.SelectorContext("a") }), new loader1.cm.Item({ label: "item 3", context: loader1.cm.SelectorContext("a") }) ]; test.showMenu(null, function (popup) { // One should be hidden test.checkMenu(allItems, [allItems[2], allItems[3]], []); test.done(); }); }; // Checks that we don't overflow if there are more items than the overflow // threshold but not all of them are visible exports.testOverflowIgnoresHiddenMultipleModules2 = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let prefs = loader0.loader.require("sdk/preferences/service"); prefs.set(OVERFLOW_THRESH_PREF, 2); let allItems = [ new loader0.cm.Item({ label: "item 0" }), new loader0.cm.Item({ label: "item 1", context: loader0.cm.SelectorContext("a") }), new loader1.cm.Item({ label: "item 2" }), new loader1.cm.Item({ label: "item 3", context: loader1.cm.SelectorContext("a") }) ]; test.showMenu(null, function (popup) { // One should be hidden test.checkMenu(allItems, [allItems[1], allItems[3]], []); test.done(); }); }; // Checks that we don't overflow if there are more items than the overflow // threshold but not all of them are visible exports.testOverflowIgnoresHiddenMultipleModules3 = function (test) { test = new TestHelper(test); let loader0 = test.newLoader(); let loader1 = test.newLoader(); let prefs = loader0.loader.require("sdk/preferences/service"); prefs.set(OVERFLOW_THRESH_PREF, 2); let allItems = [ new loader0.cm.Item({ label: "item 0", context: loader0.cm.SelectorContext("a") }), new loader0.cm.Item({ label: "item 1", context: loader0.cm.SelectorContext("a") }), new loader1.cm.Item({ label: "item 2" }), new loader1.cm.Item({ label: "item 3" }) ]; test.showMenu(null, function (popup) { // One should be hidden test.checkMenu(allItems, [allItems[0], allItems[1]], []); test.done(); }); }; // Tests that we transition between overflowing to non-overflowing to no items // and back again exports.testOverflowTransition = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let prefs = loader.loader.require("sdk/preferences/service"); prefs.set(OVERFLOW_THRESH_PREF, 2); let pItems = [ new loader.cm.Item({ label: "item 0", context: loader.cm.SelectorContext("p") }), new loader.cm.Item({ label: "item 1", context: loader.cm.SelectorContext("p") }) ]; let aItems = [ new loader.cm.Item({ label: "item 2", context: loader.cm.SelectorContext("a") }), new loader.cm.Item({ label: "item 3", context: loader.cm.SelectorContext("a") }) ]; let allItems = pItems.concat(aItems); test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("link"), function (popup) { // The menu should contain all items and will overflow test.checkMenu(allItems, [], []); popup.hidePopup(); test.showMenu(doc.getElementById("text"), function (popup) { // Only contains hald the items and will not overflow test.checkMenu(allItems, aItems, []); popup.hidePopup(); test.showMenu(null, function (popup) { // None of the items will be visible test.checkMenu(allItems, allItems, []); popup.hidePopup(); test.showMenu(doc.getElementById("text"), function (popup) { // Only contains hald the items and will not overflow test.checkMenu(allItems, aItems, []); popup.hidePopup(); test.showMenu(doc.getElementById("link"), function (popup) { // The menu should contain all items and will overflow test.checkMenu(allItems, [], []); popup.hidePopup(); test.showMenu(null, function (popup) { // None of the items will be visible test.checkMenu(allItems, allItems, []); popup.hidePopup(); test.showMenu(doc.getElementById("link"), function (popup) { // The menu should contain all items and will overflow test.checkMenu(allItems, [], []); test.done(); }); }); }); }); }); }); }); }); }; // An item's command listener should work. exports.testItemCommand = function (test) { test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "item", data: "item data", contentScript: 'self.on("click", function (node, data) {' + ' self.postMessage({' + ' tagName: node.tagName,' + ' data: data' + ' });' + '});', onMessage: function (data) { test.assertEqual(this, item, "`this` inside onMessage should be item"); test.assertEqual(data.tagName, "HTML", "node should be an HTML element"); test.assertEqual(data.data, item.data, "data should be item data"); test.done(); } }); test.showMenu(null, function (popup) { test.checkMenu([item], [], []); let elt = test.getItemElt(popup, item); // create a command event let evt = elt.ownerDocument.createEvent('Event'); evt.initEvent('command', true, true); elt.dispatchEvent(evt); }); }; // A menu's click listener should work and receive bubbling 'command' events from // sub-items appropriately. This also tests menus and ensures that when a CSS // selector context matches the clicked node's ancestor, the matching ancestor // is passed to listeners as the clicked node. exports.testMenuCommand = function (test) { // Create a top-level menu, submenu, and item, like this: // topMenu -> submenu -> item // Click the item and make sure the click bubbles. test = new TestHelper(test); let loader = test.newLoader(); let item = new loader.cm.Item({ label: "submenu item", data: "submenu item data", context: loader.cm.SelectorContext("a"), }); let submenu = new loader.cm.Menu({ label: "submenu", context: loader.cm.SelectorContext("a"), items: [item] }); let topMenu = new loader.cm.Menu({ label: "top menu", contentScript: 'self.on("click", function (node, data) {' + ' self.postMessage({' + ' tagName: node.tagName,' + ' data: data' + ' });' + '});', onMessage: function (data) { test.assertEqual(this, topMenu, "`this` inside top menu should be menu"); test.assertEqual(data.tagName, "A", "Clicked node should be anchor"); test.assertEqual(data.data, item.data, "Clicked item data should be correct"); test.done(); }, items: [submenu], context: loader.cm.SelectorContext("a") }); test.withTestDoc(function (window, doc) { test.showMenu(doc.getElementById("span-link"), function (popup) { test.checkMenu([topMenu], [], []); let topMenuElt = test.getItemElt(popup, topMenu); let topMenuPopup = topMenuElt.firstChild; let submenuElt = test.getItemElt(topMenuPopup, submenu); let submenuPopup = submenuElt.firstChild; let itemElt = test.getItemElt(submenuPopup, item); // create a command event let evt = itemElt.ownerDocument.createEvent('Event'); evt.initEvent('command', true, true); itemElt.dispatchEvent(evt); }); }); }; // Click listeners should work when multiple modules are loaded. exports.test