diff options
Diffstat (limited to 'addon-sdk/source/test/context-menu/test-helper.js')
-rw-r--r-- | addon-sdk/source/test/context-menu/test-helper.js | 539 |
1 files changed, 0 insertions, 539 deletions
diff --git a/addon-sdk/source/test/context-menu/test-helper.js b/addon-sdk/source/test/context-menu/test-helper.js deleted file mode 100644 index fb315a5d3..000000000 --- a/addon-sdk/source/test/context-menu/test-helper.js +++ /dev/null @@ -1,539 +0,0 @@ -/* 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"; - -const { Ci } = require("chrome"); -const { getMostRecentBrowserWindow } = require('sdk/window/utils'); -const { Loader } = require('sdk/test/loader'); -const { merge } = require("sdk/util/object"); -const observers = require("sdk/system/events"); -const { defer } = require("sdk/core/promise"); -const timer = require("sdk/timers"); - -// 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(/context-menu\/test-helper\.js$/, "test-context-menu.html"); - -// This makes it easier to run tests by handling things like opening the menu, -// opening new windows, making assertions, etc. Methods on |test| can be called -// on instances of this class. Don't forget to call done() to end the test! -// WARNING: This looks up items in popups by comparing labels, so don't give two -// items the same label. -function TestHelper(assert, done) { - // Methods on the wrapped test can be called on this object. - for (var prop in assert) - this[prop] = (...args) => assert[prop].apply(assert, args); - this.assert = assert; - this.end = done; - this.loaders = []; - this.browserWindow = getMostRecentBrowserWindow(); - this.overflowThreshValue = require("sdk/preferences/service"). - get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT); - this.done = this.done.bind(this); -} - -TestHelper.prototype = { - get contextMenuPopup() { - return this.browserWindow.document.getElementById("contentAreaContextMenu"); - }, - - get contextMenuSeparator() { - return this.browserWindow.document.querySelector("." + SEPARATOR_CLASS); - }, - - get overflowPopup() { - return this.browserWindow.document.querySelector("." + OVERFLOW_POPUP_CLASS); - }, - - get overflowSubmenu() { - return this.browserWindow.document.querySelector("." + OVERFLOW_MENU_CLASS); - }, - - get tabBrowser() { - return this.browserWindow.gBrowser; - }, - - // Asserts that elt, a DOM element representing item, looks OK. - checkItemElt: function (elt, item) { - let itemType = this.getItemType(item); - - switch (itemType) { - case "Item": - this.assert.equal(elt.localName, "menuitem", - "Item DOM element should be a xul:menuitem"); - if (typeof(item.data) === "string") { - this.assert.equal(elt.getAttribute("value"), item.data, - "Item should have correct data"); - } - break - case "Menu": - this.assert.equal(elt.localName, "menu", - "Menu DOM element should be a xul:menu"); - let subPopup = elt.firstChild; - this.assert.ok(subPopup, "xul:menu should have a child"); - this.assert.equal(subPopup.localName, "menupopup", - "xul:menu's first child should be a menupopup"); - break; - case "Separator": - this.assert.equal(elt.localName, "menuseparator", - "Separator DOM element should be a xul:menuseparator"); - break; - } - - if (itemType === "Item" || itemType === "Menu") { - this.assert.equal(elt.getAttribute("label"), item.label, - "Item should have correct title"); - - // validate accesskey prop - if (item.accesskey) { - this.assert.equal(elt.getAttribute("accesskey"), - item.accesskey, - "Item should have correct accesskey"); - } - else { - this.assert.equal(elt.getAttribute("accesskey"), - "", - "Item should not have accesskey"); - } - - // validate image prop - if (typeof(item.image) === "string") { - this.assert.equal(elt.getAttribute("image"), item.image, - "Item should have correct image"); - if (itemType === "Menu") - this.assert.ok(elt.classList.contains("menu-iconic"), - "Menus with images should have the correct class") - else - this.assert.ok(elt.classList.contains("menuitem-iconic"), - "Items with images should have the correct class") - } - else { - this.assert.ok(!elt.getAttribute("image"), - "Item should not have image"); - this.assert.ok(!elt.classList.contains("menu-iconic") && !elt.classList.contains("menuitem-iconic"), - "The iconic classes should not be present") - } - } - }, - - // Asserts that the context menu looks OK given the arguments. presentItems - // are items that have been added to the menu. absentItems are items that - // shouldn't match the current context. removedItems are items that have been - // removed from the menu. - checkMenu: function (presentItems, absentItems, removedItems) { - // Count up how many top-level items there are - let total = 0; - for (let item of presentItems) { - if (absentItems.indexOf(item) < 0 && removedItems.indexOf(item) < 0) - total++; - } - - let separator = this.contextMenuSeparator; - if (total == 0) { - this.assert.ok(!separator || separator.hidden, - "separator should not be present"); - } - else { - this.assert.ok(separator && !separator.hidden, - "separator should be present"); - } - - let mainNodes = this.browserWindow.document.querySelectorAll("#contentAreaContextMenu > ." + ITEM_CLASS); - let overflowNodes = this.browserWindow.document.querySelectorAll("." + OVERFLOW_POPUP_CLASS + " > ." + ITEM_CLASS); - - this.assert.ok(mainNodes.length == 0 || overflowNodes.length == 0, - "Should only see nodes at the top level or in overflow"); - - let overflow = this.overflowSubmenu; - if (this.shouldOverflow(total)) { - this.assert.ok(overflow && !overflow.hidden, - "overflow menu should be present"); - this.assert.equal(mainNodes.length, 0, - "should be no items in the main context menu"); - } - else { - this.assert.ok(!overflow || overflow.hidden, - "overflow menu should not be present"); - // When visible nodes == 0 they could be in overflow or top level - if (total > 0) { - this.assert.equal(overflowNodes.length, 0, - "should be no items in the overflow context menu"); - } - } - - // Iterate over wherever the nodes have ended up - let nodes = mainNodes.length ? mainNodes : overflowNodes; - this.checkNodes(nodes, presentItems, absentItems, removedItems) - let pos = 0; - }, - - // Recurses through the item hierarchy of presentItems comparing it to the - // node hierarchy of nodes. Any items in removedItems will be skipped (so - // should not exist in the XUL), any items in absentItems must exist and be - // hidden - checkNodes: function (nodes, presentItems, absentItems, removedItems) { - let pos = 0; - for (let item of presentItems) { - // Removed items shouldn't be in the list - if (removedItems.indexOf(item) >= 0) - continue; - - if (nodes.length <= pos) { - this.assert.ok(false, "Not enough nodes"); - return; - } - - let hidden = absentItems.indexOf(item) >= 0; - - this.checkItemElt(nodes[pos], item); - this.assert.equal(nodes[pos].hidden, hidden, - "hidden should be set correctly"); - - // The contents of hidden menus doesn't matter so much - if (!hidden && this.getItemType(item) == "Menu") { - this.assert.equal(nodes[pos].firstChild.localName, "menupopup", - "menu XUL should contain a menupopup"); - this.checkNodes(nodes[pos].firstChild.childNodes, item.items, absentItems, removedItems); - } - - if (pos > 0) - this.assert.equal(nodes[pos].previousSibling, nodes[pos - 1], - "nodes should all be in the same group"); - pos++; - } - - this.assert.equal(nodes.length, pos, - "should have checked all the XUL nodes"); - }, - - // Attaches an event listener to node. The listener is automatically removed - // when it's fired (so it's assumed it will fire), and callback is called - // after a short delay. Since the module we're testing relies on the same - // event listeners to do its work, this is to give them a little breathing - // room before callback runs. Inside callback |this| is this object. - // Optionally you can pass a function to test if the event is the event you - // want. - delayedEventListener: function (node, event, callback, useCapture, isValid) { - const self = this; - node.addEventListener(event, function handler(evt) { - if (isValid && !isValid(evt)) - return; - node.removeEventListener(event, handler, useCapture); - timer.setTimeout(function () { - try { - callback.call(self, evt); - } - catch (err) { - self.assert.fail(err); - self.end(); - } - }, 20); - }, useCapture); - }, - - // Call to finish the test. - done: function () { - const self = this; - function commonDone() { - this.closeTab(); - - while (this.loaders.length) { - this.loaders[0].unload(); - } - - require("sdk/preferences/service").set(OVERFLOW_THRESH_PREF, self.overflowThreshValue); - - this.end(); - } - - function closeBrowserWindow() { - if (this.oldBrowserWindow) { - this.delayedEventListener(this.browserWindow, "unload", commonDone, - false); - this.browserWindow.close(); - this.browserWindow = this.oldBrowserWindow; - delete this.oldBrowserWindow; - } - else { - commonDone.call(this); - } - }; - - if (this.contextMenuPopup.state == "closed") { - closeBrowserWindow.call(this); - } - else { - this.delayedEventListener(this.contextMenuPopup, "popuphidden", - function () { - return closeBrowserWindow.call(this); - }, - false); - this.contextMenuPopup.hidePopup(); - } - }, - - closeTab: function() { - if (this.tab) { - this.tabBrowser.removeTab(this.tab); - this.tabBrowser.selectedTab = this.oldSelectedTab; - this.tab = null; - } - }, - - // Returns the DOM element in popup corresponding to item. - // WARNING: The element is found by comparing labels, so don't give two items - // the same label. - getItemElt: function (popup, item) { - let nodes = popup.childNodes; - for (let i = nodes.length - 1; i >= 0; i--) { - if (this.getItemType(item) === "Separator") { - if (nodes[i].localName === "menuseparator") - return nodes[i]; - } - else if (nodes[i].getAttribute("label") === item.label) - return nodes[i]; - } - return null; - }, - - // Returns "Item", "Menu", or "Separator". - getItemType: function (item) { - // Could use instanceof here, but that would require accessing the loader - // that created the item, and I don't want to A) somehow search through the - // this.loaders list to find it, and B) assume there are any live loaders at - // all. - return /^\[object (Item|Menu|Separator)/.exec(item.toString())[1]; - }, - - // Returns a wrapper around a new loader: { loader, cm, unload, globalScope }. - // loader is a Cuddlefish sandboxed loader, cm is the context menu module, - // globalScope is the context menu module's global scope, and unload is a - // function that unloads the loader and associated resources. - newLoader: function () { - const self = this; - const selfModule = require('sdk/self'); - let loader = Loader(module, null, null, { - modules: { - "sdk/self": merge({}, selfModule, { - data: merge({}, selfModule.data, require("../fixtures")) - }) - } - }); - - let wrapper = { - loader: loader, - cm: loader.require("sdk/context-menu"), - globalScope: loader.sandbox("sdk/context-menu"), - unload: function () { - loader.unload(); - let idx = self.loaders.indexOf(wrapper); - if (idx < 0) - throw new Error("Test error: tried to unload nonexistent loader"); - self.loaders.splice(idx, 1); - } - }; - this.loaders.push(wrapper); - return wrapper; - }, - - // As above but the loader has private-browsing support enabled. - newPrivateLoader: function() { - let base = require("@loader/options"); - - // Clone current loader's options adding the private-browsing permission - let options = merge({}, base, { - metadata: merge({}, base.metadata || {}, { - permissions: merge({}, base.metadata.permissions || {}, { - 'private-browsing': true - }) - }) - }); - - const self = this; - let loader = Loader(module, null, options); - let wrapper = { - loader: loader, - cm: loader.require("sdk/context-menu"), - globalScope: loader.sandbox("sdk/context-menu"), - unload: function () { - loader.unload(); - let idx = self.loaders.indexOf(wrapper); - if (idx < 0) - throw new Error("Test error: tried to unload nonexistent loader"); - self.loaders.splice(idx, 1); - } - }; - this.loaders.push(wrapper); - return wrapper; - }, - - // Returns true if the count crosses the overflow threshold. - shouldOverflow: function (count) { - return count > - (this.loaders.length ? - this.loaders[0].loader.require("sdk/preferences/service"). - get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) : - OVERFLOW_THRESH_DEFAULT); - }, - - // Loads scripts necessary in the content process - loadFrameScript: function(browser = this.browserWindow.gBrowser.selectedBrowser) { - function frame_script() { - let { interfaces: Ci } = Components; - addMessageListener('test:contextmenu', ({ data: { selectors } }) => { - let targetNode = null; - let contentWin = content; - if (selectors) { - while (selectors.length) { - targetNode = contentWin.document.querySelector(selectors.shift()); - if (selectors.length) - contentWin = targetNode.contentWindow; - } - } - - let rect = targetNode ? - targetNode.getBoundingClientRect() : - { left: 0, top: 0, width: 0, height: 0 }; - contentWin.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .sendMouseEvent('contextmenu', - rect.left + (rect.width / 2), - rect.top + (rect.height / 2), - 2, 1, 0); - }); - - addMessageListener('test:ping', () => { - sendAsyncMessage('test:pong'); - }); - - addMessageListener('test:select', ({ data: { selector, start, end } }) => { - let element = content.document.querySelector(selector); - element.focus(); - if (end === null) - end = element.value.length; - element.setSelectionRange(start, end); - }); - } - - let messageManager = browser.messageManager; - messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")();", true); - }, - - selectRange: function(selector, start, end) { - let messageManager = this.browserWindow.gBrowser.selectedBrowser.messageManager; - messageManager.sendAsyncMessage('test:select', { selector, start, end }); - }, - - // Opens the context menu on the current page. If selectors is null, the - // menu is opened in the top-left corner. onShowncallback is passed the - // popup. selectors is an array of selectors. Starting from the main document - // each selector points to an iframe, the last selector gives the target node. - // In the simple case of a single selector just that string can be passed - // instead of an array - showMenu: function(selectors, onshownCallback) { - let { promise, resolve } = defer(); - - if (selectors && !Array.isArray(selectors)) - selectors = [selectors]; - - let sendEvent = () => { - let menu = this.browserWindow.document.getElementById("contentAreaContextMenu"); - this.delayedEventListener(menu, "popupshowing", - function (e) { - let popup = e.target; - if (onshownCallback) { - onshownCallback.call(this, popup); - } - resolve(popup); - }, false); - - let messageManager = this.browserWindow.gBrowser.selectedBrowser.messageManager; - messageManager.sendAsyncMessage('test:contextmenu', { selectors }); - } - - // Bounces an asynchronous message through the browser message manager. - // This ensures that any pending messages have been delivered to the frame - // scripts and so the remote proxies have been updated - let flushMessages = () => { - let listener = () => { - messageManager.removeMessageListener('test:pong', listener); - sendEvent(); - }; - - let messageManager = this.browserWindow.gBrowser.selectedBrowser.messageManager; - messageManager.addMessageListener('test:pong', listener); - messageManager.sendAsyncMessage('test:ping'); - } - - // If a new tab or window has not yet been opened, open a new tab now. For - // some reason using the tab already opened when the test starts causes - // leaks. See bug 566351 for details. - if (!selectors && !this.oldSelectedTab && !this.oldBrowserWindow) { - this.oldSelectedTab = this.tabBrowser.selectedTab; - this.tab = this.tabBrowser.addTab("about:blank"); - let browser = this.tabBrowser.getBrowserForTab(this.tab); - - this.delayedEventListener(browser, "load", function () { - this.tabBrowser.selectedTab = this.tab; - this.loadFrameScript(); - flushMessages(); - }, true); - } - else { - flushMessages(); - } - - return promise; - }, - - hideMenu: function(onhiddenCallback) { - this.delayedEventListener(this.browserWindow, "popuphidden", onhiddenCallback); - - this.contextMenuPopup.hidePopup(); - }, - - // Opens a new browser window. The window will be closed automatically when - // done() is called. - withNewWindow: function (onloadCallback, makePrivate = false) { - let win = this.browserWindow.OpenBrowserWindow({ private: makePrivate }); - observers.once("browser-delayed-startup-finished", () => { - // Open a new tab so we can make sure it is remote and loaded - win.gBrowser.selectedTab = win.gBrowser.addTab(); - this.loadFrameScript(); - this.delayedEventListener(win.gBrowser.selectedBrowser, "load", onloadCallback, true); - }); - this.oldBrowserWindow = this.browserWindow; - this.browserWindow = win; - }, - - // Opens a new private browser window. The window will be closed - // automatically when done() is called. - withNewPrivateWindow: function (onloadCallback) { - this.withNewWindow(onloadCallback, true); - }, - - // Opens a new tab with our test page in the current window. The tab will - // be closed automatically when done() is called. - withTestDoc: function (onloadCallback) { - this.oldSelectedTab = this.tabBrowser.selectedTab; - this.tab = this.tabBrowser.addTab(TEST_DOC_URL); - let browser = this.tabBrowser.getBrowserForTab(this.tab); - - this.delayedEventListener(browser, "load", function () { - this.tabBrowser.selectedTab = this.tab; - this.loadFrameScript(); - onloadCallback.call(this, browser.contentWindow, browser.contentDocument); - }, true, function(evt) { - return evt.target.location == TEST_DOC_URL; - }); - } -}; -exports.TestHelper = TestHelper; |