michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: 'use strict'; michael@0: michael@0: let { Cc, Ci } = require("chrome"); michael@0: michael@0: require("sdk/context-menu"); michael@0: michael@0: const { Loader } = require('sdk/test/loader'); michael@0: const timer = require("sdk/timers"); michael@0: const { merge } = require("sdk/util/object"); michael@0: michael@0: // These should match the same constants in the module. michael@0: const ITEM_CLASS = "addon-context-menu-item"; michael@0: const SEPARATOR_CLASS = "addon-context-menu-separator"; michael@0: const OVERFLOW_THRESH_DEFAULT = 10; michael@0: const OVERFLOW_THRESH_PREF = michael@0: "extensions.addon-sdk.context-menu.overflowThreshold"; michael@0: const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu"; michael@0: const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup"; michael@0: michael@0: const TEST_DOC_URL = module.uri.replace(/\.js$/, ".html"); michael@0: const data = require("./fixtures"); michael@0: michael@0: // Tests that when present the separator is placed before the separator from michael@0: // the old context-menu module michael@0: exports.testSeparatorPosition = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: // Create the old separator michael@0: let oldSeparator = test.contextMenuPopup.ownerDocument.createElement("menuseparator"); michael@0: oldSeparator.id = "jetpack-context-menu-separator"; michael@0: test.contextMenuPopup.appendChild(oldSeparator); michael@0: michael@0: // Create an item. michael@0: let item = new loader.cm.Item({ label: "item" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: assert.equal(test.contextMenuSeparator.nextSibling.nextSibling, oldSeparator, michael@0: "New separator should appear before the old one"); michael@0: test.contextMenuPopup.removeChild(oldSeparator); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: // Destroying items that were previously created should cause them to be absent michael@0: // from the menu. michael@0: exports.testConstructDestroy = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: // Create an item. michael@0: let item = new loader.cm.Item({ label: "item" }); michael@0: assert.equal(item.parentMenu, loader.cm.contentContextMenu, michael@0: "item's parent menu should be correct"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // It should be present when the menu is shown. michael@0: test.checkMenu([item], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Destroy the item. Multiple destroys should be harmless. michael@0: item.destroy(); michael@0: item.destroy(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // It should be removed from the menu. michael@0: test.checkMenu([item], [], [item]); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Destroying an item twice should not cause an error. michael@0: exports.testDestroyTwice = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ label: "item" }); michael@0: item.destroy(); michael@0: item.destroy(); michael@0: michael@0: test.pass("Destroying an item twice should not cause an error."); michael@0: test.done(); michael@0: }; michael@0: michael@0: michael@0: // CSS selector contexts should cause their items to be present in the menu michael@0: // when the menu is invoked on nodes that match the selectors. michael@0: exports.testSelectorContextMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: data: "item", michael@0: context: loader.cm.SelectorContext("img") michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // CSS selector contexts should cause their items to be present in the menu michael@0: // when the menu is invoked on nodes that have ancestors that match the michael@0: // selectors. michael@0: exports.testSelectorAncestorContextMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: data: "item", michael@0: context: loader.cm.SelectorContext("a[href]") michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("span-link"), function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // CSS selector contexts should cause their items to be absent from the menu michael@0: // when the menu is not invoked on nodes that match or have ancestors that michael@0: // match the selectors. michael@0: exports.testSelectorContextNoMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: data: "item", michael@0: context: loader.cm.SelectorContext("img") michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Page contexts should cause their items to be present in the menu when the michael@0: // menu is not invoked on an active element. michael@0: exports.testPageContextMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: new loader.cm.Item({ michael@0: label: "item 0" michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 1", michael@0: context: undefined michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 2", michael@0: context: loader.cm.PageContext() michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 3", michael@0: context: [loader.cm.PageContext()] michael@0: }) michael@0: ]; michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Page contexts should cause their items to be absent from the menu when the michael@0: // menu is invoked on an active element. michael@0: exports.testPageContextNoMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: new loader.cm.Item({ michael@0: label: "item 0" michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 1", michael@0: context: undefined michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 2", michael@0: context: loader.cm.PageContext() michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 3", michael@0: context: [loader.cm.PageContext()] michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu(items, items, []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Selection contexts should cause items to appear when a selection exists. michael@0: exports.testSelectionContextMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.SelectionContext() michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: window.getSelection().selectAllChildren(doc.body); michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Selection contexts should cause items to appear when a selection exists in michael@0: // a text field. michael@0: exports.testSelectionContextMatchInTextField = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.SelectionContext() michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: let textfield = doc.getElementById("textfield"); michael@0: textfield.setSelectionRange(0, textfield.value.length); michael@0: test.showMenu(textfield, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Selection contexts should not cause items to appear when a selection does michael@0: // not exist in a text field. michael@0: exports.testSelectionContextNoMatchInTextField = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.SelectionContext() michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: let textfield = doc.getElementById("textfield"); michael@0: textfield.setSelectionRange(0, 0); michael@0: test.showMenu(textfield, function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Selection contexts should not cause items to appear when a selection does michael@0: // not exist. michael@0: exports.testSelectionContextNoMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.SelectionContext() michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Selection contexts should cause items to appear when a selection exists even michael@0: // for newly opened pages michael@0: exports.testSelectionContextInNewTab = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.SelectionContext() michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: let link = doc.getElementById("targetlink"); michael@0: link.click(); michael@0: michael@0: test.delayedEventListener(this.tabBrowser, "load", function () { michael@0: let browser = test.tabBrowser.selectedBrowser; michael@0: let window = browser.contentWindow; michael@0: let doc = browser.contentDocument; michael@0: window.getSelection().selectAllChildren(doc.body); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: test.tabBrowser.removeTab(test.tabBrowser.selectedTab); michael@0: test.tabBrowser.selectedTab = test.tab; michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }, true); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Selection contexts should work when right clicking a form button michael@0: exports.testSelectionContextButtonMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.SelectionContext() michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: window.getSelection().selectAllChildren(doc.body); michael@0: let button = doc.getElementById("button"); michael@0: test.showMenu(button, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: //Selection contexts should work when right clicking a form button michael@0: exports.testSelectionContextButtonNoMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.SelectionContext() michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: let button = doc.getElementById("button"); michael@0: test.showMenu(button, function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // URL contexts should cause items to appear on pages that match. michael@0: exports.testURLContextMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: loader.cm.Item({ michael@0: label: "item 0", michael@0: context: loader.cm.URLContext(TEST_DOC_URL) michael@0: }), michael@0: loader.cm.Item({ michael@0: label: "item 1", michael@0: context: loader.cm.URLContext([TEST_DOC_URL, "*.bogus.com"]) michael@0: }), michael@0: loader.cm.Item({ michael@0: label: "item 2", michael@0: context: loader.cm.URLContext([new RegExp(".*\\.html")]) michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // URL contexts should not cause items to appear on pages that do not match. michael@0: exports.testURLContextNoMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: loader.cm.Item({ michael@0: label: "item 0", michael@0: context: loader.cm.URLContext("*.bogus.com") michael@0: }), michael@0: loader.cm.Item({ michael@0: label: "item 1", michael@0: context: loader.cm.URLContext(["*.bogus.com", "*.gnarly.com"]) michael@0: }), michael@0: loader.cm.Item({ michael@0: label: "item 2", michael@0: context: loader.cm.URLContext([new RegExp(".*\\.js")]) michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, items, []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Removing a non-matching URL context after its item is created and the page is michael@0: // loaded should cause the item's content script to be evaluated when the michael@0: // context menu is next opened. michael@0: exports.testURLContextRemove = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let shouldBeEvaled = false; michael@0: let context = loader.cm.URLContext("*.bogus.com"); michael@0: let item = loader.cm.Item({ michael@0: label: "item", michael@0: context: context, michael@0: contentScript: 'self.postMessage("ok"); self.on("context", function () true);', michael@0: onMessage: function (msg) { michael@0: assert.ok(shouldBeEvaled, michael@0: "content script should be evaluated when expected"); michael@0: assert.equal(msg, "ok", "Should have received the right message"); michael@0: shouldBeEvaled = false; michael@0: } michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: michael@0: item.context.remove(context); michael@0: michael@0: shouldBeEvaled = true; michael@0: michael@0: test.hideMenu(function () { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: michael@0: assert.ok(!shouldBeEvaled, michael@0: "content script should have been evaluated"); michael@0: michael@0: test.hideMenu(function () { michael@0: // Shouldn't get evaluated again michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Loading a new page in the same tab should correctly start a new worker for michael@0: // any content scripts michael@0: exports.testPageReload = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "Item", michael@0: contentScript: "var doc = document; self.on('context', function(node) doc.body.getAttribute('showItem') == 'true');" michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: // Set a flag on the document that the item uses michael@0: doc.body.setAttribute("showItem", "true"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // With the attribute true the item should be visible in the menu michael@0: test.checkMenu([item], [], []); michael@0: test.hideMenu(function() { michael@0: let browser = this.tabBrowser.getBrowserForTab(this.tab) michael@0: test.delayedEventListener(browser, "load", function() { michael@0: test.delayedEventListener(browser, "load", function() { michael@0: window = browser.contentWindow; michael@0: doc = window.document; michael@0: michael@0: // Set a flag on the document that the item uses michael@0: doc.body.setAttribute("showItem", "false"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // In the new document with the attribute false the item should be michael@0: // hidden, but if the contentScript hasn't been reloaded it will michael@0: // still see the old value michael@0: test.checkMenu([item], [item], []); michael@0: michael@0: test.done(); michael@0: }); michael@0: }, true); michael@0: browser.loadURI(TEST_DOC_URL, null, null); michael@0: }, true); michael@0: // Required to make sure we load a new page in history rather than michael@0: // just reloading the current page which would unload it michael@0: browser.loadURI("about:blank", null, null); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Closing a page after it's been used with a worker should cause the worker michael@0: // to be destroyed michael@0: /*exports.testWorkerDestroy = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let loadExpected = false; michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.postMessage("loaded"); self.on("detach", function () { console.log("saw detach"); self.postMessage("detach") });', michael@0: onMessage: function (msg) { michael@0: switch (msg) { michael@0: case "loaded": michael@0: assert.ok(loadExpected, "Should have seen the load event at the right time"); michael@0: loadExpected = false; michael@0: break; michael@0: case "detach": michael@0: test.done(); michael@0: break; michael@0: } michael@0: } michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: loadExpected = true; michael@0: test.showMenu(null, function (popup) { michael@0: assert.ok(!loadExpected, "Should have seen a message"); michael@0: michael@0: test.checkMenu([item], [], []); michael@0: michael@0: test.closeTab(); michael@0: }); michael@0: }); michael@0: };*/ michael@0: michael@0: michael@0: // Content contexts that return true should cause their items to be present michael@0: // in the menu. michael@0: exports.testContentContextMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function () true);' michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Content contexts that return false should cause their items to be absent michael@0: // from the menu. michael@0: exports.testContentContextNoMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function () false);' michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Content contexts that return undefined should cause their items to be absent michael@0: // from the menu. michael@0: exports.testContentContextUndefined = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function () {});' michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Content contexts that return an empty string should cause their items to be michael@0: // absent from the menu and shouldn't wipe the label michael@0: exports.testContentContextEmptyString = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function () "");' michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: assert.equal(item.label, "item", "Label should still be correct"); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // If any content contexts returns true then their items should be present in michael@0: // the menu. michael@0: exports.testMultipleContentContextMatch1 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function () true); ' + michael@0: 'self.on("context", function () false);', michael@0: onMessage: function() { michael@0: test.fail("Should not have called the second context listener"); michael@0: } michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // If any content contexts returns true then their items should be present in michael@0: // the menu. michael@0: exports.testMultipleContentContextMatch2 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function () false); ' + michael@0: 'self.on("context", function () true);' michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // If any content contexts returns a string then their items should be present michael@0: // in the menu. michael@0: exports.testMultipleContentContextString1 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function () "new label"); ' + michael@0: 'self.on("context", function () false);' michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: assert.equal(item.label, "new label", "Label should have changed"); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // If any content contexts returns a string then their items should be present michael@0: // in the menu. michael@0: exports.testMultipleContentContextString2 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function () false); ' + michael@0: 'self.on("context", function () "new label");' michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: assert.equal(item.label, "new label", "Label should have changed"); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // If many content contexts returns a string then the first should take effect michael@0: exports.testMultipleContentContextString3 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function () "new label 1"); ' + michael@0: 'self.on("context", function () "new label 2");' michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: assert.equal(item.label, "new label 1", "Label should have changed"); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Content contexts that return true should cause their items to be present michael@0: // in the menu when context clicking an active element. michael@0: exports.testContentContextMatchActiveElement = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: new loader.cm.Item({ michael@0: label: "item 1", michael@0: contentScript: 'self.on("context", function () true);' michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 2", michael@0: context: undefined, michael@0: contentScript: 'self.on("context", function () true);' michael@0: }), michael@0: // These items will always be hidden by the declarative usage of PageContext michael@0: new loader.cm.Item({ michael@0: label: "item 3", michael@0: context: loader.cm.PageContext(), michael@0: contentScript: 'self.on("context", function () true);' michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 4", michael@0: context: [loader.cm.PageContext()], michael@0: contentScript: 'self.on("context", function () true);' michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu(items, [items[2], items[3]], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Content contexts that return false should cause their items to be absent michael@0: // from the menu when context clicking an active element. michael@0: exports.testContentContextNoMatchActiveElement = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: new loader.cm.Item({ michael@0: label: "item 1", michael@0: contentScript: 'self.on("context", function () false);' michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 2", michael@0: context: undefined, michael@0: contentScript: 'self.on("context", function () false);' michael@0: }), michael@0: // These items will always be hidden by the declarative usage of PageContext michael@0: new loader.cm.Item({ michael@0: label: "item 3", michael@0: context: loader.cm.PageContext(), michael@0: contentScript: 'self.on("context", function () false);' michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 4", michael@0: context: [loader.cm.PageContext()], michael@0: contentScript: 'self.on("context", function () false);' michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu(items, items, []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Content contexts that return undefined should cause their items to be absent michael@0: // from the menu when context clicking an active element. michael@0: exports.testContentContextNoMatchActiveElement = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: new loader.cm.Item({ michael@0: label: "item 1", michael@0: contentScript: 'self.on("context", function () {});' michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 2", michael@0: context: undefined, michael@0: contentScript: 'self.on("context", function () {});' michael@0: }), michael@0: // These items will always be hidden by the declarative usage of PageContext michael@0: new loader.cm.Item({ michael@0: label: "item 3", michael@0: context: loader.cm.PageContext(), michael@0: contentScript: 'self.on("context", function () {});' michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 4", michael@0: context: [loader.cm.PageContext()], michael@0: contentScript: 'self.on("context", function () {});' michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu(items, items, []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Content contexts that return a string should cause their items to be present michael@0: // in the menu and the items' labels to be updated. michael@0: exports.testContentContextMatchString = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "first label", michael@0: contentScript: 'self.on("context", function () "second label");' michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: assert.equal(item.label, "second label", michael@0: "item's label should be updated"); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Ensure that contentScriptFile is working correctly michael@0: exports.testContentScriptFile = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: // Reject remote files michael@0: assert.throws(function() { michael@0: new loader.cm.Item({ michael@0: label: "item", michael@0: contentScriptFile: "http://mozilla.com/context-menu.js" michael@0: }); michael@0: }, michael@0: new RegExp("The 'contentScriptFile' option must be a local file URL " + michael@0: "or an array of local file URLs."), michael@0: "Item throws when contentScriptFile is a remote URL"); michael@0: michael@0: // But accept files from data folder michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScriptFile: data.url("test-context-menu.js") michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // The args passed to context listeners should be correct. michael@0: exports.testContentContextArgs = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: let callbacks = 0; michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'self.on("context", function (node) {' + michael@0: ' self.postMessage(node.tagName);' + michael@0: ' return false;' + michael@0: '});', michael@0: onMessage: function (tagName) { michael@0: assert.equal(tagName, "HTML", "node should be an HTML element"); michael@0: if (++callbacks == 2) test.done(); michael@0: } michael@0: }); michael@0: michael@0: test.showMenu(null, function () { michael@0: if (++callbacks == 2) test.done(); michael@0: }); michael@0: }; michael@0: michael@0: // Multiple contexts imply intersection, not union, and content context michael@0: // listeners should not be called if all declarative contexts are not current. michael@0: exports.testMultipleContexts = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()], michael@0: contentScript: 'self.on("context", function () self.postMessage());', michael@0: onMessage: function () { michael@0: test.fail("Context listener should not be called"); michael@0: } michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("span-link"), function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Once a context is removed, it should no longer cause its item to appear. michael@0: exports.testRemoveContext = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let ctxt = loader.cm.SelectorContext("img"); michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: context: ctxt michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: michael@0: // The item should be present at first. michael@0: test.checkMenu([item], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Remove the img context and check again. michael@0: item.context.remove(ctxt); michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu([item], [item], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Lots of items should overflow into the overflow submenu. michael@0: exports.testOverflow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = []; michael@0: for (let i = 0; i < OVERFLOW_THRESH_DEFAULT + 1; i++) { michael@0: let item = new loader.cm.Item({ label: "item " + i }); michael@0: items.push(item); michael@0: } michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Module unload should cause all items to be removed. michael@0: exports.testUnload = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ label: "item" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain the item. michael@0: test.checkMenu([item], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload the module. michael@0: loader.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The item should be removed from the menu. michael@0: test.checkMenu([item], [], [item]); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Using multiple module instances to add items without causing overflow should michael@0: // work OK. Assumes OVERFLOW_THRESH_DEFAULT >= 2. michael@0: exports.testMultipleModulesAdd = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: // Use each module to add an item, then unload each module in turn. michael@0: let item0 = new loader0.cm.Item({ label: "item 0" }); michael@0: let item1 = new loader1.cm.Item({ label: "item 1" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain both items. michael@0: test.checkMenu([item0, item1], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload the first module. michael@0: loader0.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The first item should be removed from the menu. michael@0: test.checkMenu([item0, item1], [], [item0]); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload the second module. michael@0: loader1.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // Both items should be removed from the menu. michael@0: test.checkMenu([item0, item1], [], [item0, item1]); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Using multiple module instances to add items causing overflow should work OK. michael@0: exports.testMultipleModulesAddOverflow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: // Use module 0 to add OVERFLOW_THRESH_DEFAULT items. michael@0: let items0 = []; michael@0: for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { michael@0: let item = new loader0.cm.Item({ label: "item 0 " + i }); michael@0: items0.push(item); michael@0: } michael@0: michael@0: // Use module 1 to add one item. michael@0: let item1 = new loader1.cm.Item({ label: "item 1" }); michael@0: michael@0: let allItems = items0.concat(item1); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain all items in overflow. michael@0: test.checkMenu(allItems, [], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload the first module. michael@0: loader0.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The first items should be removed from the menu, which should not michael@0: // overflow. michael@0: test.checkMenu(allItems, [], items0); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload the second module. michael@0: loader1.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // All items should be removed from the menu. michael@0: test.checkMenu(allItems, [], allItems); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Using multiple module instances to modify the menu without causing overflow michael@0: // should work OK. This test creates two loaders and: michael@0: // loader0 create item -> loader1 create item -> loader0.unload -> michael@0: // loader1.unload michael@0: exports.testMultipleModulesDiffContexts1 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let item0 = new loader0.cm.Item({ michael@0: label: "item 0", michael@0: context: loader0.cm.SelectorContext("img") michael@0: }); michael@0: michael@0: let item1 = new loader1.cm.Item({ label: "item 1" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain item1. michael@0: test.checkMenu([item0, item1], [item0], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload module 0. michael@0: loader0.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // item0 should be removed from the menu. michael@0: test.checkMenu([item0, item1], [], [item0]); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload module 1. michael@0: loader1.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // Both items should be removed from the menu. michael@0: test.checkMenu([item0, item1], [], [item0, item1]); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Using multiple module instances to modify the menu without causing overflow michael@0: // should work OK. This test creates two loaders and: michael@0: // loader1 create item -> loader0 create item -> loader0.unload -> michael@0: // loader1.unload michael@0: exports.testMultipleModulesDiffContexts2 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let item1 = new loader1.cm.Item({ label: "item 1" }); michael@0: michael@0: let item0 = new loader0.cm.Item({ michael@0: label: "item 0", michael@0: context: loader0.cm.SelectorContext("img") michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain item1. michael@0: test.checkMenu([item0, item1], [item0], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload module 0. michael@0: loader0.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // item0 should be removed from the menu. michael@0: test.checkMenu([item0, item1], [], [item0]); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload module 1. michael@0: loader1.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // Both items should be removed from the menu. michael@0: test.checkMenu([item0, item1], [], [item0, item1]); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Using multiple module instances to modify the menu without causing overflow michael@0: // should work OK. This test creates two loaders and: michael@0: // loader0 create item -> loader1 create item -> loader1.unload -> michael@0: // loader0.unload michael@0: exports.testMultipleModulesDiffContexts3 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let item0 = new loader0.cm.Item({ michael@0: label: "item 0", michael@0: context: loader0.cm.SelectorContext("img") michael@0: }); michael@0: michael@0: let item1 = new loader1.cm.Item({ label: "item 1" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain item1. michael@0: test.checkMenu([item0, item1], [item0], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload module 1. michael@0: loader1.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // item1 should be removed from the menu. michael@0: test.checkMenu([item0, item1], [item0], [item1]); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload module 0. michael@0: loader0.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // Both items should be removed from the menu. michael@0: test.checkMenu([item0, item1], [], [item0, item1]); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Using multiple module instances to modify the menu without causing overflow michael@0: // should work OK. This test creates two loaders and: michael@0: // loader1 create item -> loader0 create item -> loader1.unload -> michael@0: // loader0.unload michael@0: exports.testMultipleModulesDiffContexts4 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let item1 = new loader1.cm.Item({ label: "item 1" }); michael@0: michael@0: let item0 = new loader0.cm.Item({ michael@0: label: "item 0", michael@0: context: loader0.cm.SelectorContext("img") michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain item1. michael@0: test.checkMenu([item0, item1], [item0], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload module 1. michael@0: loader1.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // item1 should be removed from the menu. michael@0: test.checkMenu([item0, item1], [item0], [item1]); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload module 0. michael@0: loader0.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // Both items should be removed from the menu. michael@0: test.checkMenu([item0, item1], [], [item0, item1]); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Test interactions between a loaded module, unloading another module, and the michael@0: // menu separator and overflow submenu. michael@0: exports.testMultipleModulesAddRemove = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let item = new loader0.cm.Item({ label: "item" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain the item. michael@0: test.checkMenu([item], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: // Remove the item. michael@0: item.destroy(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The item should be removed from the menu. michael@0: test.checkMenu([item], [], [item]); michael@0: popup.hidePopup(); michael@0: michael@0: // Unload module 1. michael@0: loader1.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // There shouldn't be any errors involving the menu separator or michael@0: // overflow submenu. michael@0: test.checkMenu([item], [], [item]); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Checks that the order of menu items is correct when adding/removing across michael@0: // multiple modules. All items from a single module should remain in a group michael@0: exports.testMultipleModulesOrder = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: // Use each module to add an item, then unload each module in turn. michael@0: let item0 = new loader0.cm.Item({ label: "item 0" }); michael@0: let item1 = new loader1.cm.Item({ label: "item 1" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain both items. michael@0: test.checkMenu([item0, item1], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: let item2 = new loader0.cm.Item({ label: "item 2" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The new item should be grouped with the same items from loader0. michael@0: test.checkMenu([item0, item2, item1], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: let item3 = new loader1.cm.Item({ label: "item 3" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // Same again michael@0: test.checkMenu([item0, item2, item1, item3], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Checks that the order of menu items is correct when adding/removing across michael@0: // multiple modules when overflowing. All items from a single module should michael@0: // remain in a group michael@0: exports.testMultipleModulesOrderOverflow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let prefs = loader0.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 0); michael@0: michael@0: // Use each module to add an item, then unload each module in turn. michael@0: let item0 = new loader0.cm.Item({ label: "item 0" }); michael@0: let item1 = new loader1.cm.Item({ label: "item 1" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The menu should contain both items. michael@0: test.checkMenu([item0, item1], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: let item2 = new loader0.cm.Item({ label: "item 2" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // The new item should be grouped with the same items from loader0. michael@0: test.checkMenu([item0, item2, item1], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: let item3 = new loader1.cm.Item({ label: "item 3" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // Same again michael@0: test.checkMenu([item0, item2, item1, item3], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Checks that if a module's items are all hidden then the overflow menu doesn't michael@0: // get hidden michael@0: exports.testMultipleModulesOverflowHidden = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let prefs = loader0.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 0); michael@0: michael@0: // Use each module to add an item, then unload each module in turn. michael@0: let item0 = new loader0.cm.Item({ label: "item 0" }); michael@0: let item1 = new loader1.cm.Item({ michael@0: label: "item 1", michael@0: context: loader1.cm.SelectorContext("a") michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // One should be hidden michael@0: test.checkMenu([item0, item1], [item1], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Checks that if a module's items are all hidden then the overflow menu doesn't michael@0: // get hidden (reverse order to above) michael@0: exports.testMultipleModulesOverflowHidden2 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let prefs = loader0.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 0); michael@0: michael@0: // Use each module to add an item, then unload each module in turn. michael@0: let item0 = new loader0.cm.Item({ michael@0: label: "item 0", michael@0: context: loader0.cm.SelectorContext("a") michael@0: }); michael@0: let item1 = new loader1.cm.Item({ label: "item 1" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // One should be hidden michael@0: test.checkMenu([item0, item1], [item0], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Checks that we don't overflow if there are more items than the overflow michael@0: // threshold but not all of them are visible michael@0: exports.testOverflowIgnoresHidden = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let prefs = loader.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 2); michael@0: michael@0: let allItems = [ michael@0: new loader.cm.Item({ michael@0: label: "item 0" michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 1" michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 2", michael@0: context: loader.cm.SelectorContext("a") michael@0: }) michael@0: ]; michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // One should be hidden michael@0: test.checkMenu(allItems, [allItems[2]], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Checks that we don't overflow if there are more items than the overflow michael@0: // threshold but not all of them are visible michael@0: exports.testOverflowIgnoresHiddenMultipleModules1 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let prefs = loader0.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 2); michael@0: michael@0: let allItems = [ michael@0: new loader0.cm.Item({ michael@0: label: "item 0" michael@0: }), michael@0: new loader0.cm.Item({ michael@0: label: "item 1" michael@0: }), michael@0: new loader1.cm.Item({ michael@0: label: "item 2", michael@0: context: loader1.cm.SelectorContext("a") michael@0: }), michael@0: new loader1.cm.Item({ michael@0: label: "item 3", michael@0: context: loader1.cm.SelectorContext("a") michael@0: }) michael@0: ]; michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // One should be hidden michael@0: test.checkMenu(allItems, [allItems[2], allItems[3]], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Checks that we don't overflow if there are more items than the overflow michael@0: // threshold but not all of them are visible michael@0: exports.testOverflowIgnoresHiddenMultipleModules2 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let prefs = loader0.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 2); michael@0: michael@0: let allItems = [ michael@0: new loader0.cm.Item({ michael@0: label: "item 0" michael@0: }), michael@0: new loader0.cm.Item({ michael@0: label: "item 1", michael@0: context: loader0.cm.SelectorContext("a") michael@0: }), michael@0: new loader1.cm.Item({ michael@0: label: "item 2" michael@0: }), michael@0: new loader1.cm.Item({ michael@0: label: "item 3", michael@0: context: loader1.cm.SelectorContext("a") michael@0: }) michael@0: ]; michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // One should be hidden michael@0: test.checkMenu(allItems, [allItems[1], allItems[3]], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Checks that we don't overflow if there are more items than the overflow michael@0: // threshold but not all of them are visible michael@0: exports.testOverflowIgnoresHiddenMultipleModules3 = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let prefs = loader0.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 2); michael@0: michael@0: let allItems = [ michael@0: new loader0.cm.Item({ michael@0: label: "item 0", michael@0: context: loader0.cm.SelectorContext("a") michael@0: }), michael@0: new loader0.cm.Item({ michael@0: label: "item 1", michael@0: context: loader0.cm.SelectorContext("a") michael@0: }), michael@0: new loader1.cm.Item({ michael@0: label: "item 2" michael@0: }), michael@0: new loader1.cm.Item({ michael@0: label: "item 3" michael@0: }) michael@0: ]; michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // One should be hidden michael@0: test.checkMenu(allItems, [allItems[0], allItems[1]], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Tests that we transition between overflowing to non-overflowing to no items michael@0: // and back again michael@0: exports.testOverflowTransition = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let prefs = loader.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 2); michael@0: michael@0: let pItems = [ michael@0: new loader.cm.Item({ michael@0: label: "item 0", michael@0: context: loader.cm.SelectorContext("p") michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 1", michael@0: context: loader.cm.SelectorContext("p") michael@0: }) michael@0: ]; michael@0: michael@0: let aItems = [ michael@0: new loader.cm.Item({ michael@0: label: "item 2", michael@0: context: loader.cm.SelectorContext("a") michael@0: }), michael@0: new loader.cm.Item({ michael@0: label: "item 3", michael@0: context: loader.cm.SelectorContext("a") michael@0: }) michael@0: ]; michael@0: michael@0: let allItems = pItems.concat(aItems); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("link"), function (popup) { michael@0: // The menu should contain all items and will overflow michael@0: test.checkMenu(allItems, [], []); michael@0: popup.hidePopup(); michael@0: michael@0: test.showMenu(doc.getElementById("text"), function (popup) { michael@0: // Only contains hald the items and will not overflow michael@0: test.checkMenu(allItems, aItems, []); michael@0: popup.hidePopup(); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // None of the items will be visible michael@0: test.checkMenu(allItems, allItems, []); michael@0: popup.hidePopup(); michael@0: michael@0: test.showMenu(doc.getElementById("text"), function (popup) { michael@0: // Only contains hald the items and will not overflow michael@0: test.checkMenu(allItems, aItems, []); michael@0: popup.hidePopup(); michael@0: michael@0: test.showMenu(doc.getElementById("link"), function (popup) { michael@0: // The menu should contain all items and will overflow michael@0: test.checkMenu(allItems, [], []); michael@0: popup.hidePopup(); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: // None of the items will be visible michael@0: test.checkMenu(allItems, allItems, []); michael@0: popup.hidePopup(); michael@0: michael@0: test.showMenu(doc.getElementById("link"), function (popup) { michael@0: // The menu should contain all items and will overflow michael@0: test.checkMenu(allItems, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // An item's command listener should work. michael@0: exports.testItemCommand = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: data: "item data", michael@0: contentScript: 'self.on("click", function (node, data) {' + michael@0: ' self.postMessage({' + michael@0: ' tagName: node.tagName,' + michael@0: ' data: data' + michael@0: ' });' + michael@0: '});', michael@0: onMessage: function (data) { michael@0: assert.equal(this, item, "`this` inside onMessage should be item"); michael@0: assert.equal(data.tagName, "HTML", "node should be an HTML element"); michael@0: assert.equal(data.data, item.data, "data should be item data"); michael@0: test.done(); michael@0: } michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: let elt = test.getItemElt(popup, item); michael@0: michael@0: // create a command event michael@0: let evt = elt.ownerDocument.createEvent('Event'); michael@0: evt.initEvent('command', true, true); michael@0: elt.dispatchEvent(evt); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // A menu's click listener should work and receive bubbling 'command' events from michael@0: // sub-items appropriately. This also tests menus and ensures that when a CSS michael@0: // selector context matches the clicked node's ancestor, the matching ancestor michael@0: // is passed to listeners as the clicked node. michael@0: exports.testMenuCommand = function (assert, done) { michael@0: // Create a top-level menu, submenu, and item, like this: michael@0: // topMenu -> submenu -> item michael@0: // Click the item and make sure the click bubbles. michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "submenu item", michael@0: data: "submenu item data", michael@0: context: loader.cm.SelectorContext("a"), michael@0: }); michael@0: michael@0: let submenu = new loader.cm.Menu({ michael@0: label: "submenu", michael@0: context: loader.cm.SelectorContext("a"), michael@0: items: [item] michael@0: }); michael@0: michael@0: let topMenu = new loader.cm.Menu({ michael@0: label: "top menu", michael@0: contentScript: 'self.on("click", function (node, data) {' + michael@0: ' self.postMessage({' + michael@0: ' tagName: node.tagName,' + michael@0: ' data: data' + michael@0: ' });' + michael@0: '});', michael@0: onMessage: function (data) { michael@0: assert.equal(this, topMenu, "`this` inside top menu should be menu"); michael@0: assert.equal(data.tagName, "A", "Clicked node should be anchor"); michael@0: assert.equal(data.data, item.data, michael@0: "Clicked item data should be correct"); michael@0: test.done(); michael@0: }, michael@0: items: [submenu], michael@0: context: loader.cm.SelectorContext("a") michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("span-link"), function (popup) { michael@0: test.checkMenu([topMenu], [], []); michael@0: let topMenuElt = test.getItemElt(popup, topMenu); michael@0: let topMenuPopup = topMenuElt.firstChild; michael@0: let submenuElt = test.getItemElt(topMenuPopup, submenu); michael@0: let submenuPopup = submenuElt.firstChild; michael@0: let itemElt = test.getItemElt(submenuPopup, item); michael@0: michael@0: // create a command event michael@0: let evt = itemElt.ownerDocument.createEvent('Event'); michael@0: evt.initEvent('command', true, true); michael@0: itemElt.dispatchEvent(evt); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Click listeners should work when multiple modules are loaded. michael@0: exports.testItemCommandMultipleModules = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let item0 = loader0.cm.Item({ michael@0: label: "loader 0 item", michael@0: contentScript: 'self.on("click", self.postMessage);', michael@0: onMessage: function () { michael@0: test.fail("loader 0 item should not emit click event"); michael@0: } michael@0: }); michael@0: let item1 = loader1.cm.Item({ michael@0: label: "loader 1 item", michael@0: contentScript: 'self.on("click", self.postMessage);', michael@0: onMessage: function () { michael@0: test.pass("loader 1 item clicked as expected"); michael@0: test.done(); michael@0: } michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item0, item1], [], []); michael@0: let item1Elt = test.getItemElt(popup, item1); michael@0: michael@0: // create a command event michael@0: let evt = item1Elt.ownerDocument.createEvent('Event'); michael@0: evt.initEvent('command', true, true); michael@0: item1Elt.dispatchEvent(evt); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: michael@0: michael@0: // An item's click listener should work. michael@0: exports.testItemClick = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: data: "item data", michael@0: contentScript: 'self.on("click", function (node, data) {' + michael@0: ' self.postMessage({' + michael@0: ' tagName: node.tagName,' + michael@0: ' data: data' + michael@0: ' });' + michael@0: '});', michael@0: onMessage: function (data) { michael@0: assert.equal(this, item, "`this` inside onMessage should be item"); michael@0: assert.equal(data.tagName, "HTML", "node should be an HTML element"); michael@0: assert.equal(data.data, item.data, "data should be item data"); michael@0: test.done(); michael@0: } michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: let elt = test.getItemElt(popup, item); michael@0: elt.click(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // A menu's click listener should work and receive bubbling clicks from michael@0: // sub-items appropriately. This also tests menus and ensures that when a CSS michael@0: // selector context matches the clicked node's ancestor, the matching ancestor michael@0: // is passed to listeners as the clicked node. michael@0: exports.testMenuClick = function (assert, done) { michael@0: // Create a top-level menu, submenu, and item, like this: michael@0: // topMenu -> submenu -> item michael@0: // Click the item and make sure the click bubbles. michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "submenu item", michael@0: data: "submenu item data", michael@0: context: loader.cm.SelectorContext("a"), michael@0: }); michael@0: michael@0: let submenu = new loader.cm.Menu({ michael@0: label: "submenu", michael@0: context: loader.cm.SelectorContext("a"), michael@0: items: [item] michael@0: }); michael@0: michael@0: let topMenu = new loader.cm.Menu({ michael@0: label: "top menu", michael@0: contentScript: 'self.on("click", function (node, data) {' + michael@0: ' self.postMessage({' + michael@0: ' tagName: node.tagName,' + michael@0: ' data: data' + michael@0: ' });' + michael@0: '});', michael@0: onMessage: function (data) { michael@0: assert.equal(this, topMenu, "`this` inside top menu should be menu"); michael@0: assert.equal(data.tagName, "A", "Clicked node should be anchor"); michael@0: assert.equal(data.data, item.data, michael@0: "Clicked item data should be correct"); michael@0: test.done(); michael@0: }, michael@0: items: [submenu], michael@0: context: loader.cm.SelectorContext("a") michael@0: }); michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("span-link"), function (popup) { michael@0: test.checkMenu([topMenu], [], []); michael@0: let topMenuElt = test.getItemElt(popup, topMenu); michael@0: let topMenuPopup = topMenuElt.firstChild; michael@0: let submenuElt = test.getItemElt(topMenuPopup, submenu); michael@0: let submenuPopup = submenuElt.firstChild; michael@0: let itemElt = test.getItemElt(submenuPopup, item); michael@0: itemElt.click(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Click listeners should work when multiple modules are loaded. michael@0: exports.testItemClickMultipleModules = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let item0 = loader0.cm.Item({ michael@0: label: "loader 0 item", michael@0: contentScript: 'self.on("click", self.postMessage);', michael@0: onMessage: function () { michael@0: test.fail("loader 0 item should not emit click event"); michael@0: } michael@0: }); michael@0: let item1 = loader1.cm.Item({ michael@0: label: "loader 1 item", michael@0: contentScript: 'self.on("click", self.postMessage);', michael@0: onMessage: function () { michael@0: test.pass("loader 1 item clicked as expected"); michael@0: test.done(); michael@0: } michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item0, item1], [], []); michael@0: let item1Elt = test.getItemElt(popup, item1); michael@0: item1Elt.click(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Adding a separator to a submenu should work OK. michael@0: exports.testSeparator = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let menu = new loader.cm.Menu({ michael@0: label: "submenu", michael@0: items: [new loader.cm.Separator()] michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // The parentMenu option should work michael@0: exports.testParentMenu = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let menu = new loader.cm.Menu({ michael@0: label: "submenu", michael@0: items: [loader.cm.Item({ label: "item 1" })], michael@0: parentMenu: loader.cm.contentContextMenu michael@0: }); michael@0: michael@0: let item = loader.cm.Item({ michael@0: label: "item 2", michael@0: parentMenu: menu, michael@0: }); michael@0: michael@0: assert.equal(menu.items[1], item, "Item should be in the sub menu"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Existing context menu modifications should apply to new windows. michael@0: exports.testNewWindow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ label: "item" }); michael@0: michael@0: test.withNewWindow(function () { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // When a new window is opened, items added by an unloaded module should not michael@0: // be present in the menu. michael@0: exports.testNewWindowMultipleModules = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: let item = new loader.cm.Item({ label: "item" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: popup.hidePopup(); michael@0: loader.unload(); michael@0: test.withNewWindow(function () { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], [item]); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Existing context menu modifications should not apply to new private windows. michael@0: exports.testNewPrivateWindow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ label: "item" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: test.withNewPrivateWindow(function () { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Existing context menu modifications should apply to new private windows when michael@0: // private browsing support is enabled. michael@0: exports.testNewPrivateEnabledWindow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newPrivateLoader(); michael@0: michael@0: let item = new loader.cm.Item({ label: "item" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: test.withNewPrivateWindow(function () { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Existing context menu modifications should apply to new private windows when michael@0: // private browsing support is enabled unless unloaded. michael@0: exports.testNewPrivateEnabledWindowUnloaded = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newPrivateLoader(); michael@0: michael@0: let item = new loader.cm.Item({ label: "item" }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: popup.hidePopup(); michael@0: michael@0: loader.unload(); michael@0: michael@0: test.withNewPrivateWindow(function () { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Items in the context menu should be sorted according to locale. michael@0: exports.testSorting = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: // Make an unsorted items list. It'll look like this: michael@0: // item 1, item 0, item 3, item 2, item 5, item 4, ... michael@0: let items = []; michael@0: for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i += 2) { michael@0: items.push(new loader.cm.Item({ label: "item " + (i + 1) })); michael@0: items.push(new loader.cm.Item({ label: "item " + i })); michael@0: } michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Items in the overflow menu should be sorted according to locale. michael@0: exports.testSortingOverflow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: // Make an unsorted items list. It'll look like this: michael@0: // item 1, item 0, item 3, item 2, item 5, item 4, ... michael@0: let items = []; michael@0: for (let i = 0; i < OVERFLOW_THRESH_DEFAULT * 2; i += 2) { michael@0: items.push(new loader.cm.Item({ label: "item " + (i + 1) })); michael@0: items.push(new loader.cm.Item({ label: "item " + i })); michael@0: } michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Multiple modules shouldn't interfere with sorting. michael@0: exports.testSortingMultipleModules = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader0 = test.newLoader(); michael@0: let loader1 = test.newLoader(); michael@0: michael@0: let items0 = []; michael@0: let items1 = []; michael@0: for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { michael@0: if (i % 2) { michael@0: let item = new loader0.cm.Item({ label: "item " + i }); michael@0: items0.push(item); michael@0: } michael@0: else { michael@0: let item = new loader1.cm.Item({ label: "item " + i }); michael@0: items1.push(item); michael@0: } michael@0: } michael@0: let allItems = items0.concat(items1); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // All items should be present and sorted. michael@0: test.checkMenu(allItems, [], []); michael@0: popup.hidePopup(); michael@0: loader0.unload(); michael@0: loader1.unload(); michael@0: test.showMenu(null, function (popup) { michael@0: michael@0: // All items should be removed. michael@0: test.checkMenu(allItems, [], allItems); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Content click handlers and context handlers should be able to communicate, michael@0: // i.e., they're eval'ed in the same worker and sandbox. michael@0: exports.testContentCommunication = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: 'var potato;' + michael@0: 'self.on("context", function () {' + michael@0: ' potato = "potato";' + michael@0: ' return true;' + michael@0: '});' + michael@0: 'self.on("click", function () {' + michael@0: ' self.postMessage(potato);' + michael@0: '});', michael@0: }); michael@0: michael@0: item.on("message", function (data) { michael@0: assert.equal(data, "potato", "That's a lot of potatoes!"); michael@0: test.done(); michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: let elt = test.getItemElt(popup, item); michael@0: elt.click(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // When the context menu is invoked on a tab that was already open when the michael@0: // module was loaded, it should contain the expected items and content workers michael@0: // should function as expected. michael@0: exports.testLoadWithOpenTab = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: test.withTestDoc(function (window, doc) { michael@0: let loader = test.newLoader(); michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: contentScript: michael@0: 'self.on("click", function () self.postMessage("click"));', michael@0: onMessage: function (msg) { michael@0: if (msg === "click") michael@0: test.done(); michael@0: } michael@0: }); michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.getItemElt(popup, item).click(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Bug 732716: Ensure that the node given in `click` event works fine michael@0: // (i.e. is correctly wrapped) michael@0: exports.testDrawImageOnClickNode = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: test.withTestDoc(function (window, doc) { michael@0: let loader = test.newLoader(); michael@0: let item = new loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.SelectorContext("img"), michael@0: contentScript: "new " + function() { michael@0: self.on("click", function (img, data) { michael@0: let ctx = document.createElement("canvas").getContext("2d"); michael@0: ctx.drawImage(img, 1, 1, 1, 1); michael@0: self.postMessage("done"); michael@0: }); michael@0: }, michael@0: onMessage: function (msg) { michael@0: if (msg === "done") michael@0: test.done(); michael@0: } michael@0: }); michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.getItemElt(popup, item).click(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Setting an item's label before the menu is ever shown should correctly change michael@0: // its label. michael@0: exports.testSetLabelBeforeShow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: new loader.cm.Item({ label: "a" }), michael@0: new loader.cm.Item({ label: "b" }) michael@0: ] michael@0: items[0].label = "z"; michael@0: assert.equal(items[0].label, "z"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Setting an item's label after the menu is shown should correctly change its michael@0: // label. michael@0: exports.testSetLabelAfterShow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: new loader.cm.Item({ label: "a" }), michael@0: new loader.cm.Item({ label: "b" }) michael@0: ]; michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: popup.hidePopup(); michael@0: michael@0: items[0].label = "z"; michael@0: assert.equal(items[0].label, "z"); michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Setting an item's label before the menu is ever shown should correctly change michael@0: // its label. michael@0: exports.testSetLabelBeforeShowOverflow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let prefs = loader.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 0); michael@0: michael@0: let items = [ michael@0: new loader.cm.Item({ label: "a" }), michael@0: new loader.cm.Item({ label: "b" }) michael@0: ] michael@0: items[0].label = "z"; michael@0: assert.equal(items[0].label, "z"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Setting an item's label after the menu is shown should correctly change its michael@0: // label. michael@0: exports.testSetLabelAfterShowOverflow = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let prefs = loader.loader.require("sdk/preferences/service"); michael@0: prefs.set(OVERFLOW_THRESH_PREF, 0); michael@0: michael@0: let items = [ michael@0: new loader.cm.Item({ label: "a" }), michael@0: new loader.cm.Item({ label: "b" }) michael@0: ]; michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: popup.hidePopup(); michael@0: michael@0: items[0].label = "z"; michael@0: assert.equal(items[0].label, "z"); michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Setting the label of an item in a Menu should work. michael@0: exports.testSetLabelMenuItem = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let menu = loader.cm.Menu({ michael@0: label: "menu", michael@0: items: [loader.cm.Item({ label: "a" })] michael@0: }); michael@0: menu.items[0].label = "z"; michael@0: michael@0: assert.equal(menu.items[0].label, "z"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Menu.addItem() should work. michael@0: exports.testMenuAddItem = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let menu = loader.cm.Menu({ michael@0: label: "menu", michael@0: items: [ michael@0: loader.cm.Item({ label: "item 0" }) michael@0: ] michael@0: }); michael@0: menu.addItem(loader.cm.Item({ label: "item 1" })); michael@0: menu.addItem(loader.cm.Item({ label: "item 2" })); michael@0: michael@0: assert.equal(menu.items.length, 3, michael@0: "menu should have correct number of items"); michael@0: for (let i = 0; i < 3; i++) { michael@0: assert.equal(menu.items[i].label, "item " + i, michael@0: "item label should be correct"); michael@0: assert.equal(menu.items[i].parentMenu, menu, michael@0: "item's parent menu should be correct"); michael@0: } michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Adding the same item twice to a menu should work as expected. michael@0: exports.testMenuAddItemTwice = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let menu = loader.cm.Menu({ michael@0: label: "menu", michael@0: items: [] michael@0: }); michael@0: let subitem = loader.cm.Item({ label: "item 1" }) michael@0: menu.addItem(subitem); michael@0: menu.addItem(loader.cm.Item({ label: "item 0" })); michael@0: menu.addItem(subitem); michael@0: michael@0: assert.equal(menu.items.length, 2, michael@0: "menu should have correct number of items"); michael@0: for (let i = 0; i < 2; i++) { michael@0: assert.equal(menu.items[i].label, "item " + i, michael@0: "item label should be correct"); michael@0: } michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Menu.removeItem() should work. michael@0: exports.testMenuRemoveItem = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let subitem = loader.cm.Item({ label: "item 1" }); michael@0: let menu = loader.cm.Menu({ michael@0: label: "menu", michael@0: items: [ michael@0: loader.cm.Item({ label: "item 0" }), michael@0: subitem, michael@0: loader.cm.Item({ label: "item 2" }) michael@0: ] michael@0: }); michael@0: michael@0: // Removing twice should be harmless. michael@0: menu.removeItem(subitem); michael@0: menu.removeItem(subitem); michael@0: michael@0: assert.equal(subitem.parentMenu, null, michael@0: "item's parent menu should be correct"); michael@0: michael@0: assert.equal(menu.items.length, 2, michael@0: "menu should have correct number of items"); michael@0: assert.equal(menu.items[0].label, "item 0", michael@0: "item label should be correct"); michael@0: assert.equal(menu.items[1].label, "item 2", michael@0: "item label should be correct"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Adding an item currently contained in one menu to another menu should work. michael@0: exports.testMenuItemSwap = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let subitem = loader.cm.Item({ label: "item" }); michael@0: let menu0 = loader.cm.Menu({ michael@0: label: "menu 0", michael@0: items: [subitem] michael@0: }); michael@0: let menu1 = loader.cm.Menu({ michael@0: label: "menu 1", michael@0: items: [] michael@0: }); michael@0: menu1.addItem(subitem); michael@0: michael@0: assert.equal(menu0.items.length, 0, michael@0: "menu should have correct number of items"); michael@0: michael@0: assert.equal(menu1.items.length, 1, michael@0: "menu should have correct number of items"); michael@0: assert.equal(menu1.items[0].label, "item", michael@0: "item label should be correct"); michael@0: michael@0: assert.equal(subitem.parentMenu, menu1, michael@0: "item's parent menu should be correct"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu0, menu1], [menu0], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Destroying an item should remove it from its parent menu. michael@0: exports.testMenuItemDestroy = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let subitem = loader.cm.Item({ label: "item" }); michael@0: let menu = loader.cm.Menu({ michael@0: label: "menu", michael@0: items: [subitem] michael@0: }); michael@0: subitem.destroy(); michael@0: michael@0: assert.equal(menu.items.length, 0, michael@0: "menu should have correct number of items"); michael@0: assert.equal(subitem.parentMenu, null, michael@0: "item's parent menu should be correct"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu], [menu], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Setting Menu.items should work. michael@0: exports.testMenuItemsSetter = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let menu = loader.cm.Menu({ michael@0: label: "menu", michael@0: items: [ michael@0: loader.cm.Item({ label: "old item 0" }), michael@0: loader.cm.Item({ label: "old item 1" }) michael@0: ] michael@0: }); michael@0: menu.items = [ michael@0: loader.cm.Item({ label: "new item 0" }), michael@0: loader.cm.Item({ label: "new item 1" }), michael@0: loader.cm.Item({ label: "new item 2" }) michael@0: ]; michael@0: michael@0: assert.equal(menu.items.length, 3, michael@0: "menu should have correct number of items"); michael@0: for (let i = 0; i < 3; i++) { michael@0: assert.equal(menu.items[i].label, "new item " + i, michael@0: "item label should be correct"); michael@0: assert.equal(menu.items[i].parentMenu, menu, michael@0: "item's parent menu should be correct"); michael@0: } michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Setting Item.data should work. michael@0: exports.testItemDataSetter = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item = loader.cm.Item({ label: "old item 0", data: "old" }); michael@0: item.data = "new"; michael@0: michael@0: assert.equal(item.data, "new", "item should have correct data"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Open the test doc, load the module, make sure items appear when context- michael@0: // clicking the iframe. michael@0: exports.testAlreadyOpenIframe = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: test.withTestDoc(function (window, doc) { michael@0: let loader = test.newLoader(); michael@0: let item = new loader.cm.Item({ michael@0: label: "item" michael@0: }); michael@0: test.showMenu(doc.getElementById("iframe"), function (popup) { michael@0: test.checkMenu([item], [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Tests that a missing label throws an exception michael@0: exports.testItemNoLabel = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: try { michael@0: new loader.cm.Item({}); michael@0: assert.ok(false, "Should have seen exception"); michael@0: } michael@0: catch (e) { michael@0: assert.ok(true, "Should have seen exception"); michael@0: } michael@0: michael@0: try { michael@0: new loader.cm.Item({ label: null }); michael@0: assert.ok(false, "Should have seen exception"); michael@0: } michael@0: catch (e) { michael@0: assert.ok(true, "Should have seen exception"); michael@0: } michael@0: michael@0: try { michael@0: new loader.cm.Item({ label: undefined }); michael@0: assert.ok(false, "Should have seen exception"); michael@0: } michael@0: catch (e) { michael@0: assert.ok(true, "Should have seen exception"); michael@0: } michael@0: michael@0: try { michael@0: new loader.cm.Item({ label: "" }); michael@0: assert.ok(false, "Should have seen exception"); michael@0: } michael@0: catch (e) { michael@0: assert.ok(true, "Should have seen exception"); michael@0: } michael@0: michael@0: test.done(); michael@0: } michael@0: michael@0: michael@0: // Tests that items can have an empty data property michael@0: exports.testItemNoData = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: function checkData(data) { michael@0: assert.equal(data, undefined, "Data should be undefined"); michael@0: } michael@0: michael@0: let item1 = new loader.cm.Item({ michael@0: label: "item 1", michael@0: contentScript: 'self.on("click", function(node, data) self.postMessage(data))', michael@0: onMessage: checkData michael@0: }); michael@0: let item2 = new loader.cm.Item({ michael@0: label: "item 2", michael@0: data: null, michael@0: contentScript: 'self.on("click", function(node, data) self.postMessage(data))', michael@0: onMessage: checkData michael@0: }); michael@0: let item3 = new loader.cm.Item({ michael@0: label: "item 3", michael@0: data: undefined, michael@0: contentScript: 'self.on("click", function(node, data) self.postMessage(data))', michael@0: onMessage: checkData michael@0: }); michael@0: michael@0: assert.equal(item1.data, undefined, "Should be no defined data"); michael@0: assert.equal(item2.data, null, "Should be no defined data"); michael@0: assert.equal(item3.data, undefined, "Should be no defined data"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item1, item2, item3], [], []); michael@0: michael@0: let itemElt = test.getItemElt(popup, item1); michael@0: itemElt.click(); michael@0: michael@0: test.hideMenu(function() { michael@0: test.showMenu(null, function (popup) { michael@0: let itemElt = test.getItemElt(popup, item2); michael@0: itemElt.click(); michael@0: michael@0: test.hideMenu(function() { michael@0: test.showMenu(null, function (popup) { michael@0: let itemElt = test.getItemElt(popup, item3); michael@0: itemElt.click(); michael@0: michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: michael@0: // Tests that items without an image don't attempt to show one michael@0: exports.testItemNoImage = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let item1 = new loader.cm.Item({ label: "item 1" }); michael@0: let item2 = new loader.cm.Item({ label: "item 2", image: null }); michael@0: let item3 = new loader.cm.Item({ label: "item 3", image: undefined }); michael@0: michael@0: assert.equal(item1.image, undefined, "Should be no defined image"); michael@0: assert.equal(item2.image, null, "Should be no defined image"); michael@0: assert.equal(item3.image, undefined, "Should be no defined image"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item1, item2, item3], [], []); michael@0: michael@0: test.done(); michael@0: }); michael@0: } michael@0: michael@0: michael@0: // Test image support. michael@0: exports.testItemImage = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let imageURL = data.url("moz_favicon.ico"); michael@0: let item = new loader.cm.Item({ label: "item", image: imageURL }); michael@0: let menu = new loader.cm.Menu({ label: "menu", image: imageURL, items: [ michael@0: loader.cm.Item({ label: "subitem" }) michael@0: ]}); michael@0: assert.equal(item.image, imageURL, "Should have set the image correctly"); michael@0: assert.equal(menu.image, imageURL, "Should have set the image correctly"); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([item, menu], [], []); michael@0: michael@0: let imageURL2 = data.url("dummy.ico"); michael@0: item.image = imageURL2; michael@0: menu.image = imageURL2; michael@0: assert.equal(item.image, imageURL2, "Should have set the image correctly"); michael@0: assert.equal(menu.image, imageURL2, "Should have set the image correctly"); michael@0: test.checkMenu([item, menu], [], []); michael@0: michael@0: item.image = null; michael@0: menu.image = null; michael@0: assert.equal(item.image, null, "Should have set the image correctly"); michael@0: assert.equal(menu.image, null, "Should have set the image correctly"); michael@0: test.checkMenu([item, menu], [], []); michael@0: michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: // Test image URL validation. michael@0: exports.testItemImageValidURL = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: assert.throws(function(){ michael@0: new loader.cm.Item({ michael@0: label: "item 1", michael@0: image: "foo" michael@0: }) michael@0: }, /Image URL validation failed/ michael@0: ); michael@0: michael@0: assert.throws(function(){ michael@0: new loader.cm.Item({ michael@0: label: "item 2", michael@0: image: false michael@0: }) michael@0: }, /Image URL validation failed/ michael@0: ); michael@0: michael@0: assert.throws(function(){ michael@0: new loader.cm.Item({ michael@0: label: "item 3", michael@0: image: 0 michael@0: }) michael@0: }, /Image URL validation failed/ michael@0: ); michael@0: michael@0: let imageURL = data.url("moz_favicon.ico"); michael@0: let item4 = new loader.cm.Item({ label: "item 4", image: imageURL }); michael@0: let item5 = new loader.cm.Item({ label: "item 5", image: null }); michael@0: let item6 = new loader.cm.Item({ label: "item 6", image: undefined }); michael@0: michael@0: assert.equal(item4.image, imageURL, "Should be proper image URL"); michael@0: assert.equal(item5.image, null, "Should be null image"); michael@0: assert.equal(item6.image, undefined, "Should be undefined image"); michael@0: michael@0: test.done(); michael@0: }; michael@0: michael@0: michael@0: // Menu.destroy should destroy the item tree rooted at that menu. michael@0: exports.testMenuDestroy = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let menu = loader.cm.Menu({ michael@0: label: "menu", michael@0: items: [ michael@0: loader.cm.Item({ label: "item 0" }), michael@0: loader.cm.Menu({ michael@0: label: "item 1", michael@0: items: [ michael@0: loader.cm.Item({ label: "subitem 0" }), michael@0: loader.cm.Item({ label: "subitem 1" }), michael@0: loader.cm.Item({ label: "subitem 2" }) michael@0: ] michael@0: }), michael@0: loader.cm.Item({ label: "item 2" }) michael@0: ] michael@0: }); michael@0: menu.destroy(); michael@0: michael@0: /*let numRegistryEntries = 0; michael@0: loader.globalScope.browserManager.browserWins.forEach(function (bwin) { michael@0: for (let itemID in bwin.items) michael@0: numRegistryEntries++; michael@0: }); michael@0: assert.equal(numRegistryEntries, 0, "All items should be unregistered.");*/ michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([menu], [], [menu]); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: // Checks that if a menu contains sub items that are hidden then the menu is michael@0: // hidden too. Also checks that content scripts and contexts work for sub items. michael@0: exports.testSubItemContextNoMatchHideMenu = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: loader.cm.Menu({ michael@0: label: "menu 1", michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 1", michael@0: context: loader.cm.SelectorContext(".foo") michael@0: }) michael@0: ] michael@0: }), michael@0: loader.cm.Menu({ michael@0: label: "menu 2", michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 2", michael@0: contentScript: 'self.on("context", function () false);' michael@0: }) michael@0: ] michael@0: }), michael@0: loader.cm.Menu({ michael@0: label: "menu 3", michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 3", michael@0: context: loader.cm.SelectorContext(".foo") michael@0: }), michael@0: loader.cm.Item({ michael@0: label: "subitem 4", michael@0: contentScript: 'self.on("context", function () false);' michael@0: }) michael@0: ] michael@0: }) michael@0: ]; michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, items, []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Checks that if a menu contains a combination of hidden and visible sub items michael@0: // then the menu is still visible too. michael@0: exports.testSubItemContextMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let hiddenItems = [ michael@0: loader.cm.Item({ michael@0: label: "subitem 3", michael@0: context: loader.cm.SelectorContext(".foo") michael@0: }), michael@0: loader.cm.Item({ michael@0: label: "subitem 6", michael@0: contentScript: 'self.on("context", function () false);' michael@0: }) michael@0: ]; michael@0: michael@0: let items = [ michael@0: loader.cm.Menu({ michael@0: label: "menu 1", michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 1", michael@0: context: loader.cm.URLContext(TEST_DOC_URL) michael@0: }) michael@0: ] michael@0: }), michael@0: loader.cm.Menu({ michael@0: label: "menu 2", michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 2", michael@0: contentScript: 'self.on("context", function () true);' michael@0: }) michael@0: ] michael@0: }), michael@0: loader.cm.Menu({ michael@0: label: "menu 3", michael@0: items: [ michael@0: hiddenItems[0], michael@0: loader.cm.Item({ michael@0: label: "subitem 4", michael@0: contentScript: 'self.on("context", function () true);' michael@0: }) michael@0: ] michael@0: }), michael@0: loader.cm.Menu({ michael@0: label: "menu 4", michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 5", michael@0: context: loader.cm.URLContext(TEST_DOC_URL) michael@0: }), michael@0: hiddenItems[1] michael@0: ] michael@0: }), michael@0: loader.cm.Menu({ michael@0: label: "menu 5", michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 7", michael@0: context: loader.cm.URLContext(TEST_DOC_URL) michael@0: }), michael@0: loader.cm.Item({ michael@0: label: "subitem 8", michael@0: contentScript: 'self.on("context", function () true);' michael@0: }) michael@0: ] michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, hiddenItems, []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Child items should default to visible, not to PageContext michael@0: exports.testSubItemDefaultVisible = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [ michael@0: loader.cm.Menu({ michael@0: label: "menu 1", michael@0: context: loader.cm.SelectorContext("img"), michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 1" michael@0: }), michael@0: loader.cm.Item({ michael@0: label: "subitem 2", michael@0: context: loader.cm.SelectorContext("img") michael@0: }), michael@0: loader.cm.Item({ michael@0: label: "subitem 3", michael@0: context: loader.cm.SelectorContext("a") michael@0: }) michael@0: ] michael@0: }) michael@0: ]; michael@0: michael@0: // subitem 3 will be hidden michael@0: let hiddenItems = [items[0].items[2]]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu(items, hiddenItems, []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Tests that the click event on sub menuitem michael@0: // tiggers the click event for the sub menuitem and the parent menu michael@0: exports.testSubItemClick = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let state = 0; michael@0: michael@0: let items = [ michael@0: loader.cm.Menu({ michael@0: label: "menu 1", michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 1", michael@0: data: "foobar", michael@0: contentScript: 'self.on("click", function (node, data) {' + michael@0: ' self.postMessage({' + michael@0: ' tagName: node.tagName,' + michael@0: ' data: data' + michael@0: ' });' + michael@0: '});', michael@0: onMessage: function(msg) { michael@0: assert.equal(msg.tagName, "HTML", "should have seen the right node"); michael@0: assert.equal(msg.data, "foobar", "should have seen the right data"); michael@0: assert.equal(state, 0, "should have seen the event at the right time"); michael@0: state++; michael@0: } michael@0: }) michael@0: ], michael@0: contentScript: 'self.on("click", function (node, data) {' + michael@0: ' self.postMessage({' + michael@0: ' tagName: node.tagName,' + michael@0: ' data: data' + michael@0: ' });' + michael@0: '});', michael@0: onMessage: function(msg) { michael@0: assert.equal(msg.tagName, "HTML", "should have seen the right node"); michael@0: assert.equal(msg.data, "foobar", "should have seen the right data"); michael@0: assert.equal(state, 1, "should have seen the event at the right time"); michael@0: michael@0: test.done(); michael@0: } michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: michael@0: let topMenuElt = test.getItemElt(popup, items[0]); michael@0: let topMenuPopup = topMenuElt.firstChild; michael@0: let itemElt = test.getItemElt(topMenuPopup, items[0].items[0]); michael@0: itemElt.click(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Tests that the command event on sub menuitem michael@0: // tiggers the click event for the sub menuitem and the parent menu michael@0: exports.testSubItemCommand = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let state = 0; michael@0: michael@0: let items = [ michael@0: loader.cm.Menu({ michael@0: label: "menu 1", michael@0: items: [ michael@0: loader.cm.Item({ michael@0: label: "subitem 1", michael@0: data: "foobar", michael@0: contentScript: 'self.on("click", function (node, data) {' + michael@0: ' self.postMessage({' + michael@0: ' tagName: node.tagName,' + michael@0: ' data: data' + michael@0: ' });' + michael@0: '});', michael@0: onMessage: function(msg) { michael@0: assert.equal(msg.tagName, "HTML", "should have seen the right node"); michael@0: assert.equal(msg.data, "foobar", "should have seen the right data"); michael@0: assert.equal(state, 0, "should have seen the event at the right time"); michael@0: state++; michael@0: } michael@0: }) michael@0: ], michael@0: contentScript: 'self.on("click", function (node, data) {' + michael@0: ' self.postMessage({' + michael@0: ' tagName: node.tagName,' + michael@0: ' data: data' + michael@0: ' });' + michael@0: '});', michael@0: onMessage: function(msg) { michael@0: assert.equal(msg.tagName, "HTML", "should have seen the right node"); michael@0: assert.equal(msg.data, "foobar", "should have seen the right data"); michael@0: assert.equal(state, 1, "should have seen the event at the right time"); michael@0: state++ michael@0: michael@0: test.done(); michael@0: } michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: michael@0: let topMenuElt = test.getItemElt(popup, items[0]); michael@0: let topMenuPopup = topMenuElt.firstChild; michael@0: let itemElt = test.getItemElt(topMenuPopup, items[0].items[0]); michael@0: michael@0: // create a command event michael@0: let evt = itemElt.ownerDocument.createEvent('Event'); michael@0: evt.initEvent('command', true, true); michael@0: itemElt.dispatchEvent(evt); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Tests that opening a context menu for an outer frame when an inner frame michael@0: // has a selection doesn't activate the SelectionContext michael@0: exports.testSelectionInInnerFrameNoMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let state = 0; michael@0: michael@0: let items = [ michael@0: loader.cm.Item({ michael@0: label: "test item", michael@0: context: loader.cm.SelectionContext() michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: let frame = doc.getElementById("iframe"); michael@0: frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, items, []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Tests that opening a context menu for an inner frame when the inner frame michael@0: // has a selection does activate the SelectionContext michael@0: exports.testSelectionInInnerFrameMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let state = 0; michael@0: michael@0: let items = [ michael@0: loader.cm.Item({ michael@0: label: "test item", michael@0: context: loader.cm.SelectionContext() michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: let frame = doc.getElementById("iframe"); michael@0: frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body); michael@0: michael@0: test.showMenu(frame.contentDocument.getElementById("text"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Tests that opening a context menu for an inner frame when the outer frame michael@0: // has a selection doesn't activate the SelectionContext michael@0: exports.testSelectionInOuterFrameNoMatch = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let state = 0; michael@0: michael@0: let items = [ michael@0: loader.cm.Item({ michael@0: label: "test item", michael@0: context: loader.cm.SelectionContext() michael@0: }) michael@0: ]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: let frame = doc.getElementById("iframe"); michael@0: window.getSelection().selectAllChildren(doc.body); michael@0: michael@0: test.showMenu(frame.contentDocument.getElementById("text"), function (popup) { michael@0: test.checkMenu(items, items, []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Test that the return value of the predicate function determines if michael@0: // item is shown michael@0: exports.testPredicateContextControl = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let itemTrue = loader.cm.Item({ michael@0: label: "visible", michael@0: context: loader.cm.PredicateContext(function () { return true; }) michael@0: }); michael@0: michael@0: let itemFalse = loader.cm.Item({ michael@0: label: "hidden", michael@0: context: loader.cm.PredicateContext(function () { return false; }) michael@0: }); michael@0: michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu([itemTrue, itemFalse], [itemFalse], []); michael@0: test.done(); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object has the correct document type michael@0: exports.testPredicateContextDocumentType = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.equal(data.documentType, 'text/html'); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object has the correct document URL michael@0: exports.testPredicateContextDocumentURL = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.equal(data.documentURL, TEST_DOC_URL); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Test that the data object has the correct element name michael@0: exports.testPredicateContextTargetName = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.targetName, "input"); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("button"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Test that the data object has the correct ID michael@0: exports.testPredicateContextTargetIDSet = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.targetID, "button"); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("button"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object has the correct ID michael@0: exports.testPredicateContextTargetIDNotSet = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.targetID, null); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object is showing editable correctly for regular text inputs michael@0: exports.testPredicateContextTextBoxIsEditable = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.isEditable, true); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("textbox"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object is showing editable correctly for readonly text inputs michael@0: exports.testPredicateContextReadonlyTextBoxIsNotEditable = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.isEditable, false); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("readonly-textbox"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object is showing editable correctly for disabled text inputs michael@0: exports.testPredicateContextDisabledTextBoxIsNotEditable = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.isEditable, false); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("disabled-textbox"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object is showing editable correctly for text areas michael@0: exports.testPredicateContextTextAreaIsEditable = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.isEditable, true); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("textfield"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that non-text inputs are not considered editable michael@0: exports.testPredicateContextButtonIsNotEditable = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.isEditable, false); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("button"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Test that the data object is showing editable correctly michael@0: exports.testPredicateContextNonInputIsNotEditable = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.isEditable, false); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Test that the data object is showing editable correctly for HTML contenteditable elements michael@0: exports.testPredicateContextEditableElement = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.isEditable, true); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("editable"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Test that the data object does not have a selection when there is none michael@0: exports.testPredicateContextNoSelectionInPage = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.selectionText, null); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object includes the selected page text michael@0: exports.testPredicateContextSelectionInPage = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: // since we might get whitespace michael@0: assert.ok(data.selectionText && data.selectionText.search(/^\s*Some text.\s*$/) != -1, michael@0: 'Expected "Some text.", got "' + data.selectionText + '"'); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: window.getSelection().selectAllChildren(doc.getElementById("text")); michael@0: test.showMenu(null, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object includes the selected input text michael@0: exports.testPredicateContextSelectionInTextBox = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: // since we might get whitespace michael@0: assert.strictEqual(data.selectionText, "t v"); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: let textbox = doc.getElementById("textbox"); michael@0: textbox.focus(); michael@0: textbox.setSelectionRange(3, 6); michael@0: test.showMenu(textbox, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object has the correct src for an image michael@0: exports.testPredicateContextTargetSrcSet = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: let image; michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.srcURL, image.src); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: image = doc.getElementById("image"); michael@0: test.showMenu(image, function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object has no src for a link michael@0: exports.testPredicateContextTargetSrcNotSet = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.srcURL, null); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("link"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // Test that the data object has the correct link set michael@0: exports.testPredicateContextTargetLinkSet = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: let image; michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.linkURL, TEST_DOC_URL + "#test"); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object has no link for an image michael@0: exports.testPredicateContextTargetLinkNotSet = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.linkURL, null); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object has the value for an input textbox michael@0: exports.testPredicateContextTargetValueSet = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: let image; michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.value, "test value"); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("textbox"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: // Test that the data object has no value for an image michael@0: exports.testPredicateContextTargetValueNotSet = function (assert, done) { michael@0: let test = new TestHelper(assert, done); michael@0: let loader = test.newLoader(); michael@0: michael@0: let items = [loader.cm.Item({ michael@0: label: "item", michael@0: context: loader.cm.PredicateContext(function (data) { michael@0: assert.strictEqual(data.value, null); michael@0: return true; michael@0: }) michael@0: })]; michael@0: michael@0: test.withTestDoc(function (window, doc) { michael@0: test.showMenu(doc.getElementById("image"), function (popup) { michael@0: test.checkMenu(items, [], []); michael@0: test.done(); michael@0: }); michael@0: }); michael@0: }; michael@0: michael@0: michael@0: // NO TESTS BELOW THIS LINE! /////////////////////////////////////////////////// michael@0: michael@0: // This makes it easier to run tests by handling things like opening the menu, michael@0: // opening new windows, making assertions, etc. Methods on |test| can be called michael@0: // on instances of this class. Don't forget to call done() to end the test! michael@0: // WARNING: This looks up items in popups by comparing labels, so don't give two michael@0: // items the same label. michael@0: function TestHelper(assert, done) { michael@0: this.assert = assert; michael@0: this.end = done; michael@0: this.loaders = []; michael@0: this.browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. michael@0: getService(Ci.nsIWindowMediator). michael@0: getMostRecentWindow("navigator:browser"); michael@0: this.overflowThreshValue = require("sdk/preferences/service"). michael@0: get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT); michael@0: } michael@0: michael@0: TestHelper.prototype = { michael@0: get contextMenuPopup() { michael@0: return this.browserWindow.document.getElementById("contentAreaContextMenu"); michael@0: }, michael@0: michael@0: get contextMenuSeparator() { michael@0: return this.browserWindow.document.querySelector("." + SEPARATOR_CLASS); michael@0: }, michael@0: michael@0: get overflowPopup() { michael@0: return this.browserWindow.document.querySelector("." + OVERFLOW_POPUP_CLASS); michael@0: }, michael@0: michael@0: get overflowSubmenu() { michael@0: return this.browserWindow.document.querySelector("." + OVERFLOW_MENU_CLASS); michael@0: }, michael@0: michael@0: get tabBrowser() { michael@0: return this.browserWindow.gBrowser; michael@0: }, michael@0: michael@0: // Methods on the wrapped test can be called on this object. michael@0: __noSuchMethod__: function (methodName, args) { michael@0: this.assert[methodName].apply(this.assert, args); michael@0: }, michael@0: michael@0: // Asserts that elt, a DOM element representing item, looks OK. michael@0: checkItemElt: function (elt, item) { michael@0: let itemType = this.getItemType(item); michael@0: michael@0: switch (itemType) { michael@0: case "Item": michael@0: this.assert.equal(elt.localName, "menuitem", michael@0: "Item DOM element should be a xul:menuitem"); michael@0: if (typeof(item.data) === "string") { michael@0: this.assert.equal(elt.getAttribute("value"), item.data, michael@0: "Item should have correct data"); michael@0: } michael@0: break michael@0: case "Menu": michael@0: this.assert.equal(elt.localName, "menu", michael@0: "Menu DOM element should be a xul:menu"); michael@0: let subPopup = elt.firstChild; michael@0: this.assert.ok(subPopup, "xul:menu should have a child"); michael@0: this.assert.equal(subPopup.localName, "menupopup", michael@0: "xul:menu's first child should be a menupopup"); michael@0: break; michael@0: case "Separator": michael@0: this.assert.equal(elt.localName, "menuseparator", michael@0: "Separator DOM element should be a xul:menuseparator"); michael@0: break; michael@0: } michael@0: michael@0: if (itemType === "Item" || itemType === "Menu") { michael@0: this.assert.equal(elt.getAttribute("label"), item.label, michael@0: "Item should have correct title"); michael@0: if (typeof(item.image) === "string") { michael@0: this.assert.equal(elt.getAttribute("image"), item.image, michael@0: "Item should have correct image"); michael@0: if (itemType === "Menu") michael@0: this.assert.ok(elt.classList.contains("menu-iconic"), michael@0: "Menus with images should have the correct class") michael@0: else michael@0: this.assert.ok(elt.classList.contains("menuitem-iconic"), michael@0: "Items with images should have the correct class") michael@0: } michael@0: else { michael@0: this.assert.ok(!elt.getAttribute("image"), michael@0: "Item should not have image"); michael@0: this.assert.ok(!elt.classList.contains("menu-iconic") && !elt.classList.contains("menuitem-iconic"), michael@0: "The iconic classes should not be present") michael@0: } michael@0: } michael@0: }, michael@0: michael@0: // Asserts that the context menu looks OK given the arguments. presentItems michael@0: // are items that have been added to the menu. absentItems are items that michael@0: // shouldn't match the current context. removedItems are items that have been michael@0: // removed from the menu. michael@0: checkMenu: function (presentItems, absentItems, removedItems) { michael@0: // Count up how many top-level items there are michael@0: let total = 0; michael@0: for (let item of presentItems) { michael@0: if (absentItems.indexOf(item) < 0 && removedItems.indexOf(item) < 0) michael@0: total++; michael@0: } michael@0: michael@0: let separator = this.contextMenuSeparator; michael@0: if (total == 0) { michael@0: this.assert.ok(!separator || separator.hidden, michael@0: "separator should not be present"); michael@0: } michael@0: else { michael@0: this.assert.ok(separator && !separator.hidden, michael@0: "separator should be present"); michael@0: } michael@0: michael@0: let mainNodes = this.browserWindow.document.querySelectorAll("#contentAreaContextMenu > ." + ITEM_CLASS); michael@0: let overflowNodes = this.browserWindow.document.querySelectorAll("." + OVERFLOW_POPUP_CLASS + " > ." + ITEM_CLASS); michael@0: michael@0: this.assert.ok(mainNodes.length == 0 || overflowNodes.length == 0, michael@0: "Should only see nodes at the top level or in overflow"); michael@0: michael@0: let overflow = this.overflowSubmenu; michael@0: if (this.shouldOverflow(total)) { michael@0: this.assert.ok(overflow && !overflow.hidden, michael@0: "overflow menu should be present"); michael@0: this.assert.equal(mainNodes.length, 0, michael@0: "should be no items in the main context menu"); michael@0: } michael@0: else { michael@0: this.assert.ok(!overflow || overflow.hidden, michael@0: "overflow menu should not be present"); michael@0: // When visible nodes == 0 they could be in overflow or top level michael@0: if (total > 0) { michael@0: this.assert.equal(overflowNodes.length, 0, michael@0: "should be no items in the overflow context menu"); michael@0: } michael@0: } michael@0: michael@0: // Iterate over wherever the nodes have ended up michael@0: let nodes = mainNodes.length ? mainNodes : overflowNodes; michael@0: this.checkNodes(nodes, presentItems, absentItems, removedItems) michael@0: let pos = 0; michael@0: }, michael@0: michael@0: // Recurses through the item hierarchy of presentItems comparing it to the michael@0: // node hierarchy of nodes. Any items in removedItems will be skipped (so michael@0: // should not exist in the XUL), any items in absentItems must exist and be michael@0: // hidden michael@0: checkNodes: function (nodes, presentItems, absentItems, removedItems) { michael@0: let pos = 0; michael@0: for (let item of presentItems) { michael@0: // Removed items shouldn't be in the list michael@0: if (removedItems.indexOf(item) >= 0) michael@0: continue; michael@0: michael@0: if (nodes.length <= pos) { michael@0: this.assert.ok(false, "Not enough nodes"); michael@0: return; michael@0: } michael@0: michael@0: let hidden = absentItems.indexOf(item) >= 0; michael@0: michael@0: this.checkItemElt(nodes[pos], item); michael@0: this.assert.equal(nodes[pos].hidden, hidden, michael@0: "hidden should be set correctly"); michael@0: michael@0: // The contents of hidden menus doesn't matter so much michael@0: if (!hidden && this.getItemType(item) == "Menu") { michael@0: this.assert.equal(nodes[pos].firstChild.localName, "menupopup", michael@0: "menu XUL should contain a menupopup"); michael@0: this.checkNodes(nodes[pos].firstChild.childNodes, item.items, absentItems, removedItems); michael@0: } michael@0: michael@0: if (pos > 0) michael@0: this.assert.equal(nodes[pos].previousSibling, nodes[pos - 1], michael@0: "nodes should all be in the same group"); michael@0: pos++; michael@0: } michael@0: michael@0: this.assert.equal(nodes.length, pos, michael@0: "should have checked all the XUL nodes"); michael@0: }, michael@0: michael@0: // Attaches an event listener to node. The listener is automatically removed michael@0: // when it's fired (so it's assumed it will fire), and callback is called michael@0: // after a short delay. Since the module we're testing relies on the same michael@0: // event listeners to do its work, this is to give them a little breathing michael@0: // room before callback runs. Inside callback |this| is this object. michael@0: // Optionally you can pass a function to test if the event is the event you michael@0: // want. michael@0: delayedEventListener: function (node, event, callback, useCapture, isValid) { michael@0: const self = this; michael@0: node.addEventListener(event, function handler(evt) { michael@0: if (isValid && !isValid(evt)) michael@0: return; michael@0: node.removeEventListener(event, handler, useCapture); michael@0: timer.setTimeout(function () { michael@0: try { michael@0: callback.call(self, evt); michael@0: } michael@0: catch (err) { michael@0: self.assert.fail(err); michael@0: self.end(); michael@0: } michael@0: }, 20); michael@0: }, useCapture); michael@0: }, michael@0: michael@0: // Call to finish the test. michael@0: done: function () { michael@0: const self = this; michael@0: function commonDone() { michael@0: this.closeTab(); michael@0: michael@0: while (this.loaders.length) { michael@0: this.loaders[0].unload(); michael@0: } michael@0: michael@0: require("sdk/preferences/service").set(OVERFLOW_THRESH_PREF, self.overflowThreshValue); michael@0: michael@0: this.end(); michael@0: } michael@0: michael@0: function closeBrowserWindow() { michael@0: if (this.oldBrowserWindow) { michael@0: this.delayedEventListener(this.browserWindow, "unload", commonDone, michael@0: false); michael@0: this.browserWindow.close(); michael@0: this.browserWindow = this.oldBrowserWindow; michael@0: delete this.oldBrowserWindow; michael@0: } michael@0: else { michael@0: commonDone.call(this); michael@0: } michael@0: }; michael@0: michael@0: if (this.contextMenuPopup.state == "closed") { michael@0: closeBrowserWindow.call(this); michael@0: } michael@0: else { michael@0: this.delayedEventListener(this.contextMenuPopup, "popuphidden", michael@0: function () closeBrowserWindow.call(this), michael@0: false); michael@0: this.contextMenuPopup.hidePopup(); michael@0: } michael@0: }, michael@0: michael@0: closeTab: function() { michael@0: if (this.tab) { michael@0: this.tabBrowser.removeTab(this.tab); michael@0: this.tabBrowser.selectedTab = this.oldSelectedTab; michael@0: this.tab = null; michael@0: } michael@0: }, michael@0: michael@0: // Returns the DOM element in popup corresponding to item. michael@0: // WARNING: The element is found by comparing labels, so don't give two items michael@0: // the same label. michael@0: getItemElt: function (popup, item) { michael@0: let nodes = popup.childNodes; michael@0: for (let i = nodes.length - 1; i >= 0; i--) { michael@0: if (this.getItemType(item) === "Separator") { michael@0: if (nodes[i].localName === "menuseparator") michael@0: return nodes[i]; michael@0: } michael@0: else if (nodes[i].getAttribute("label") === item.label) michael@0: return nodes[i]; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: // Returns "Item", "Menu", or "Separator". michael@0: getItemType: function (item) { michael@0: // Could use instanceof here, but that would require accessing the loader michael@0: // that created the item, and I don't want to A) somehow search through the michael@0: // this.loaders list to find it, and B) assume there are any live loaders at michael@0: // all. michael@0: return /^\[object (Item|Menu|Separator)/.exec(item.toString())[1]; michael@0: }, michael@0: michael@0: // Returns a wrapper around a new loader: { loader, cm, unload, globalScope }. michael@0: // loader is a Cuddlefish sandboxed loader, cm is the context menu module, michael@0: // globalScope is the context menu module's global scope, and unload is a michael@0: // function that unloads the loader and associated resources. michael@0: newLoader: function () { michael@0: const self = this; michael@0: let loader = Loader(module); michael@0: let wrapper = { michael@0: loader: loader, michael@0: cm: loader.require("sdk/context-menu"), michael@0: globalScope: loader.sandbox("sdk/context-menu"), michael@0: unload: function () { michael@0: loader.unload(); michael@0: let idx = self.loaders.indexOf(wrapper); michael@0: if (idx < 0) michael@0: throw new Error("Test error: tried to unload nonexistent loader"); michael@0: self.loaders.splice(idx, 1); michael@0: } michael@0: }; michael@0: this.loaders.push(wrapper); michael@0: return wrapper; michael@0: }, michael@0: michael@0: // As above but the loader has private-browsing support enabled. michael@0: newPrivateLoader: function() { michael@0: let base = require("@loader/options"); michael@0: michael@0: // Clone current loader's options adding the private-browsing permission michael@0: let options = merge({}, base, { michael@0: metadata: merge({}, base.metadata || {}, { michael@0: permissions: merge({}, base.metadata.permissions || {}, { michael@0: 'private-browsing': true michael@0: }) michael@0: }) michael@0: }); michael@0: michael@0: const self = this; michael@0: let loader = Loader(module, null, options); michael@0: let wrapper = { michael@0: loader: loader, michael@0: cm: loader.require("sdk/context-menu"), michael@0: globalScope: loader.sandbox("sdk/context-menu"), michael@0: unload: function () { michael@0: loader.unload(); michael@0: let idx = self.loaders.indexOf(wrapper); michael@0: if (idx < 0) michael@0: throw new Error("Test error: tried to unload nonexistent loader"); michael@0: self.loaders.splice(idx, 1); michael@0: } michael@0: }; michael@0: this.loaders.push(wrapper); michael@0: return wrapper; michael@0: }, michael@0: michael@0: // Returns true if the count crosses the overflow threshold. michael@0: shouldOverflow: function (count) { michael@0: return count > michael@0: (this.loaders.length ? michael@0: this.loaders[0].loader.require("sdk/preferences/service"). michael@0: get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) : michael@0: OVERFLOW_THRESH_DEFAULT); michael@0: }, michael@0: michael@0: // Opens the context menu on the current page. If targetNode is null, the michael@0: // menu is opened in the top-left corner. onShowncallback is passed the michael@0: // popup. michael@0: showMenu: function(targetNode, onshownCallback) { michael@0: function sendEvent() { michael@0: this.delayedEventListener(this.browserWindow, "popupshowing", michael@0: function (e) { michael@0: let popup = e.target; michael@0: onshownCallback.call(this, popup); michael@0: }, false); michael@0: michael@0: let rect = targetNode ? michael@0: targetNode.getBoundingClientRect() : michael@0: { left: 0, top: 0, width: 0, height: 0 }; michael@0: let contentWin = targetNode ? targetNode.ownerDocument.defaultView michael@0: : this.browserWindow.content; michael@0: contentWin. michael@0: QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils). michael@0: sendMouseEvent("contextmenu", michael@0: rect.left + (rect.width / 2), michael@0: rect.top + (rect.height / 2), michael@0: 2, 1, 0); michael@0: } michael@0: michael@0: // If a new tab or window has not yet been opened, open a new tab now. For michael@0: // some reason using the tab already opened when the test starts causes michael@0: // leaks. See bug 566351 for details. michael@0: if (!targetNode && !this.oldSelectedTab && !this.oldBrowserWindow) { michael@0: this.oldSelectedTab = this.tabBrowser.selectedTab; michael@0: this.tab = this.tabBrowser.addTab("about:blank"); michael@0: let browser = this.tabBrowser.getBrowserForTab(this.tab); michael@0: michael@0: this.delayedEventListener(browser, "load", function () { michael@0: this.tabBrowser.selectedTab = this.tab; michael@0: sendEvent.call(this); michael@0: }, true); michael@0: } michael@0: else michael@0: sendEvent.call(this); michael@0: }, michael@0: michael@0: hideMenu: function(onhiddenCallback) { michael@0: this.delayedEventListener(this.browserWindow, "popuphidden", onhiddenCallback); michael@0: michael@0: this.contextMenuPopup.hidePopup(); michael@0: }, michael@0: michael@0: // Opens a new browser window. The window will be closed automatically when michael@0: // done() is called. michael@0: withNewWindow: function (onloadCallback) { michael@0: let win = this.browserWindow.OpenBrowserWindow(); michael@0: this.delayedEventListener(win, "load", onloadCallback, true); michael@0: this.oldBrowserWindow = this.browserWindow; michael@0: this.browserWindow = win; michael@0: }, michael@0: michael@0: // Opens a new private browser window. The window will be closed michael@0: // automatically when done() is called. michael@0: withNewPrivateWindow: function (onloadCallback) { michael@0: let win = this.browserWindow.OpenBrowserWindow({private: true}); michael@0: this.delayedEventListener(win, "load", onloadCallback, true); michael@0: this.oldBrowserWindow = this.browserWindow; michael@0: this.browserWindow = win; michael@0: }, michael@0: michael@0: // Opens a new tab with our test page in the current window. The tab will michael@0: // be closed automatically when done() is called. michael@0: withTestDoc: function (onloadCallback) { michael@0: this.oldSelectedTab = this.tabBrowser.selectedTab; michael@0: this.tab = this.tabBrowser.addTab(TEST_DOC_URL); michael@0: let browser = this.tabBrowser.getBrowserForTab(this.tab); michael@0: michael@0: this.delayedEventListener(browser, "load", function () { michael@0: this.tabBrowser.selectedTab = this.tab; michael@0: onloadCallback.call(this, browser.contentWindow, browser.contentDocument); michael@0: }, true, function(evt) { michael@0: return evt.target.location == TEST_DOC_URL; michael@0: }); michael@0: } michael@0: }; michael@0: michael@0: require('sdk/test').run(exports);