diff options
Diffstat (limited to 'tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.js')
-rw-r--r-- | tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.js | 2075 |
1 files changed, 0 insertions, 2075 deletions
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.js b/tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.js deleted file mode 100644 index 157edf1..0000000 --- a/tools/addon-sdk-1.4/packages/addon-kit/tests/test-context-menu.js +++ /dev/null @@ -1,2075 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et: */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Jetpack. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2010 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Drew Willcoxon <adw@mozilla.com> (Original Author) - * Matteo Ferretti <zer0@mozilla.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -let {Cc,Ci} = require("chrome"); -const { Loader } = require('./helpers'); - -// These should match the same constants in the module. -const ITEM_CLASS = "jetpack-context-menu-item"; -const SEPARATOR_ID = "jetpack-context-menu-separator"; -const OVERFLOW_THRESH_DEFAULT = 10; -const OVERFLOW_THRESH_PREF = - "extensions.addon-sdk.context-menu.overflowThreshold"; -const OVERFLOW_MENU_ID = "jetpack-content-menu-overflow-menu"; -const OVERFLOW_POPUP_ID = "jetpack-content-menu-overflow-popup"; - -const TEST_DOC_URL = module.uri.replace(/\.js$/, ".html"); - - -// 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, null, "item's parent menu should be null"); - - 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]); - 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], []); - 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, []); - 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], []); - 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], []); - 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"]) - }) - ]; - - 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"]) - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu(null, function (popup) { - test.checkMenu([], 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. -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");', - onMessage: function (msg) { - test.assert(shouldBeEvaled, - "content script should be evaluated when expected"); - shouldBeEvaled = false; - test.done(); - } - }); - - test.withTestDoc(function (window, doc) { - shouldBeEvaled = true; - item.context.remove(context); - }); -}; - - -// Adding a non-matching URL context after its item is created and the page is -// loaded should cause the item's worker to be destroyed. -exports.testURLContextAdd = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let item = loader.cm.Item({ label: "item" }); - - test.withTestDoc(function (window, doc) { - let privatePropsKey = loader.globalScope.PRIVATE_PROPS_KEY; - let workerReg = item.valueOf(privatePropsKey)._workerReg; - - let found = false; - for each (let winWorker in workerReg.winWorkers) { - if (winWorker.win === window) { - found = true; - break; - } - } - this.test.assert(found, "window should be present in worker registry"); - - item.context.add(loader.cm.URLContext("*.bogus.com")); - - for each (let winWorker in workerReg.winWorkers) - this.test.assertNotEqual(winWorker.win, window, - "window should not be present in worker registry"); - - test.done(); - }); -}; - - -// 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], []); - 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(); - }); -}; - - -// 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) {' + - ' let Ci = Components.interfaces;' + - ' self.postMessage(node instanceof Ci.nsIDOMHTMLElement);' + - ' return false;' + - '});', - onMessage: function (isElt) { - test.assert(isElt, "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], []); - 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], []); - 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]); - 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([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]); - 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([item1], [], 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); - 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([item1], [item0], []); - popup.hidePopup(); - - // Unload module 0. - loader0.unload(); - test.showMenu(null, function (popup) { - - // item0 should be removed from the menu. - test.checkMenu([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]); - 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([item1], [item0], []); - popup.hidePopup(); - - // Unload module 0. - loader0.unload(); - test.showMenu(null, function (popup) { - - // item0 should be removed from the menu. - test.checkMenu([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]); - 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([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]); - popup.hidePopup(); - - // Unload module 0. - loader0.unload(); - test.showMenu(null, function (popup) { - - // Both items should be removed from the menu. - test.checkMenu([], [], [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([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]); - popup.hidePopup(); - - // Unload module 0. - loader0.unload(); - test.showMenu(null, function (popup) { - - // Both items should be removed from the menu. - test.checkMenu([], [], [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]); - 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]); - test.done(); - }); - }); - }); -}; - - -// An item's click listener should work. -exports.testItemClick = 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) {' + - ' let Ci = Components.interfaces;' + - ' self.postMessage({' + - ' isElt: node instanceof Ci.nsIDOMHTMLElement,' + - ' data: data' + - ' });' + - '});', - onMessage: function (data) { - test.assertEqual(this, item, "`this` inside onMessage should be item"); - test.assert(data.isElt, "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); - elt.click(); - }); -}; - - -// A menu's click listener should work and receive bubbling clicks 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.testMenuClick = 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" - }); - - let submenu = new loader.cm.Menu({ - label: "submenu", - items: [item] - }); - - let topMenu = new loader.cm.Menu({ - label: "top menu", - contentScript: 'self.on("click", function (node, data) {' + - ' let Ci = Components.interfaces;' + - ' self.postMessage({' + - ' isAnchor: node instanceof Ci.nsIDOMHTMLAnchorElement,' + - ' data: data' + - ' });' + - '});', - onMessage: function (data) { - test.assertEqual(this, topMenu, "`this` inside top menu should be menu"); - test.assert(data.isAnchor, "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); - itemElt.click(); - }); - }); -}; - - -// Click listeners should work when multiple modules are loaded. -exports.testItemClickMultipleModules = function (test) { - test = new TestHelper(test); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let item0 = loader0.cm.Item({ - label: "loader 0 item", - contentScript: 'self.on("click", self.postMessage);', - onMessage: function () { - test.fail("loader 0 item should not emit click event"); - } - }); - let item1 = loader1.cm.Item({ - label: "loader 1 item", - contentScript: 'self.on("click", self.postMessage);', - onMessage: function () { - test.pass("loader 1 item clicked as expected"); - test.done(); - } - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item0, item1], [], []); - let item1Elt = test.getItemElt(popup, item1); - item1Elt.click(); - }); -}; - - -// Adding a separator to a submenu should work OK. -exports.testSeparator = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let menu = new loader.cm.Menu({ - label: "submenu", - items: [new loader.cm.Separator()] - }); - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Existing context menu modifications should apply to new windows. -exports.testNewWindow = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ label: "item" }); - - test.withNewWindow(function () { - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); -}; - - -// When a new window is opened, items added by an unloaded module should not -// be present in the menu. -exports.testNewWindowMultipleModules = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - let item = new loader.cm.Item({ label: "item" }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - popup.hidePopup(); - loader.unload(); - test.withNewWindow(function () { - test.showMenu(null, function (popup) { - test.checkMenu([], [], []); - test.done(); - }); - }); - }); -}; - - -// Items in the context menu should be sorted according to locale. -exports.testSorting = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - // Make an unsorted items list. It'll look like this: - // item 1, item 0, item 3, item 2, item 5, item 4, ... - let items = []; - for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i += 2) { - items.push(new loader.cm.Item({ label: "item " + (i + 1) })); - items.push(new loader.cm.Item({ label: "item " + i })); - } - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); -}; - - -// Items in the overflow menu should be sorted according to locale. -exports.testSortingOverflow = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - // Make an unsorted items list. It'll look like this: - // item 1, item 0, item 3, item 2, item 5, item 4, ... - let items = []; - for (let i = 0; i < OVERFLOW_THRESH_DEFAULT * 2; i += 2) { - items.push(new loader.cm.Item({ label: "item " + (i + 1) })); - items.push(new loader.cm.Item({ label: "item " + i })); - } - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); -}; - - -// Multiple modules shouldn't interfere with sorting. -exports.testSortingMultipleModules = function (test) { - test = new TestHelper(test); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let items0 = []; - let items1 = []; - for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { - if (i % 2) { - let item = new loader0.cm.Item({ label: "item " + i }); - items0.push(item); - } - else { - let item = new loader1.cm.Item({ label: "item " + i }); - items1.push(item); - } - } - let allItems = items0.concat(items1); - - test.showMenu(null, function (popup) { - - // All items should be present and sorted. - test.checkMenu(allItems, [], []); - popup.hidePopup(); - loader0.unload(); - loader1.unload(); - test.showMenu(null, function (popup) { - - // All items should be removed. - test.checkMenu([], [], allItems); - test.done(); - }); - }); -}; - - -// The binary search of insertionPoint should work OK. -exports.testInsertionPoint = function (test) { - function mockElts(labels) { - return labels.map(function (label) { - return { label: label, getAttribute: function (l) label }; - }); - } - - test = new TestHelper(test); - let loader = test.newLoader(); - let insertionPoint = loader.globalScope.insertionPoint; - - let ip = insertionPoint("a", []); - test.assertStrictEqual(ip, null, "Insertion point should be null"); - - ip = insertionPoint("a", mockElts(["b"])); - test.assertEqual(ip.label, "b", "Insertion point should be 'b'"); - - ip = insertionPoint("c", mockElts(["b"])); - test.assertStrictEqual(ip, null, "Insertion point should be null"); - - ip = insertionPoint("b", mockElts(["a", "c"])); - test.assertEqual(ip.label, "c", "Insertion point should be 'c'"); - - ip = insertionPoint("c", mockElts(["a", "b", "d"])); - test.assertEqual(ip.label, "d", "Insertion point should be 'd'"); - - ip = insertionPoint("a", mockElts(["b", "c", "d"])); - test.assertEqual(ip.label, "b", "Insertion point should be 'b'"); - - ip = insertionPoint("d", mockElts(["a", "b", "c"])); - test.assertStrictEqual(ip, null, "Insertion point should be null"); - - test.done(); -}; - - -// Content click handlers and context handlers should be able to communicate, -// i.e., they're eval'ed in the same worker and sandbox. -exports.testContentCommunication = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'var potato;' + - 'self.on("context", function () {' + - ' potato = "potato";' + - ' return true;' + - '});' + - 'self.on("click", function () {' + - ' self.postMessage(potato);' + - '});', - }); - - item.on("message", function (data) { - test.assertEqual(data, "potato", "That's a lot of potatoes!"); - test.done(); - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - let elt = test.getItemElt(popup, item); - elt.click(); - }); -}; - - -// When the context menu is invoked on a tab that was already open when the -// module was loaded, it should contain the expected items and content workers -// should function as expected. -exports.testLoadWithOpenTab = function (test) { - test = new TestHelper(test); - test.withTestDoc(function (window, doc) { - let loader = test.newLoader(); - let item = new loader.cm.Item({ - label: "item", - contentScript: - 'self.on("click", function () self.postMessage("click"));', - onMessage: function (msg) { - if (msg === "click") - test.done(); - } - }); - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.getItemElt(popup, item).click(); - }); - }); -}; - - -// Setting an item's label before the menu is ever shown should correctly change -// its label and, if necessary, its order within the menu. -exports.testSetLabelBeforeShow = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let items = [ - new loader.cm.Item({ label: "a" }), - new loader.cm.Item({ label: "b" }) - ] - items[0].label = "z"; - test.assertEqual(items[0].label, "z"); - - test.showMenu(null, function (popup) { - test.checkMenu([items[1], items[0]], [], []); - test.done(); - }); -}; - - -// Setting an item's label after the menu is shown should correctly change its -// label and, if necessary, its order within the menu. -exports.testSetLabelAfterShow = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let items = [ - new loader.cm.Item({ label: "a" }), - new loader.cm.Item({ label: "b" }) - ]; - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - popup.hidePopup(); - - items[0].label = "z"; - test.assertEqual(items[0].label, "z"); - test.showMenu(null, function (popup) { - test.checkMenu([items[1], items[0]], [], []); - test.done(); - }); - }); -}; - - -// Setting an item's label before the menu is ever shown should correctly change -// its label and, if necessary, its order within the menu if the item is in the -// overflow submenu. -exports.testSetLabelBeforeShowOverflow = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let prefs = loader.loader.require("preferences-service"); - prefs.set(OVERFLOW_THRESH_PREF, 0); - - let items = [ - new loader.cm.Item({ label: "a" }), - new loader.cm.Item({ label: "b" }) - ] - items[0].label = "z"; - test.assertEqual(items[0].label, "z"); - - test.showMenu(null, function (popup) { - test.checkMenu([items[1], items[0]], [], []); - prefs.set(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT); - test.done(); - }); -}; - - -// Setting an item's label after the menu is shown should correctly change its -// label and, if necessary, its order within the menu if the item is in the -// overflow submenu. -exports.testSetLabelAfterShowOverflow = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let prefs = loader.loader.require("preferences-service"); - prefs.set(OVERFLOW_THRESH_PREF, 0); - - let items = [ - new loader.cm.Item({ label: "a" }), - new loader.cm.Item({ label: "b" }) - ]; - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - popup.hidePopup(); - - items[0].label = "z"; - test.assertEqual(items[0].label, "z"); - test.showMenu(null, function (popup) { - test.checkMenu([items[1], items[0]], [], []); - prefs.set(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT); - test.done(); - }); - }); -}; - - -// Setting the label of an item in a Menu should work. -exports.testSetLabelMenuItem = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [loader.cm.Item({ label: "a" })] - }); - menu.items[0].label = "z"; - - test.assertEqual(menu.items[0].label, "z"); - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Menu.addItem() should work. -exports.testMenuAddItem = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [ - loader.cm.Item({ label: "item 0" }) - ] - }); - menu.addItem(loader.cm.Item({ label: "item 1" })); - menu.addItem(loader.cm.Item({ label: "item 2" })); - - test.assertEqual(menu.items.length, 3, - "menu should have correct number of items"); - for (let i = 0; i < 3; i++) { - test.assertEqual(menu.items[i].label, "item " + i, - "item label should be correct"); - test.assertEqual(menu.items[i].parentMenu, menu, - "item's parent menu should be correct"); - } - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Adding the same item twice to a menu should work as expected. -exports.testMenuAddItemTwice = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [] - }); - let subitem = loader.cm.Item({ label: "item 1" }) - menu.addItem(subitem); - menu.addItem(loader.cm.Item({ label: "item 0" })); - menu.addItem(subitem); - - test.assertEqual(menu.items.length, 2, - "menu should have correct number of items"); - for (let i = 0; i < 2; i++) { - test.assertEqual(menu.items[i].label, "item " + i, - "item label should be correct"); - } - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Menu.removeItem() should work. -exports.testMenuRemoveItem = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let subitem = loader.cm.Item({ label: "item 1" }); - let menu = loader.cm.Menu({ - label: "menu", - items: [ - loader.cm.Item({ label: "item 0" }), - subitem, - loader.cm.Item({ label: "item 2" }) - ] - }); - - // Removing twice should be harmless. - menu.removeItem(subitem); - menu.removeItem(subitem); - - test.assertEqual(subitem.parentMenu, null, - "item's parent menu should be correct"); - - test.assertEqual(menu.items.length, 2, - "menu should have correct number of items"); - test.assertEqual(menu.items[0].label, "item 0", - "item label should be correct"); - test.assertEqual(menu.items[1].label, "item 2", - "item label should be correct"); - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Adding an item currently contained in one menu to another menu should work. -exports.testMenuItemSwap = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let subitem = loader.cm.Item({ label: "item" }); - let menu0 = loader.cm.Menu({ - label: "menu 0", - items: [subitem] - }); - let menu1 = loader.cm.Menu({ - label: "menu 1", - items: [] - }); - menu1.addItem(subitem); - - test.assertEqual(menu0.items.length, 0, - "menu should have correct number of items"); - - test.assertEqual(menu1.items.length, 1, - "menu should have correct number of items"); - test.assertEqual(menu1.items[0].label, "item", - "item label should be correct"); - - test.assertEqual(subitem.parentMenu, menu1, - "item's parent menu should be correct"); - - test.showMenu(null, function (popup) { - test.checkMenu([menu0, menu1], [], []); - test.done(); - }); -}; - - -// Destroying an item should remove it from its parent menu. -exports.testMenuItemDestroy = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let subitem = loader.cm.Item({ label: "item" }); - let menu = loader.cm.Menu({ - label: "menu", - items: [subitem] - }); - subitem.destroy(); - - test.assertEqual(menu.items.length, 0, - "menu should have correct number of items"); - test.assertEqual(subitem.parentMenu, null, - "item's parent menu should be correct"); - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Setting Menu.items should work. -exports.testMenuItemsSetter = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [ - loader.cm.Item({ label: "old item 0" }), - loader.cm.Item({ label: "old item 1" }) - ] - }); - menu.items = [ - loader.cm.Item({ label: "new item 0" }), - loader.cm.Item({ label: "new item 1" }), - loader.cm.Item({ label: "new item 2" }) - ]; - - test.assertEqual(menu.items.length, 3, - "menu should have correct number of items"); - for (let i = 0; i < 3; i++) { - test.assertEqual(menu.items[i].label, "new item " + i, - "item label should be correct"); - test.assertEqual(menu.items[i].parentMenu, menu, - "item's parent menu should be correct"); - } - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Setting Item.data should work. -exports.testItemDataSetter = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let item = loader.cm.Item({ label: "old item 0", data: "old" }); - item.data = "new"; - - test.assertEqual(item.data, "new", "item should have correct data"); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); -}; - - -// Open the test doc, load the module, make sure items appear when context- -// clicking the iframe. -exports.testAlreadyOpenIframe = function (test) { - test = new TestHelper(test); - test.withTestDoc(function (window, doc) { - let loader = test.newLoader(); - let item = new loader.cm.Item({ - label: "item" - }); - test.showMenu(doc.getElementById("iframe"), function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); -}; - - -// Test image support. -exports.testItemImage = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let imageURL = require("self").data.url("moz_favicon.ico"); - let item = new loader.cm.Item({ label: "item", image: imageURL }); - let menu = new loader.cm.Menu({ label: "menu", image: imageURL, items: [] }); - - test.showMenu(null, function (popup) { - test.checkMenu([item, menu], [], []); - - let imageURL2 = require("self").data.url("dummy.ico"); - item.image = imageURL2; - menu.image = imageURL2; - test.checkMenu([item, menu], [], []); - - item.image = null; - menu.image = null; - test.checkMenu([item, menu], [], []); - - test.done(); - }); -}; - - -// Menu.destroy should destroy the item tree rooted at that menu. -exports.testMenuDestroy = function (test) { - test = new TestHelper(test); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [ - loader.cm.Item({ label: "item 0" }), - loader.cm.Menu({ - label: "item 1", - items: [ - loader.cm.Item({ label: "subitem 0" }), - loader.cm.Item({ label: "subitem 1" }), - loader.cm.Item({ label: "subitem 2" }) - ] - }), - loader.cm.Item({ label: "item 2" }) - ] - }); - menu.destroy(); - - let numRegistryEntries = 0; - loader.globalScope.browserManager.browserWins.forEach(function (bwin) { - for (let itemID in bwin.items) - numRegistryEntries++; - }); - test.assertEqual(numRegistryEntries, 0, "All items should be unregistered."); - - test.showMenu(null, function (popup) { - test.checkMenu([], [], [menu]); - test.done(); - }); -}; - - -// NO TESTS BELOW THIS LINE! /////////////////////////////////////////////////// - -// Run only a dummy test if context-menu doesn't support the host app. -if (!require("xul-app").is("Firefox")) { - for (let [prop, val] in Iterator(exports)) - if (/^test/.test(prop) && typeof(val) === "function") - delete exports[prop]; - exports.testAppNotSupported = function (test) { - test.pass("context-menu does not support this application."); - }; -} - - -// This makes it easier to run tests by handling things like opening the menu, -// opening new windows, making assertions, etc. Methods on |test| can be called -// on instances of this class. Don't forget to call done() to end the test! -// WARNING: This looks up items in popups by comparing labels, so don't give two -// items the same label. -function TestHelper(test) { - // default waitUntilDone timeout is 10s, which is too short on the win7 - // buildslave - test.waitUntilDone(30*1000); - this.test = test; - this.loaders = []; - this.browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. - getService(Ci.nsIWindowMediator). - getMostRecentWindow("navigator:browser"); -} - -TestHelper.prototype = { - get contextMenuPopup() { - return this.browserWindow.document.getElementById("contentAreaContextMenu"); - }, - - get contextMenuSeparator() { - return this.browserWindow.document.getElementById(SEPARATOR_ID); - }, - - get overflowPopup() { - return this.browserWindow.document.getElementById(OVERFLOW_POPUP_ID); - }, - - get overflowSubmenu() { - return this.browserWindow.document.getElementById(OVERFLOW_MENU_ID); - }, - - get tabBrowser() { - return this.browserWindow.gBrowser; - }, - - // Methods on the wrapped test can be called on this object. - __noSuchMethod__: function (methodName, args) { - this.test[methodName].apply(this.test, args); - }, - - // Asserts that absentItems -- an array of items that should not match the - // current context -- aren't present in the menu. - checkAbsentItems: function (presentItems, absentItems) { - for (let i = 0; i < absentItems.length; i++) { - let item = absentItems[i]; - let elt = this.getItemElt(this.contextMenuPopup, item); - - // The implementation actually hides items rather than removing or not - // adding them in the first place, but that's an implementation detail. - this.test.assert(!elt || elt.hidden, - "Item should not be present in top-level menu"); - - if (this.shouldOverflow(presentItems)) { - elt = getItemElt(this.overflowPopup, item); - this.test.assert(!elt || elt.hidden, - "Item should not be present in overflow submenu"); - } - } - }, - - // Asserts that elt, a DOM element representing item, looks OK. - checkItemElt: function (elt, item) { - let itemType = this.getItemType(item); - - switch (itemType) { - case "Item": - this.test.assertEqual(elt.localName, "menuitem", - "Item DOM element should be a xul:menuitem"); - if (typeof(item.data) === "string") { - this.test.assertEqual(elt.getAttribute("value"), item.data, - "Item should have correct data"); - } - break - case "Menu": - this.test.assertEqual(elt.localName, "menu", - "Menu DOM element should be a xul:menu"); - let subPopup = elt.firstChild; - this.test.assert(subPopup, "xul:menu should have a child"); - this.test.assertEqual(subPopup.localName, "menupopup", - "xul:menu's first child should be a menupopup"); - break; - case "Separator": - this.test.assertEqual(elt.localName, "menuseparator", - "Separator DOM element should be a xul:menuseparator"); - break; - } - - if (itemType === "Item" || itemType === "Menu") { - this.test.assertEqual(elt.getAttribute("label"), item.label, - "Item should have correct title"); - if (typeof(item.image) === "string") - this.test.assertEqual(elt.getAttribute("image"), item.image, - "Item should have correct image"); - else - this.test.assert(!elt.hasAttribute("image"), - "Item should not have image"); - } - }, - - // Asserts that the context menu looks OK given the arguments. presentItems - // are items that should match the current context. absentItems are items - // that shouldn't. removedItems are items that have been removed from the - // menu. - checkMenu: function (presentItems, absentItems, removedItems) { - this.checkSeparator(presentItems); - this.checkOverflow(presentItems); - this.checkPresentItems(presentItems); - this.checkAbsentItems(presentItems, absentItems); - this.checkRemovedItems(removedItems); - this.checkSort(presentItems); - }, - - // Asserts that the overflow submenu is present or absent as appropriate for - // presentItems. - checkOverflow: function (presentItems) { - let submenu = this.overflowSubmenu; - if (this.shouldOverflow(presentItems)) { - this.test.assert(submenu && !submenu.hidden, - "Overflow submenu should be present"); - this.test.assert(submenu.localName, "menu", - "Overflow submenu should be a <menu>"); - let overflowPopup = this.overflowPopup; - this.test.assert(overflowPopup, - "Overflow submenu popup should be present"); - this.test.assert(overflowPopup.localName, "menupopup", - "Overflow submenu popup should be a <menupopup>"); - } - else { - this.test.assert(!submenu || submenu.hidden, - "Overflow submenu should be absent"); - } - }, - - // Asserts that the items that are present in the menu because they match the - // current context look OK. - checkPresentItems: function (presentItems) { - function recurse(popup, items, isTopLevel) { - items.forEach(function (item) { - let elt = this.getItemElt(popup, item); - - if (isTopLevel) { - if (this.shouldOverflow(items)) { - this.test.assert(!elt || elt.hidden, - "Item should not be present in top-level menu"); - - let overflowPopup = this.overflowPopup; - this.test.assert(overflowPopup, - "Overflow submenu should be present"); - - elt = this.getItemElt(overflowPopup, item); - this.test.assert(elt && !elt.hidden, - "Item should be present in overflow submenu"); - } - else { - this.test.assert(elt && !elt.hidden, - "Item should be present in top-level menu"); - } - } - else { - this.test.assert(elt && !elt.hidden, - "Item should be present in menu"); - } - - this.checkItemElt(elt, item); - if (this.getItemType(item) === "Menu") - recurse.call(this, elt.firstChild, item.items, false); - }, this); - } - - recurse.call(this, this.contextMenuPopup, presentItems, true); - }, - - // Asserts that items that have been removed from the menu are really removed. - checkRemovedItems: function (removedItems) { - for (let i = 0; i < removedItems.length; i++) { - let item = removedItems[i]; - - let elt = this.getItemElt(this.contextMenuPopup, item); - this.test.assert(!elt, "Item should be removed from top-level menu"); - - let overflowPopup = this.overflowPopup; - if (overflowPopup) { - elt = this.getItemElt(overflowPopup, item); - this.test.assert(!elt, "Item should be removed from overflow submenu"); - } - } - }, - - // Asserts that the menu separator separating standard items from our items - // looks OK. - checkSeparator: function (presentItems) { - let sep = this.contextMenuSeparator; - if (presentItems.length) { - this.test.assert(sep && !sep.hidden, "Menu separator should be present"); - this.test.assertEqual(sep.localName, "menuseparator", - "Menu separator should be a <menuseparator>"); - } - else { - this.test.assert(!sep || sep.hidden, "Menu separator should be absent"); - } - }, - - // Asserts that our items are sorted. - checkSort: function (presentItems) { - // Get the first item in sorted order, get its elt, walk the nextSibling - // chain, making sure each is greater than the previous. - if (presentItems.length) { - let sorted = presentItems.slice(0). - sort(function (a, b) a.label.localeCompare(b.label)); - let elt = this.shouldOverflow(presentItems) ? - this.getItemElt(this.overflowPopup, sorted[0]) : - this.getItemElt(this.contextMenuPopup, sorted[0]); - let numElts = 1; - while (elt.nextSibling && - elt.nextSibling.className.split(/\s+/).indexOf(ITEM_CLASS) >= 0) { - let eltLabel = elt.getAttribute("label"); - let nextLabel = elt.nextSibling.getAttribute("label"); - this.test.assert(eltLabel.localeCompare(nextLabel) < 0, - "Item label should be < next item's label"); - elt = elt.nextSibling; - numElts++; - } - this.test.assertEqual(numElts, presentItems.length, - "The first item in sorted order should have the " + - "first element in sorted order"); - } - }, - - // Attaches an event listener to node. The listener is automatically removed - // when it's fired (so it's assumed it will fire), and callback is called - // after a short delay. Since the module we're testing relies on the same - // event listeners to do its work, this is to give them a little breathing - // room before callback runs. Inside callback |this| is this object. - delayedEventListener: function (node, event, callback, useCapture) { - const self = this; - node.addEventListener(event, function handler(evt) { - node.removeEventListener(event, handler, useCapture); - require("timer").setTimeout(function () { - try { - callback.call(self, evt); - } - catch (err) { - self.test.exception(err); - self.test.done(); - } - }, 20); - }, useCapture); - }, - - // Call to finish the test. - done: function () { - function commonDone() { - if (this.tab) { - this.tabBrowser.removeTab(this.tab); - this.tabBrowser.selectedTab = this.oldSelectedTab; - } - while (this.loaders.length) { - let browserManager = this.loaders[0].globalScope.browserManager; - let topLevelItems = browserManager.topLevelItems.slice(); - let privatePropsKey = this.loaders[0].globalScope.PRIVATE_PROPS_KEY; - let workerRegs = topLevelItems.map(function (item) { - return item.valueOf(privatePropsKey)._workerReg; - }); - - this.loaders[0].unload(); - - // Make sure the browser manager is cleaned up. - this.test.assertEqual(browserManager.browserWins.length, 0, - "browserManager should have no windows left"); - this.test.assertEqual(browserManager.topLevelItems.length, 0, - "browserManager should have no items left"); - this.test.assert(!("contentWins" in browserManager), - "browserManager should have no content windows left"); - - // Make sure the items' worker registries are cleaned up. - topLevelItems.forEach(function (item) { - this.test.assert(!("_workerReg" in item.valueOf(privatePropsKey)), - "item's worker registry should be removed"); - }, this); - workerRegs.forEach(function (workerReg) { - this.test.assertEqual(Object.keys(workerReg.winWorkers).length, 0, - "worker registry should be empty"); - this.test.assertEqual( - Object.keys(workerReg.winsWithoutWorkers).length, 0, - "worker registry list of windows without workers should be empty"); - }, this); - } - this.test.done(); - } - - function closeBrowserWindow() { - if (this.oldBrowserWindow) { - this.delayedEventListener(this.browserWindow, "unload", commonDone, - false); - this.browserWindow.close(); - this.browserWindow = this.oldBrowserWindow; - delete this.oldBrowserWindow; - } - else { - commonDone.call(this); - } - }; - - if (this.contextMenuPopup.state == "closed") { - closeBrowserWindow.call(this); - } - else { - this.delayedEventListener(this.contextMenuPopup, "popuphidden", - function () closeBrowserWindow.call(this), - false); - this.contextMenuPopup.hidePopup(); - } - }, - - // Returns the DOM element in popup corresponding to item. - // WARNING: The element is found by comparing labels, so don't give two items - // the same label. - getItemElt: function (popup, item) { - let nodes = popup.childNodes; - for (let i = nodes.length - 1; i >= 0; i--) { - if (this.getItemType(item) === "Separator") { - if (nodes[i].localName === "menuseparator") - return nodes[i]; - } - else if (nodes[i].getAttribute("label") === item.label) - return nodes[i]; - } - return null; - }, - - // Returns "Item", "Menu", or "Separator". - getItemType: function (item) { - // Could use instanceof here, but that would require accessing the loader - // that created the item, and I don't want to A) somehow search through the - // this.loaders list to find it, and B) assume there are any live loaders at - // all. - return /^\[object (Item|Menu|Separator)/.exec(item.toString())[1]; - }, - - // Returns a wrapper around a new loader: { loader, cm, unload, globalScope }. - // loader is a Cuddlefish sandboxed loader, cm is the context menu module, - // globalScope is the context menu module's global scope, and unload is a - // function that unloads the loader and associated resources. - newLoader: function () { - const self = this; - let loader = Loader(module); - let wrapper = { - loader: loader, - cm: loader.require("context-menu"), - globalScope: loader.sandbox("context-menu"), - unload: function () { - loader.unload(); - let idx = self.loaders.indexOf(wrapper); - if (idx < 0) - throw new Error("Test error: tried to unload nonexistent loader"); - self.loaders.splice(idx, 1); - } - }; - this.loaders.push(wrapper); - return wrapper; - }, - - // Returns true if the number of presentItems crosses the overflow threshold. - shouldOverflow: function (presentItems) { - return presentItems.length > - (this.loaders.length ? - this.loaders[0].loader.require("preferences-service"). - get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) : - OVERFLOW_THRESH_DEFAULT); - }, - - // Opens the context menu on the current page. If targetNode is null, the - // menu is opened in the top-left corner. onShowncallback is passed the - // popup. - showMenu: function(targetNode, onshownCallback) { - function sendEvent() { - this.delayedEventListener(this.browserWindow, "popupshowing", - function (e) { - let popup = e.target; - onshownCallback.call(this, popup); - }, false); - - let rect = targetNode ? - targetNode.getBoundingClientRect() : - { left: 0, top: 0, width: 0, height: 0 }; - let contentWin = this.browserWindow.content; - contentWin. - QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils). - sendMouseEvent("contextmenu", - rect.left + (rect.width / 2), - rect.top + (rect.height / 2), - 2, 1, 0); - } - - // If a new tab or window has not yet been opened, open a new tab now. For - // some reason using the tab already opened when the test starts causes - // leaks. See bug 566351 for details. - if (!targetNode && !this.oldSelectedTab && !this.oldBrowserWindow) { - this.oldSelectedTab = this.tabBrowser.selectedTab; - this.tab = this.tabBrowser.addTab("about:blank"); - let browser = this.tabBrowser.getBrowserForTab(this.tab); - - this.delayedEventListener(browser, "load", function () { - this.tabBrowser.selectedTab = this.tab; - sendEvent.call(this); - }, true); - } - else - sendEvent.call(this); - }, - - // Opens a new browser window. The window will be closed automatically when - // done() is called. - withNewWindow: function (onloadCallback) { - let win = this.browserWindow.OpenBrowserWindow(); - this.delayedEventListener(win, "load", onloadCallback, true); - this.oldBrowserWindow = this.browserWindow; - this.browserWindow = win; - }, - - // Opens a new tab with our test page in the current window. The tab will - // be closed automatically when done() is called. - withTestDoc: function (onloadCallback) { - this.oldSelectedTab = this.tabBrowser.selectedTab; - this.tab = this.tabBrowser.addTab(TEST_DOC_URL); - let browser = this.tabBrowser.getBrowserForTab(this.tab); - - this.delayedEventListener(browser, "load", function () { - this.tabBrowser.selectedTab = this.tab; - onloadCallback.call(this, browser.contentWindow, browser.contentDocument); - }, true); - } -}; |