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