1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/test/test-context-menu.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,4044 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 'use strict'; 1.8 + 1.9 +let { Cc, Ci } = require("chrome"); 1.10 + 1.11 +require("sdk/context-menu"); 1.12 + 1.13 +const { Loader } = require('sdk/test/loader'); 1.14 +const timer = require("sdk/timers"); 1.15 +const { merge } = require("sdk/util/object"); 1.16 + 1.17 +// These should match the same constants in the module. 1.18 +const ITEM_CLASS = "addon-context-menu-item"; 1.19 +const SEPARATOR_CLASS = "addon-context-menu-separator"; 1.20 +const OVERFLOW_THRESH_DEFAULT = 10; 1.21 +const OVERFLOW_THRESH_PREF = 1.22 + "extensions.addon-sdk.context-menu.overflowThreshold"; 1.23 +const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu"; 1.24 +const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup"; 1.25 + 1.26 +const TEST_DOC_URL = module.uri.replace(/\.js$/, ".html"); 1.27 +const data = require("./fixtures"); 1.28 + 1.29 +// Tests that when present the separator is placed before the separator from 1.30 +// the old context-menu module 1.31 +exports.testSeparatorPosition = function (assert, done) { 1.32 + let test = new TestHelper(assert, done); 1.33 + let loader = test.newLoader(); 1.34 + 1.35 + // Create the old separator 1.36 + let oldSeparator = test.contextMenuPopup.ownerDocument.createElement("menuseparator"); 1.37 + oldSeparator.id = "jetpack-context-menu-separator"; 1.38 + test.contextMenuPopup.appendChild(oldSeparator); 1.39 + 1.40 + // Create an item. 1.41 + let item = new loader.cm.Item({ label: "item" }); 1.42 + 1.43 + test.showMenu(null, function (popup) { 1.44 + assert.equal(test.contextMenuSeparator.nextSibling.nextSibling, oldSeparator, 1.45 + "New separator should appear before the old one"); 1.46 + test.contextMenuPopup.removeChild(oldSeparator); 1.47 + test.done(); 1.48 + }); 1.49 +}; 1.50 + 1.51 +// Destroying items that were previously created should cause them to be absent 1.52 +// from the menu. 1.53 +exports.testConstructDestroy = function (assert, done) { 1.54 + let test = new TestHelper(assert, done); 1.55 + let loader = test.newLoader(); 1.56 + 1.57 + // Create an item. 1.58 + let item = new loader.cm.Item({ label: "item" }); 1.59 + assert.equal(item.parentMenu, loader.cm.contentContextMenu, 1.60 + "item's parent menu should be correct"); 1.61 + 1.62 + test.showMenu(null, function (popup) { 1.63 + 1.64 + // It should be present when the menu is shown. 1.65 + test.checkMenu([item], [], []); 1.66 + popup.hidePopup(); 1.67 + 1.68 + // Destroy the item. Multiple destroys should be harmless. 1.69 + item.destroy(); 1.70 + item.destroy(); 1.71 + test.showMenu(null, function (popup) { 1.72 + 1.73 + // It should be removed from the menu. 1.74 + test.checkMenu([item], [], [item]); 1.75 + test.done(); 1.76 + }); 1.77 + }); 1.78 +}; 1.79 + 1.80 + 1.81 +// Destroying an item twice should not cause an error. 1.82 +exports.testDestroyTwice = function (assert, done) { 1.83 + let test = new TestHelper(assert, done); 1.84 + let loader = test.newLoader(); 1.85 + 1.86 + let item = new loader.cm.Item({ label: "item" }); 1.87 + item.destroy(); 1.88 + item.destroy(); 1.89 + 1.90 + test.pass("Destroying an item twice should not cause an error."); 1.91 + test.done(); 1.92 +}; 1.93 + 1.94 + 1.95 +// CSS selector contexts should cause their items to be present in the menu 1.96 +// when the menu is invoked on nodes that match the selectors. 1.97 +exports.testSelectorContextMatch = function (assert, done) { 1.98 + let test = new TestHelper(assert, done); 1.99 + let loader = test.newLoader(); 1.100 + 1.101 + let item = new loader.cm.Item({ 1.102 + label: "item", 1.103 + data: "item", 1.104 + context: loader.cm.SelectorContext("img") 1.105 + }); 1.106 + 1.107 + test.withTestDoc(function (window, doc) { 1.108 + test.showMenu(doc.getElementById("image"), function (popup) { 1.109 + test.checkMenu([item], [], []); 1.110 + test.done(); 1.111 + }); 1.112 + }); 1.113 +}; 1.114 + 1.115 + 1.116 +// CSS selector contexts should cause their items to be present in the menu 1.117 +// when the menu is invoked on nodes that have ancestors that match the 1.118 +// selectors. 1.119 +exports.testSelectorAncestorContextMatch = function (assert, done) { 1.120 + let test = new TestHelper(assert, done); 1.121 + let loader = test.newLoader(); 1.122 + 1.123 + let item = new loader.cm.Item({ 1.124 + label: "item", 1.125 + data: "item", 1.126 + context: loader.cm.SelectorContext("a[href]") 1.127 + }); 1.128 + 1.129 + test.withTestDoc(function (window, doc) { 1.130 + test.showMenu(doc.getElementById("span-link"), function (popup) { 1.131 + test.checkMenu([item], [], []); 1.132 + test.done(); 1.133 + }); 1.134 + }); 1.135 +}; 1.136 + 1.137 + 1.138 +// CSS selector contexts should cause their items to be absent from the menu 1.139 +// when the menu is not invoked on nodes that match or have ancestors that 1.140 +// match the selectors. 1.141 +exports.testSelectorContextNoMatch = function (assert, done) { 1.142 + let test = new TestHelper(assert, done); 1.143 + let loader = test.newLoader(); 1.144 + 1.145 + let item = new loader.cm.Item({ 1.146 + label: "item", 1.147 + data: "item", 1.148 + context: loader.cm.SelectorContext("img") 1.149 + }); 1.150 + 1.151 + test.showMenu(null, function (popup) { 1.152 + test.checkMenu([item], [item], []); 1.153 + test.done(); 1.154 + }); 1.155 +}; 1.156 + 1.157 + 1.158 +// Page contexts should cause their items to be present in the menu when the 1.159 +// menu is not invoked on an active element. 1.160 +exports.testPageContextMatch = function (assert, done) { 1.161 + let test = new TestHelper(assert, done); 1.162 + let loader = test.newLoader(); 1.163 + 1.164 + let items = [ 1.165 + new loader.cm.Item({ 1.166 + label: "item 0" 1.167 + }), 1.168 + new loader.cm.Item({ 1.169 + label: "item 1", 1.170 + context: undefined 1.171 + }), 1.172 + new loader.cm.Item({ 1.173 + label: "item 2", 1.174 + context: loader.cm.PageContext() 1.175 + }), 1.176 + new loader.cm.Item({ 1.177 + label: "item 3", 1.178 + context: [loader.cm.PageContext()] 1.179 + }) 1.180 + ]; 1.181 + 1.182 + test.showMenu(null, function (popup) { 1.183 + test.checkMenu(items, [], []); 1.184 + test.done(); 1.185 + }); 1.186 +}; 1.187 + 1.188 + 1.189 +// Page contexts should cause their items to be absent from the menu when the 1.190 +// menu is invoked on an active element. 1.191 +exports.testPageContextNoMatch = function (assert, done) { 1.192 + let test = new TestHelper(assert, done); 1.193 + let loader = test.newLoader(); 1.194 + 1.195 + let items = [ 1.196 + new loader.cm.Item({ 1.197 + label: "item 0" 1.198 + }), 1.199 + new loader.cm.Item({ 1.200 + label: "item 1", 1.201 + context: undefined 1.202 + }), 1.203 + new loader.cm.Item({ 1.204 + label: "item 2", 1.205 + context: loader.cm.PageContext() 1.206 + }), 1.207 + new loader.cm.Item({ 1.208 + label: "item 3", 1.209 + context: [loader.cm.PageContext()] 1.210 + }) 1.211 + ]; 1.212 + 1.213 + test.withTestDoc(function (window, doc) { 1.214 + test.showMenu(doc.getElementById("image"), function (popup) { 1.215 + test.checkMenu(items, items, []); 1.216 + test.done(); 1.217 + }); 1.218 + }); 1.219 +}; 1.220 + 1.221 + 1.222 +// Selection contexts should cause items to appear when a selection exists. 1.223 +exports.testSelectionContextMatch = function (assert, done) { 1.224 + let test = new TestHelper(assert, done); 1.225 + let loader = test.newLoader(); 1.226 + 1.227 + let item = loader.cm.Item({ 1.228 + label: "item", 1.229 + context: loader.cm.SelectionContext() 1.230 + }); 1.231 + 1.232 + test.withTestDoc(function (window, doc) { 1.233 + window.getSelection().selectAllChildren(doc.body); 1.234 + test.showMenu(null, function (popup) { 1.235 + test.checkMenu([item], [], []); 1.236 + test.done(); 1.237 + }); 1.238 + }); 1.239 +}; 1.240 + 1.241 + 1.242 +// Selection contexts should cause items to appear when a selection exists in 1.243 +// a text field. 1.244 +exports.testSelectionContextMatchInTextField = function (assert, done) { 1.245 + let test = new TestHelper(assert, done); 1.246 + let loader = test.newLoader(); 1.247 + 1.248 + let item = loader.cm.Item({ 1.249 + label: "item", 1.250 + context: loader.cm.SelectionContext() 1.251 + }); 1.252 + 1.253 + test.withTestDoc(function (window, doc) { 1.254 + let textfield = doc.getElementById("textfield"); 1.255 + textfield.setSelectionRange(0, textfield.value.length); 1.256 + test.showMenu(textfield, function (popup) { 1.257 + test.checkMenu([item], [], []); 1.258 + test.done(); 1.259 + }); 1.260 + }); 1.261 +}; 1.262 + 1.263 + 1.264 +// Selection contexts should not cause items to appear when a selection does 1.265 +// not exist in a text field. 1.266 +exports.testSelectionContextNoMatchInTextField = function (assert, done) { 1.267 + let test = new TestHelper(assert, done); 1.268 + let loader = test.newLoader(); 1.269 + 1.270 + let item = loader.cm.Item({ 1.271 + label: "item", 1.272 + context: loader.cm.SelectionContext() 1.273 + }); 1.274 + 1.275 + test.withTestDoc(function (window, doc) { 1.276 + let textfield = doc.getElementById("textfield"); 1.277 + textfield.setSelectionRange(0, 0); 1.278 + test.showMenu(textfield, function (popup) { 1.279 + test.checkMenu([item], [item], []); 1.280 + test.done(); 1.281 + }); 1.282 + }); 1.283 +}; 1.284 + 1.285 + 1.286 +// Selection contexts should not cause items to appear when a selection does 1.287 +// not exist. 1.288 +exports.testSelectionContextNoMatch = function (assert, done) { 1.289 + let test = new TestHelper(assert, done); 1.290 + let loader = test.newLoader(); 1.291 + 1.292 + let item = loader.cm.Item({ 1.293 + label: "item", 1.294 + context: loader.cm.SelectionContext() 1.295 + }); 1.296 + 1.297 + test.showMenu(null, function (popup) { 1.298 + test.checkMenu([item], [item], []); 1.299 + test.done(); 1.300 + }); 1.301 +}; 1.302 + 1.303 + 1.304 +// Selection contexts should cause items to appear when a selection exists even 1.305 +// for newly opened pages 1.306 +exports.testSelectionContextInNewTab = function (assert, done) { 1.307 + let test = new TestHelper(assert, done); 1.308 + let loader = test.newLoader(); 1.309 + 1.310 + let item = loader.cm.Item({ 1.311 + label: "item", 1.312 + context: loader.cm.SelectionContext() 1.313 + }); 1.314 + 1.315 + test.withTestDoc(function (window, doc) { 1.316 + let link = doc.getElementById("targetlink"); 1.317 + link.click(); 1.318 + 1.319 + test.delayedEventListener(this.tabBrowser, "load", function () { 1.320 + let browser = test.tabBrowser.selectedBrowser; 1.321 + let window = browser.contentWindow; 1.322 + let doc = browser.contentDocument; 1.323 + window.getSelection().selectAllChildren(doc.body); 1.324 + 1.325 + test.showMenu(null, function (popup) { 1.326 + test.checkMenu([item], [], []); 1.327 + popup.hidePopup(); 1.328 + 1.329 + test.tabBrowser.removeTab(test.tabBrowser.selectedTab); 1.330 + test.tabBrowser.selectedTab = test.tab; 1.331 + 1.332 + test.showMenu(null, function (popup) { 1.333 + test.checkMenu([item], [item], []); 1.334 + test.done(); 1.335 + }); 1.336 + }); 1.337 + }, true); 1.338 + }); 1.339 +}; 1.340 + 1.341 + 1.342 +// Selection contexts should work when right clicking a form button 1.343 +exports.testSelectionContextButtonMatch = function (assert, done) { 1.344 + let test = new TestHelper(assert, done); 1.345 + let loader = test.newLoader(); 1.346 + 1.347 + let item = loader.cm.Item({ 1.348 + label: "item", 1.349 + context: loader.cm.SelectionContext() 1.350 + }); 1.351 + 1.352 + test.withTestDoc(function (window, doc) { 1.353 + window.getSelection().selectAllChildren(doc.body); 1.354 + let button = doc.getElementById("button"); 1.355 + test.showMenu(button, function (popup) { 1.356 + test.checkMenu([item], [], []); 1.357 + test.done(); 1.358 + }); 1.359 + }); 1.360 +}; 1.361 + 1.362 + 1.363 +//Selection contexts should work when right clicking a form button 1.364 +exports.testSelectionContextButtonNoMatch = function (assert, done) { 1.365 + let test = new TestHelper(assert, done); 1.366 + let loader = test.newLoader(); 1.367 + 1.368 + let item = loader.cm.Item({ 1.369 + label: "item", 1.370 + context: loader.cm.SelectionContext() 1.371 + }); 1.372 + 1.373 + test.withTestDoc(function (window, doc) { 1.374 + let button = doc.getElementById("button"); 1.375 + test.showMenu(button, function (popup) { 1.376 + test.checkMenu([item], [item], []); 1.377 + test.done(); 1.378 + }); 1.379 + }); 1.380 +}; 1.381 + 1.382 + 1.383 +// URL contexts should cause items to appear on pages that match. 1.384 +exports.testURLContextMatch = function (assert, done) { 1.385 + let test = new TestHelper(assert, done); 1.386 + let loader = test.newLoader(); 1.387 + 1.388 + let items = [ 1.389 + loader.cm.Item({ 1.390 + label: "item 0", 1.391 + context: loader.cm.URLContext(TEST_DOC_URL) 1.392 + }), 1.393 + loader.cm.Item({ 1.394 + label: "item 1", 1.395 + context: loader.cm.URLContext([TEST_DOC_URL, "*.bogus.com"]) 1.396 + }), 1.397 + loader.cm.Item({ 1.398 + label: "item 2", 1.399 + context: loader.cm.URLContext([new RegExp(".*\\.html")]) 1.400 + }) 1.401 + ]; 1.402 + 1.403 + test.withTestDoc(function (window, doc) { 1.404 + test.showMenu(null, function (popup) { 1.405 + test.checkMenu(items, [], []); 1.406 + test.done(); 1.407 + }); 1.408 + }); 1.409 +}; 1.410 + 1.411 + 1.412 +// URL contexts should not cause items to appear on pages that do not match. 1.413 +exports.testURLContextNoMatch = function (assert, done) { 1.414 + let test = new TestHelper(assert, done); 1.415 + let loader = test.newLoader(); 1.416 + 1.417 + let items = [ 1.418 + loader.cm.Item({ 1.419 + label: "item 0", 1.420 + context: loader.cm.URLContext("*.bogus.com") 1.421 + }), 1.422 + loader.cm.Item({ 1.423 + label: "item 1", 1.424 + context: loader.cm.URLContext(["*.bogus.com", "*.gnarly.com"]) 1.425 + }), 1.426 + loader.cm.Item({ 1.427 + label: "item 2", 1.428 + context: loader.cm.URLContext([new RegExp(".*\\.js")]) 1.429 + }) 1.430 + ]; 1.431 + 1.432 + test.withTestDoc(function (window, doc) { 1.433 + test.showMenu(null, function (popup) { 1.434 + test.checkMenu(items, items, []); 1.435 + test.done(); 1.436 + }); 1.437 + }); 1.438 +}; 1.439 + 1.440 + 1.441 +// Removing a non-matching URL context after its item is created and the page is 1.442 +// loaded should cause the item's content script to be evaluated when the 1.443 +// context menu is next opened. 1.444 +exports.testURLContextRemove = function (assert, done) { 1.445 + let test = new TestHelper(assert, done); 1.446 + let loader = test.newLoader(); 1.447 + 1.448 + let shouldBeEvaled = false; 1.449 + let context = loader.cm.URLContext("*.bogus.com"); 1.450 + let item = loader.cm.Item({ 1.451 + label: "item", 1.452 + context: context, 1.453 + contentScript: 'self.postMessage("ok"); self.on("context", function () true);', 1.454 + onMessage: function (msg) { 1.455 + assert.ok(shouldBeEvaled, 1.456 + "content script should be evaluated when expected"); 1.457 + assert.equal(msg, "ok", "Should have received the right message"); 1.458 + shouldBeEvaled = false; 1.459 + } 1.460 + }); 1.461 + 1.462 + test.withTestDoc(function (window, doc) { 1.463 + test.showMenu(null, function (popup) { 1.464 + test.checkMenu([item], [item], []); 1.465 + 1.466 + item.context.remove(context); 1.467 + 1.468 + shouldBeEvaled = true; 1.469 + 1.470 + test.hideMenu(function () { 1.471 + test.showMenu(null, function (popup) { 1.472 + test.checkMenu([item], [], []); 1.473 + 1.474 + assert.ok(!shouldBeEvaled, 1.475 + "content script should have been evaluated"); 1.476 + 1.477 + test.hideMenu(function () { 1.478 + // Shouldn't get evaluated again 1.479 + test.showMenu(null, function (popup) { 1.480 + test.checkMenu([item], [], []); 1.481 + test.done(); 1.482 + }); 1.483 + }); 1.484 + }); 1.485 + }); 1.486 + }); 1.487 + }); 1.488 +}; 1.489 + 1.490 +// Loading a new page in the same tab should correctly start a new worker for 1.491 +// any content scripts 1.492 +exports.testPageReload = function (assert, done) { 1.493 + let test = new TestHelper(assert, done); 1.494 + let loader = test.newLoader(); 1.495 + 1.496 + let item = loader.cm.Item({ 1.497 + label: "Item", 1.498 + contentScript: "var doc = document; self.on('context', function(node) doc.body.getAttribute('showItem') == 'true');" 1.499 + }); 1.500 + 1.501 + test.withTestDoc(function (window, doc) { 1.502 + // Set a flag on the document that the item uses 1.503 + doc.body.setAttribute("showItem", "true"); 1.504 + 1.505 + test.showMenu(null, function (popup) { 1.506 + // With the attribute true the item should be visible in the menu 1.507 + test.checkMenu([item], [], []); 1.508 + test.hideMenu(function() { 1.509 + let browser = this.tabBrowser.getBrowserForTab(this.tab) 1.510 + test.delayedEventListener(browser, "load", function() { 1.511 + test.delayedEventListener(browser, "load", function() { 1.512 + window = browser.contentWindow; 1.513 + doc = window.document; 1.514 + 1.515 + // Set a flag on the document that the item uses 1.516 + doc.body.setAttribute("showItem", "false"); 1.517 + 1.518 + test.showMenu(null, function (popup) { 1.519 + // In the new document with the attribute false the item should be 1.520 + // hidden, but if the contentScript hasn't been reloaded it will 1.521 + // still see the old value 1.522 + test.checkMenu([item], [item], []); 1.523 + 1.524 + test.done(); 1.525 + }); 1.526 + }, true); 1.527 + browser.loadURI(TEST_DOC_URL, null, null); 1.528 + }, true); 1.529 + // Required to make sure we load a new page in history rather than 1.530 + // just reloading the current page which would unload it 1.531 + browser.loadURI("about:blank", null, null); 1.532 + }); 1.533 + }); 1.534 + }); 1.535 +}; 1.536 + 1.537 +// Closing a page after it's been used with a worker should cause the worker 1.538 +// to be destroyed 1.539 +/*exports.testWorkerDestroy = function (assert, done) { 1.540 + let test = new TestHelper(assert, done); 1.541 + let loader = test.newLoader(); 1.542 + 1.543 + let loadExpected = false; 1.544 + 1.545 + let item = loader.cm.Item({ 1.546 + label: "item", 1.547 + contentScript: 'self.postMessage("loaded"); self.on("detach", function () { console.log("saw detach"); self.postMessage("detach") });', 1.548 + onMessage: function (msg) { 1.549 + switch (msg) { 1.550 + case "loaded": 1.551 + assert.ok(loadExpected, "Should have seen the load event at the right time"); 1.552 + loadExpected = false; 1.553 + break; 1.554 + case "detach": 1.555 + test.done(); 1.556 + break; 1.557 + } 1.558 + } 1.559 + }); 1.560 + 1.561 + test.withTestDoc(function (window, doc) { 1.562 + loadExpected = true; 1.563 + test.showMenu(null, function (popup) { 1.564 + assert.ok(!loadExpected, "Should have seen a message"); 1.565 + 1.566 + test.checkMenu([item], [], []); 1.567 + 1.568 + test.closeTab(); 1.569 + }); 1.570 + }); 1.571 +};*/ 1.572 + 1.573 + 1.574 +// Content contexts that return true should cause their items to be present 1.575 +// in the menu. 1.576 +exports.testContentContextMatch = function (assert, done) { 1.577 + let test = new TestHelper(assert, done); 1.578 + let loader = test.newLoader(); 1.579 + 1.580 + let item = new loader.cm.Item({ 1.581 + label: "item", 1.582 + contentScript: 'self.on("context", function () true);' 1.583 + }); 1.584 + 1.585 + test.showMenu(null, function (popup) { 1.586 + test.checkMenu([item], [], []); 1.587 + test.done(); 1.588 + }); 1.589 +}; 1.590 + 1.591 + 1.592 +// Content contexts that return false should cause their items to be absent 1.593 +// from the menu. 1.594 +exports.testContentContextNoMatch = function (assert, done) { 1.595 + let test = new TestHelper(assert, done); 1.596 + let loader = test.newLoader(); 1.597 + 1.598 + let item = new loader.cm.Item({ 1.599 + label: "item", 1.600 + contentScript: 'self.on("context", function () false);' 1.601 + }); 1.602 + 1.603 + test.showMenu(null, function (popup) { 1.604 + test.checkMenu([item], [item], []); 1.605 + test.done(); 1.606 + }); 1.607 +}; 1.608 + 1.609 + 1.610 +// Content contexts that return undefined should cause their items to be absent 1.611 +// from the menu. 1.612 +exports.testContentContextUndefined = function (assert, done) { 1.613 + let test = new TestHelper(assert, done); 1.614 + let loader = test.newLoader(); 1.615 + 1.616 + let item = new loader.cm.Item({ 1.617 + label: "item", 1.618 + contentScript: 'self.on("context", function () {});' 1.619 + }); 1.620 + 1.621 + test.showMenu(null, function (popup) { 1.622 + test.checkMenu([item], [item], []); 1.623 + test.done(); 1.624 + }); 1.625 +}; 1.626 + 1.627 + 1.628 +// Content contexts that return an empty string should cause their items to be 1.629 +// absent from the menu and shouldn't wipe the label 1.630 +exports.testContentContextEmptyString = function (assert, done) { 1.631 + let test = new TestHelper(assert, done); 1.632 + let loader = test.newLoader(); 1.633 + 1.634 + let item = new loader.cm.Item({ 1.635 + label: "item", 1.636 + contentScript: 'self.on("context", function () "");' 1.637 + }); 1.638 + 1.639 + test.showMenu(null, function (popup) { 1.640 + test.checkMenu([item], [item], []); 1.641 + assert.equal(item.label, "item", "Label should still be correct"); 1.642 + test.done(); 1.643 + }); 1.644 +}; 1.645 + 1.646 + 1.647 +// If any content contexts returns true then their items should be present in 1.648 +// the menu. 1.649 +exports.testMultipleContentContextMatch1 = function (assert, done) { 1.650 + let test = new TestHelper(assert, done); 1.651 + let loader = test.newLoader(); 1.652 + 1.653 + let item = new loader.cm.Item({ 1.654 + label: "item", 1.655 + contentScript: 'self.on("context", function () true); ' + 1.656 + 'self.on("context", function () false);', 1.657 + onMessage: function() { 1.658 + test.fail("Should not have called the second context listener"); 1.659 + } 1.660 + }); 1.661 + 1.662 + test.showMenu(null, function (popup) { 1.663 + test.checkMenu([item], [], []); 1.664 + test.done(); 1.665 + }); 1.666 +}; 1.667 + 1.668 + 1.669 +// If any content contexts returns true then their items should be present in 1.670 +// the menu. 1.671 +exports.testMultipleContentContextMatch2 = function (assert, done) { 1.672 + let test = new TestHelper(assert, done); 1.673 + let loader = test.newLoader(); 1.674 + 1.675 + let item = new loader.cm.Item({ 1.676 + label: "item", 1.677 + contentScript: 'self.on("context", function () false); ' + 1.678 + 'self.on("context", function () true);' 1.679 + }); 1.680 + 1.681 + test.showMenu(null, function (popup) { 1.682 + test.checkMenu([item], [], []); 1.683 + test.done(); 1.684 + }); 1.685 +}; 1.686 + 1.687 + 1.688 +// If any content contexts returns a string then their items should be present 1.689 +// in the menu. 1.690 +exports.testMultipleContentContextString1 = function (assert, done) { 1.691 + let test = new TestHelper(assert, done); 1.692 + let loader = test.newLoader(); 1.693 + 1.694 + let item = new loader.cm.Item({ 1.695 + label: "item", 1.696 + contentScript: 'self.on("context", function () "new label"); ' + 1.697 + 'self.on("context", function () false);' 1.698 + }); 1.699 + 1.700 + test.showMenu(null, function (popup) { 1.701 + test.checkMenu([item], [], []); 1.702 + assert.equal(item.label, "new label", "Label should have changed"); 1.703 + test.done(); 1.704 + }); 1.705 +}; 1.706 + 1.707 + 1.708 +// If any content contexts returns a string then their items should be present 1.709 +// in the menu. 1.710 +exports.testMultipleContentContextString2 = function (assert, done) { 1.711 + let test = new TestHelper(assert, done); 1.712 + let loader = test.newLoader(); 1.713 + 1.714 + let item = new loader.cm.Item({ 1.715 + label: "item", 1.716 + contentScript: 'self.on("context", function () false); ' + 1.717 + 'self.on("context", function () "new label");' 1.718 + }); 1.719 + 1.720 + test.showMenu(null, function (popup) { 1.721 + test.checkMenu([item], [], []); 1.722 + assert.equal(item.label, "new label", "Label should have changed"); 1.723 + test.done(); 1.724 + }); 1.725 +}; 1.726 + 1.727 + 1.728 +// If many content contexts returns a string then the first should take effect 1.729 +exports.testMultipleContentContextString3 = function (assert, done) { 1.730 + let test = new TestHelper(assert, done); 1.731 + let loader = test.newLoader(); 1.732 + 1.733 + let item = new loader.cm.Item({ 1.734 + label: "item", 1.735 + contentScript: 'self.on("context", function () "new label 1"); ' + 1.736 + 'self.on("context", function () "new label 2");' 1.737 + }); 1.738 + 1.739 + test.showMenu(null, function (popup) { 1.740 + test.checkMenu([item], [], []); 1.741 + assert.equal(item.label, "new label 1", "Label should have changed"); 1.742 + test.done(); 1.743 + }); 1.744 +}; 1.745 + 1.746 + 1.747 +// Content contexts that return true should cause their items to be present 1.748 +// in the menu when context clicking an active element. 1.749 +exports.testContentContextMatchActiveElement = function (assert, done) { 1.750 + let test = new TestHelper(assert, done); 1.751 + let loader = test.newLoader(); 1.752 + 1.753 + let items = [ 1.754 + new loader.cm.Item({ 1.755 + label: "item 1", 1.756 + contentScript: 'self.on("context", function () true);' 1.757 + }), 1.758 + new loader.cm.Item({ 1.759 + label: "item 2", 1.760 + context: undefined, 1.761 + contentScript: 'self.on("context", function () true);' 1.762 + }), 1.763 + // These items will always be hidden by the declarative usage of PageContext 1.764 + new loader.cm.Item({ 1.765 + label: "item 3", 1.766 + context: loader.cm.PageContext(), 1.767 + contentScript: 'self.on("context", function () true);' 1.768 + }), 1.769 + new loader.cm.Item({ 1.770 + label: "item 4", 1.771 + context: [loader.cm.PageContext()], 1.772 + contentScript: 'self.on("context", function () true);' 1.773 + }) 1.774 + ]; 1.775 + 1.776 + test.withTestDoc(function (window, doc) { 1.777 + test.showMenu(doc.getElementById("image"), function (popup) { 1.778 + test.checkMenu(items, [items[2], items[3]], []); 1.779 + test.done(); 1.780 + }); 1.781 + }); 1.782 +}; 1.783 + 1.784 + 1.785 +// Content contexts that return false should cause their items to be absent 1.786 +// from the menu when context clicking an active element. 1.787 +exports.testContentContextNoMatchActiveElement = function (assert, done) { 1.788 + let test = new TestHelper(assert, done); 1.789 + let loader = test.newLoader(); 1.790 + 1.791 + let items = [ 1.792 + new loader.cm.Item({ 1.793 + label: "item 1", 1.794 + contentScript: 'self.on("context", function () false);' 1.795 + }), 1.796 + new loader.cm.Item({ 1.797 + label: "item 2", 1.798 + context: undefined, 1.799 + contentScript: 'self.on("context", function () false);' 1.800 + }), 1.801 + // These items will always be hidden by the declarative usage of PageContext 1.802 + new loader.cm.Item({ 1.803 + label: "item 3", 1.804 + context: loader.cm.PageContext(), 1.805 + contentScript: 'self.on("context", function () false);' 1.806 + }), 1.807 + new loader.cm.Item({ 1.808 + label: "item 4", 1.809 + context: [loader.cm.PageContext()], 1.810 + contentScript: 'self.on("context", function () false);' 1.811 + }) 1.812 + ]; 1.813 + 1.814 + test.withTestDoc(function (window, doc) { 1.815 + test.showMenu(doc.getElementById("image"), function (popup) { 1.816 + test.checkMenu(items, items, []); 1.817 + test.done(); 1.818 + }); 1.819 + }); 1.820 +}; 1.821 + 1.822 + 1.823 +// Content contexts that return undefined should cause their items to be absent 1.824 +// from the menu when context clicking an active element. 1.825 +exports.testContentContextNoMatchActiveElement = function (assert, done) { 1.826 + let test = new TestHelper(assert, done); 1.827 + let loader = test.newLoader(); 1.828 + 1.829 + let items = [ 1.830 + new loader.cm.Item({ 1.831 + label: "item 1", 1.832 + contentScript: 'self.on("context", function () {});' 1.833 + }), 1.834 + new loader.cm.Item({ 1.835 + label: "item 2", 1.836 + context: undefined, 1.837 + contentScript: 'self.on("context", function () {});' 1.838 + }), 1.839 + // These items will always be hidden by the declarative usage of PageContext 1.840 + new loader.cm.Item({ 1.841 + label: "item 3", 1.842 + context: loader.cm.PageContext(), 1.843 + contentScript: 'self.on("context", function () {});' 1.844 + }), 1.845 + new loader.cm.Item({ 1.846 + label: "item 4", 1.847 + context: [loader.cm.PageContext()], 1.848 + contentScript: 'self.on("context", function () {});' 1.849 + }) 1.850 + ]; 1.851 + 1.852 + test.withTestDoc(function (window, doc) { 1.853 + test.showMenu(doc.getElementById("image"), function (popup) { 1.854 + test.checkMenu(items, items, []); 1.855 + test.done(); 1.856 + }); 1.857 + }); 1.858 +}; 1.859 + 1.860 + 1.861 +// Content contexts that return a string should cause their items to be present 1.862 +// in the menu and the items' labels to be updated. 1.863 +exports.testContentContextMatchString = function (assert, done) { 1.864 + let test = new TestHelper(assert, done); 1.865 + let loader = test.newLoader(); 1.866 + 1.867 + let item = new loader.cm.Item({ 1.868 + label: "first label", 1.869 + contentScript: 'self.on("context", function () "second label");' 1.870 + }); 1.871 + 1.872 + test.showMenu(null, function (popup) { 1.873 + test.checkMenu([item], [], []); 1.874 + assert.equal(item.label, "second label", 1.875 + "item's label should be updated"); 1.876 + test.done(); 1.877 + }); 1.878 +}; 1.879 + 1.880 + 1.881 +// Ensure that contentScriptFile is working correctly 1.882 +exports.testContentScriptFile = function (assert, done) { 1.883 + let test = new TestHelper(assert, done); 1.884 + let loader = test.newLoader(); 1.885 + 1.886 + // Reject remote files 1.887 + assert.throws(function() { 1.888 + new loader.cm.Item({ 1.889 + label: "item", 1.890 + contentScriptFile: "http://mozilla.com/context-menu.js" 1.891 + }); 1.892 + }, 1.893 + new RegExp("The 'contentScriptFile' option must be a local file URL " + 1.894 + "or an array of local file URLs."), 1.895 + "Item throws when contentScriptFile is a remote URL"); 1.896 + 1.897 + // But accept files from data folder 1.898 + let item = new loader.cm.Item({ 1.899 + label: "item", 1.900 + contentScriptFile: data.url("test-context-menu.js") 1.901 + }); 1.902 + 1.903 + test.showMenu(null, function (popup) { 1.904 + test.checkMenu([item], [], []); 1.905 + test.done(); 1.906 + }); 1.907 +}; 1.908 + 1.909 + 1.910 +// The args passed to context listeners should be correct. 1.911 +exports.testContentContextArgs = function (assert, done) { 1.912 + let test = new TestHelper(assert, done); 1.913 + let loader = test.newLoader(); 1.914 + let callbacks = 0; 1.915 + 1.916 + let item = new loader.cm.Item({ 1.917 + label: "item", 1.918 + contentScript: 'self.on("context", function (node) {' + 1.919 + ' self.postMessage(node.tagName);' + 1.920 + ' return false;' + 1.921 + '});', 1.922 + onMessage: function (tagName) { 1.923 + assert.equal(tagName, "HTML", "node should be an HTML element"); 1.924 + if (++callbacks == 2) test.done(); 1.925 + } 1.926 + }); 1.927 + 1.928 + test.showMenu(null, function () { 1.929 + if (++callbacks == 2) test.done(); 1.930 + }); 1.931 +}; 1.932 + 1.933 +// Multiple contexts imply intersection, not union, and content context 1.934 +// listeners should not be called if all declarative contexts are not current. 1.935 +exports.testMultipleContexts = function (assert, done) { 1.936 + let test = new TestHelper(assert, done); 1.937 + let loader = test.newLoader(); 1.938 + 1.939 + let item = new loader.cm.Item({ 1.940 + label: "item", 1.941 + context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()], 1.942 + contentScript: 'self.on("context", function () self.postMessage());', 1.943 + onMessage: function () { 1.944 + test.fail("Context listener should not be called"); 1.945 + } 1.946 + }); 1.947 + 1.948 + test.withTestDoc(function (window, doc) { 1.949 + test.showMenu(doc.getElementById("span-link"), function (popup) { 1.950 + test.checkMenu([item], [item], []); 1.951 + test.done(); 1.952 + }); 1.953 + }); 1.954 +}; 1.955 + 1.956 +// Once a context is removed, it should no longer cause its item to appear. 1.957 +exports.testRemoveContext = function (assert, done) { 1.958 + let test = new TestHelper(assert, done); 1.959 + let loader = test.newLoader(); 1.960 + 1.961 + let ctxt = loader.cm.SelectorContext("img"); 1.962 + let item = new loader.cm.Item({ 1.963 + label: "item", 1.964 + context: ctxt 1.965 + }); 1.966 + 1.967 + test.withTestDoc(function (window, doc) { 1.968 + test.showMenu(doc.getElementById("image"), function (popup) { 1.969 + 1.970 + // The item should be present at first. 1.971 + test.checkMenu([item], [], []); 1.972 + popup.hidePopup(); 1.973 + 1.974 + // Remove the img context and check again. 1.975 + item.context.remove(ctxt); 1.976 + test.showMenu(doc.getElementById("image"), function (popup) { 1.977 + test.checkMenu([item], [item], []); 1.978 + test.done(); 1.979 + }); 1.980 + }); 1.981 + }); 1.982 +}; 1.983 + 1.984 + 1.985 +// Lots of items should overflow into the overflow submenu. 1.986 +exports.testOverflow = function (assert, done) { 1.987 + let test = new TestHelper(assert, done); 1.988 + let loader = test.newLoader(); 1.989 + 1.990 + let items = []; 1.991 + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT + 1; i++) { 1.992 + let item = new loader.cm.Item({ label: "item " + i }); 1.993 + items.push(item); 1.994 + } 1.995 + 1.996 + test.showMenu(null, function (popup) { 1.997 + test.checkMenu(items, [], []); 1.998 + test.done(); 1.999 + }); 1.1000 +}; 1.1001 + 1.1002 + 1.1003 +// Module unload should cause all items to be removed. 1.1004 +exports.testUnload = function (assert, done) { 1.1005 + let test = new TestHelper(assert, done); 1.1006 + let loader = test.newLoader(); 1.1007 + 1.1008 + let item = new loader.cm.Item({ label: "item" }); 1.1009 + 1.1010 + test.showMenu(null, function (popup) { 1.1011 + 1.1012 + // The menu should contain the item. 1.1013 + test.checkMenu([item], [], []); 1.1014 + popup.hidePopup(); 1.1015 + 1.1016 + // Unload the module. 1.1017 + loader.unload(); 1.1018 + test.showMenu(null, function (popup) { 1.1019 + 1.1020 + // The item should be removed from the menu. 1.1021 + test.checkMenu([item], [], [item]); 1.1022 + test.done(); 1.1023 + }); 1.1024 + }); 1.1025 +}; 1.1026 + 1.1027 + 1.1028 +// Using multiple module instances to add items without causing overflow should 1.1029 +// work OK. Assumes OVERFLOW_THRESH_DEFAULT >= 2. 1.1030 +exports.testMultipleModulesAdd = function (assert, done) { 1.1031 + let test = new TestHelper(assert, done); 1.1032 + let loader0 = test.newLoader(); 1.1033 + let loader1 = test.newLoader(); 1.1034 + 1.1035 + // Use each module to add an item, then unload each module in turn. 1.1036 + let item0 = new loader0.cm.Item({ label: "item 0" }); 1.1037 + let item1 = new loader1.cm.Item({ label: "item 1" }); 1.1038 + 1.1039 + test.showMenu(null, function (popup) { 1.1040 + 1.1041 + // The menu should contain both items. 1.1042 + test.checkMenu([item0, item1], [], []); 1.1043 + popup.hidePopup(); 1.1044 + 1.1045 + // Unload the first module. 1.1046 + loader0.unload(); 1.1047 + test.showMenu(null, function (popup) { 1.1048 + 1.1049 + // The first item should be removed from the menu. 1.1050 + test.checkMenu([item0, item1], [], [item0]); 1.1051 + popup.hidePopup(); 1.1052 + 1.1053 + // Unload the second module. 1.1054 + loader1.unload(); 1.1055 + test.showMenu(null, function (popup) { 1.1056 + 1.1057 + // Both items should be removed from the menu. 1.1058 + test.checkMenu([item0, item1], [], [item0, item1]); 1.1059 + test.done(); 1.1060 + }); 1.1061 + }); 1.1062 + }); 1.1063 +}; 1.1064 + 1.1065 + 1.1066 +// Using multiple module instances to add items causing overflow should work OK. 1.1067 +exports.testMultipleModulesAddOverflow = function (assert, done) { 1.1068 + let test = new TestHelper(assert, done); 1.1069 + let loader0 = test.newLoader(); 1.1070 + let loader1 = test.newLoader(); 1.1071 + 1.1072 + // Use module 0 to add OVERFLOW_THRESH_DEFAULT items. 1.1073 + let items0 = []; 1.1074 + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { 1.1075 + let item = new loader0.cm.Item({ label: "item 0 " + i }); 1.1076 + items0.push(item); 1.1077 + } 1.1078 + 1.1079 + // Use module 1 to add one item. 1.1080 + let item1 = new loader1.cm.Item({ label: "item 1" }); 1.1081 + 1.1082 + let allItems = items0.concat(item1); 1.1083 + 1.1084 + test.showMenu(null, function (popup) { 1.1085 + 1.1086 + // The menu should contain all items in overflow. 1.1087 + test.checkMenu(allItems, [], []); 1.1088 + popup.hidePopup(); 1.1089 + 1.1090 + // Unload the first module. 1.1091 + loader0.unload(); 1.1092 + test.showMenu(null, function (popup) { 1.1093 + 1.1094 + // The first items should be removed from the menu, which should not 1.1095 + // overflow. 1.1096 + test.checkMenu(allItems, [], items0); 1.1097 + popup.hidePopup(); 1.1098 + 1.1099 + // Unload the second module. 1.1100 + loader1.unload(); 1.1101 + test.showMenu(null, function (popup) { 1.1102 + 1.1103 + // All items should be removed from the menu. 1.1104 + test.checkMenu(allItems, [], allItems); 1.1105 + test.done(); 1.1106 + }); 1.1107 + }); 1.1108 + }); 1.1109 +}; 1.1110 + 1.1111 + 1.1112 +// Using multiple module instances to modify the menu without causing overflow 1.1113 +// should work OK. This test creates two loaders and: 1.1114 +// loader0 create item -> loader1 create item -> loader0.unload -> 1.1115 +// loader1.unload 1.1116 +exports.testMultipleModulesDiffContexts1 = function (assert, done) { 1.1117 + let test = new TestHelper(assert, done); 1.1118 + let loader0 = test.newLoader(); 1.1119 + let loader1 = test.newLoader(); 1.1120 + 1.1121 + let item0 = new loader0.cm.Item({ 1.1122 + label: "item 0", 1.1123 + context: loader0.cm.SelectorContext("img") 1.1124 + }); 1.1125 + 1.1126 + let item1 = new loader1.cm.Item({ label: "item 1" }); 1.1127 + 1.1128 + test.showMenu(null, function (popup) { 1.1129 + 1.1130 + // The menu should contain item1. 1.1131 + test.checkMenu([item0, item1], [item0], []); 1.1132 + popup.hidePopup(); 1.1133 + 1.1134 + // Unload module 0. 1.1135 + loader0.unload(); 1.1136 + test.showMenu(null, function (popup) { 1.1137 + 1.1138 + // item0 should be removed from the menu. 1.1139 + test.checkMenu([item0, item1], [], [item0]); 1.1140 + popup.hidePopup(); 1.1141 + 1.1142 + // Unload module 1. 1.1143 + loader1.unload(); 1.1144 + test.showMenu(null, function (popup) { 1.1145 + 1.1146 + // Both items should be removed from the menu. 1.1147 + test.checkMenu([item0, item1], [], [item0, item1]); 1.1148 + test.done(); 1.1149 + }); 1.1150 + }); 1.1151 + }); 1.1152 +}; 1.1153 + 1.1154 + 1.1155 +// Using multiple module instances to modify the menu without causing overflow 1.1156 +// should work OK. This test creates two loaders and: 1.1157 +// loader1 create item -> loader0 create item -> loader0.unload -> 1.1158 +// loader1.unload 1.1159 +exports.testMultipleModulesDiffContexts2 = function (assert, done) { 1.1160 + let test = new TestHelper(assert, done); 1.1161 + let loader0 = test.newLoader(); 1.1162 + let loader1 = test.newLoader(); 1.1163 + 1.1164 + let item1 = new loader1.cm.Item({ label: "item 1" }); 1.1165 + 1.1166 + let item0 = new loader0.cm.Item({ 1.1167 + label: "item 0", 1.1168 + context: loader0.cm.SelectorContext("img") 1.1169 + }); 1.1170 + 1.1171 + test.showMenu(null, function (popup) { 1.1172 + 1.1173 + // The menu should contain item1. 1.1174 + test.checkMenu([item0, item1], [item0], []); 1.1175 + popup.hidePopup(); 1.1176 + 1.1177 + // Unload module 0. 1.1178 + loader0.unload(); 1.1179 + test.showMenu(null, function (popup) { 1.1180 + 1.1181 + // item0 should be removed from the menu. 1.1182 + test.checkMenu([item0, item1], [], [item0]); 1.1183 + popup.hidePopup(); 1.1184 + 1.1185 + // Unload module 1. 1.1186 + loader1.unload(); 1.1187 + test.showMenu(null, function (popup) { 1.1188 + 1.1189 + // Both items should be removed from the menu. 1.1190 + test.checkMenu([item0, item1], [], [item0, item1]); 1.1191 + test.done(); 1.1192 + }); 1.1193 + }); 1.1194 + }); 1.1195 +}; 1.1196 + 1.1197 + 1.1198 +// Using multiple module instances to modify the menu without causing overflow 1.1199 +// should work OK. This test creates two loaders and: 1.1200 +// loader0 create item -> loader1 create item -> loader1.unload -> 1.1201 +// loader0.unload 1.1202 +exports.testMultipleModulesDiffContexts3 = function (assert, done) { 1.1203 + let test = new TestHelper(assert, done); 1.1204 + let loader0 = test.newLoader(); 1.1205 + let loader1 = test.newLoader(); 1.1206 + 1.1207 + let item0 = new loader0.cm.Item({ 1.1208 + label: "item 0", 1.1209 + context: loader0.cm.SelectorContext("img") 1.1210 + }); 1.1211 + 1.1212 + let item1 = new loader1.cm.Item({ label: "item 1" }); 1.1213 + 1.1214 + test.showMenu(null, function (popup) { 1.1215 + 1.1216 + // The menu should contain item1. 1.1217 + test.checkMenu([item0, item1], [item0], []); 1.1218 + popup.hidePopup(); 1.1219 + 1.1220 + // Unload module 1. 1.1221 + loader1.unload(); 1.1222 + test.showMenu(null, function (popup) { 1.1223 + 1.1224 + // item1 should be removed from the menu. 1.1225 + test.checkMenu([item0, item1], [item0], [item1]); 1.1226 + popup.hidePopup(); 1.1227 + 1.1228 + // Unload module 0. 1.1229 + loader0.unload(); 1.1230 + test.showMenu(null, function (popup) { 1.1231 + 1.1232 + // Both items should be removed from the menu. 1.1233 + test.checkMenu([item0, item1], [], [item0, item1]); 1.1234 + test.done(); 1.1235 + }); 1.1236 + }); 1.1237 + }); 1.1238 +}; 1.1239 + 1.1240 + 1.1241 +// Using multiple module instances to modify the menu without causing overflow 1.1242 +// should work OK. This test creates two loaders and: 1.1243 +// loader1 create item -> loader0 create item -> loader1.unload -> 1.1244 +// loader0.unload 1.1245 +exports.testMultipleModulesDiffContexts4 = function (assert, done) { 1.1246 + let test = new TestHelper(assert, done); 1.1247 + let loader0 = test.newLoader(); 1.1248 + let loader1 = test.newLoader(); 1.1249 + 1.1250 + let item1 = new loader1.cm.Item({ label: "item 1" }); 1.1251 + 1.1252 + let item0 = new loader0.cm.Item({ 1.1253 + label: "item 0", 1.1254 + context: loader0.cm.SelectorContext("img") 1.1255 + }); 1.1256 + 1.1257 + test.showMenu(null, function (popup) { 1.1258 + 1.1259 + // The menu should contain item1. 1.1260 + test.checkMenu([item0, item1], [item0], []); 1.1261 + popup.hidePopup(); 1.1262 + 1.1263 + // Unload module 1. 1.1264 + loader1.unload(); 1.1265 + test.showMenu(null, function (popup) { 1.1266 + 1.1267 + // item1 should be removed from the menu. 1.1268 + test.checkMenu([item0, item1], [item0], [item1]); 1.1269 + popup.hidePopup(); 1.1270 + 1.1271 + // Unload module 0. 1.1272 + loader0.unload(); 1.1273 + test.showMenu(null, function (popup) { 1.1274 + 1.1275 + // Both items should be removed from the menu. 1.1276 + test.checkMenu([item0, item1], [], [item0, item1]); 1.1277 + test.done(); 1.1278 + }); 1.1279 + }); 1.1280 + }); 1.1281 +}; 1.1282 + 1.1283 + 1.1284 +// Test interactions between a loaded module, unloading another module, and the 1.1285 +// menu separator and overflow submenu. 1.1286 +exports.testMultipleModulesAddRemove = function (assert, done) { 1.1287 + let test = new TestHelper(assert, done); 1.1288 + let loader0 = test.newLoader(); 1.1289 + let loader1 = test.newLoader(); 1.1290 + 1.1291 + let item = new loader0.cm.Item({ label: "item" }); 1.1292 + 1.1293 + test.showMenu(null, function (popup) { 1.1294 + 1.1295 + // The menu should contain the item. 1.1296 + test.checkMenu([item], [], []); 1.1297 + popup.hidePopup(); 1.1298 + 1.1299 + // Remove the item. 1.1300 + item.destroy(); 1.1301 + test.showMenu(null, function (popup) { 1.1302 + 1.1303 + // The item should be removed from the menu. 1.1304 + test.checkMenu([item], [], [item]); 1.1305 + popup.hidePopup(); 1.1306 + 1.1307 + // Unload module 1. 1.1308 + loader1.unload(); 1.1309 + test.showMenu(null, function (popup) { 1.1310 + 1.1311 + // There shouldn't be any errors involving the menu separator or 1.1312 + // overflow submenu. 1.1313 + test.checkMenu([item], [], [item]); 1.1314 + test.done(); 1.1315 + }); 1.1316 + }); 1.1317 + }); 1.1318 +}; 1.1319 + 1.1320 + 1.1321 +// Checks that the order of menu items is correct when adding/removing across 1.1322 +// multiple modules. All items from a single module should remain in a group 1.1323 +exports.testMultipleModulesOrder = function (assert, done) { 1.1324 + let test = new TestHelper(assert, done); 1.1325 + let loader0 = test.newLoader(); 1.1326 + let loader1 = test.newLoader(); 1.1327 + 1.1328 + // Use each module to add an item, then unload each module in turn. 1.1329 + let item0 = new loader0.cm.Item({ label: "item 0" }); 1.1330 + let item1 = new loader1.cm.Item({ label: "item 1" }); 1.1331 + 1.1332 + test.showMenu(null, function (popup) { 1.1333 + 1.1334 + // The menu should contain both items. 1.1335 + test.checkMenu([item0, item1], [], []); 1.1336 + popup.hidePopup(); 1.1337 + 1.1338 + let item2 = new loader0.cm.Item({ label: "item 2" }); 1.1339 + 1.1340 + test.showMenu(null, function (popup) { 1.1341 + 1.1342 + // The new item should be grouped with the same items from loader0. 1.1343 + test.checkMenu([item0, item2, item1], [], []); 1.1344 + popup.hidePopup(); 1.1345 + 1.1346 + let item3 = new loader1.cm.Item({ label: "item 3" }); 1.1347 + 1.1348 + test.showMenu(null, function (popup) { 1.1349 + 1.1350 + // Same again 1.1351 + test.checkMenu([item0, item2, item1, item3], [], []); 1.1352 + test.done(); 1.1353 + }); 1.1354 + }); 1.1355 + }); 1.1356 +}; 1.1357 + 1.1358 + 1.1359 +// Checks that the order of menu items is correct when adding/removing across 1.1360 +// multiple modules when overflowing. All items from a single module should 1.1361 +// remain in a group 1.1362 +exports.testMultipleModulesOrderOverflow = function (assert, done) { 1.1363 + let test = new TestHelper(assert, done); 1.1364 + let loader0 = test.newLoader(); 1.1365 + let loader1 = test.newLoader(); 1.1366 + 1.1367 + let prefs = loader0.loader.require("sdk/preferences/service"); 1.1368 + prefs.set(OVERFLOW_THRESH_PREF, 0); 1.1369 + 1.1370 + // Use each module to add an item, then unload each module in turn. 1.1371 + let item0 = new loader0.cm.Item({ label: "item 0" }); 1.1372 + let item1 = new loader1.cm.Item({ label: "item 1" }); 1.1373 + 1.1374 + test.showMenu(null, function (popup) { 1.1375 + 1.1376 + // The menu should contain both items. 1.1377 + test.checkMenu([item0, item1], [], []); 1.1378 + popup.hidePopup(); 1.1379 + 1.1380 + let item2 = new loader0.cm.Item({ label: "item 2" }); 1.1381 + 1.1382 + test.showMenu(null, function (popup) { 1.1383 + 1.1384 + // The new item should be grouped with the same items from loader0. 1.1385 + test.checkMenu([item0, item2, item1], [], []); 1.1386 + popup.hidePopup(); 1.1387 + 1.1388 + let item3 = new loader1.cm.Item({ label: "item 3" }); 1.1389 + 1.1390 + test.showMenu(null, function (popup) { 1.1391 + 1.1392 + // Same again 1.1393 + test.checkMenu([item0, item2, item1, item3], [], []); 1.1394 + test.done(); 1.1395 + }); 1.1396 + }); 1.1397 + }); 1.1398 +}; 1.1399 + 1.1400 + 1.1401 +// Checks that if a module's items are all hidden then the overflow menu doesn't 1.1402 +// get hidden 1.1403 +exports.testMultipleModulesOverflowHidden = function (assert, done) { 1.1404 + let test = new TestHelper(assert, done); 1.1405 + let loader0 = test.newLoader(); 1.1406 + let loader1 = test.newLoader(); 1.1407 + 1.1408 + let prefs = loader0.loader.require("sdk/preferences/service"); 1.1409 + prefs.set(OVERFLOW_THRESH_PREF, 0); 1.1410 + 1.1411 + // Use each module to add an item, then unload each module in turn. 1.1412 + let item0 = new loader0.cm.Item({ label: "item 0" }); 1.1413 + let item1 = new loader1.cm.Item({ 1.1414 + label: "item 1", 1.1415 + context: loader1.cm.SelectorContext("a") 1.1416 + }); 1.1417 + 1.1418 + test.showMenu(null, function (popup) { 1.1419 + // One should be hidden 1.1420 + test.checkMenu([item0, item1], [item1], []); 1.1421 + test.done(); 1.1422 + }); 1.1423 +}; 1.1424 + 1.1425 + 1.1426 +// Checks that if a module's items are all hidden then the overflow menu doesn't 1.1427 +// get hidden (reverse order to above) 1.1428 +exports.testMultipleModulesOverflowHidden2 = function (assert, done) { 1.1429 + let test = new TestHelper(assert, done); 1.1430 + let loader0 = test.newLoader(); 1.1431 + let loader1 = test.newLoader(); 1.1432 + 1.1433 + let prefs = loader0.loader.require("sdk/preferences/service"); 1.1434 + prefs.set(OVERFLOW_THRESH_PREF, 0); 1.1435 + 1.1436 + // Use each module to add an item, then unload each module in turn. 1.1437 + let item0 = new loader0.cm.Item({ 1.1438 + label: "item 0", 1.1439 + context: loader0.cm.SelectorContext("a") 1.1440 + }); 1.1441 + let item1 = new loader1.cm.Item({ label: "item 1" }); 1.1442 + 1.1443 + test.showMenu(null, function (popup) { 1.1444 + // One should be hidden 1.1445 + test.checkMenu([item0, item1], [item0], []); 1.1446 + test.done(); 1.1447 + }); 1.1448 +}; 1.1449 + 1.1450 + 1.1451 +// Checks that we don't overflow if there are more items than the overflow 1.1452 +// threshold but not all of them are visible 1.1453 +exports.testOverflowIgnoresHidden = function (assert, done) { 1.1454 + let test = new TestHelper(assert, done); 1.1455 + let loader = test.newLoader(); 1.1456 + 1.1457 + let prefs = loader.loader.require("sdk/preferences/service"); 1.1458 + prefs.set(OVERFLOW_THRESH_PREF, 2); 1.1459 + 1.1460 + let allItems = [ 1.1461 + new loader.cm.Item({ 1.1462 + label: "item 0" 1.1463 + }), 1.1464 + new loader.cm.Item({ 1.1465 + label: "item 1" 1.1466 + }), 1.1467 + new loader.cm.Item({ 1.1468 + label: "item 2", 1.1469 + context: loader.cm.SelectorContext("a") 1.1470 + }) 1.1471 + ]; 1.1472 + 1.1473 + test.showMenu(null, function (popup) { 1.1474 + // One should be hidden 1.1475 + test.checkMenu(allItems, [allItems[2]], []); 1.1476 + test.done(); 1.1477 + }); 1.1478 +}; 1.1479 + 1.1480 + 1.1481 +// Checks that we don't overflow if there are more items than the overflow 1.1482 +// threshold but not all of them are visible 1.1483 +exports.testOverflowIgnoresHiddenMultipleModules1 = function (assert, done) { 1.1484 + let test = new TestHelper(assert, done); 1.1485 + let loader0 = test.newLoader(); 1.1486 + let loader1 = test.newLoader(); 1.1487 + 1.1488 + let prefs = loader0.loader.require("sdk/preferences/service"); 1.1489 + prefs.set(OVERFLOW_THRESH_PREF, 2); 1.1490 + 1.1491 + let allItems = [ 1.1492 + new loader0.cm.Item({ 1.1493 + label: "item 0" 1.1494 + }), 1.1495 + new loader0.cm.Item({ 1.1496 + label: "item 1" 1.1497 + }), 1.1498 + new loader1.cm.Item({ 1.1499 + label: "item 2", 1.1500 + context: loader1.cm.SelectorContext("a") 1.1501 + }), 1.1502 + new loader1.cm.Item({ 1.1503 + label: "item 3", 1.1504 + context: loader1.cm.SelectorContext("a") 1.1505 + }) 1.1506 + ]; 1.1507 + 1.1508 + test.showMenu(null, function (popup) { 1.1509 + // One should be hidden 1.1510 + test.checkMenu(allItems, [allItems[2], allItems[3]], []); 1.1511 + test.done(); 1.1512 + }); 1.1513 +}; 1.1514 + 1.1515 + 1.1516 +// Checks that we don't overflow if there are more items than the overflow 1.1517 +// threshold but not all of them are visible 1.1518 +exports.testOverflowIgnoresHiddenMultipleModules2 = function (assert, done) { 1.1519 + let test = new TestHelper(assert, done); 1.1520 + let loader0 = test.newLoader(); 1.1521 + let loader1 = test.newLoader(); 1.1522 + 1.1523 + let prefs = loader0.loader.require("sdk/preferences/service"); 1.1524 + prefs.set(OVERFLOW_THRESH_PREF, 2); 1.1525 + 1.1526 + let allItems = [ 1.1527 + new loader0.cm.Item({ 1.1528 + label: "item 0" 1.1529 + }), 1.1530 + new loader0.cm.Item({ 1.1531 + label: "item 1", 1.1532 + context: loader0.cm.SelectorContext("a") 1.1533 + }), 1.1534 + new loader1.cm.Item({ 1.1535 + label: "item 2" 1.1536 + }), 1.1537 + new loader1.cm.Item({ 1.1538 + label: "item 3", 1.1539 + context: loader1.cm.SelectorContext("a") 1.1540 + }) 1.1541 + ]; 1.1542 + 1.1543 + test.showMenu(null, function (popup) { 1.1544 + // One should be hidden 1.1545 + test.checkMenu(allItems, [allItems[1], allItems[3]], []); 1.1546 + test.done(); 1.1547 + }); 1.1548 +}; 1.1549 + 1.1550 + 1.1551 +// Checks that we don't overflow if there are more items than the overflow 1.1552 +// threshold but not all of them are visible 1.1553 +exports.testOverflowIgnoresHiddenMultipleModules3 = function (assert, done) { 1.1554 + let test = new TestHelper(assert, done); 1.1555 + let loader0 = test.newLoader(); 1.1556 + let loader1 = test.newLoader(); 1.1557 + 1.1558 + let prefs = loader0.loader.require("sdk/preferences/service"); 1.1559 + prefs.set(OVERFLOW_THRESH_PREF, 2); 1.1560 + 1.1561 + let allItems = [ 1.1562 + new loader0.cm.Item({ 1.1563 + label: "item 0", 1.1564 + context: loader0.cm.SelectorContext("a") 1.1565 + }), 1.1566 + new loader0.cm.Item({ 1.1567 + label: "item 1", 1.1568 + context: loader0.cm.SelectorContext("a") 1.1569 + }), 1.1570 + new loader1.cm.Item({ 1.1571 + label: "item 2" 1.1572 + }), 1.1573 + new loader1.cm.Item({ 1.1574 + label: "item 3" 1.1575 + }) 1.1576 + ]; 1.1577 + 1.1578 + test.showMenu(null, function (popup) { 1.1579 + // One should be hidden 1.1580 + test.checkMenu(allItems, [allItems[0], allItems[1]], []); 1.1581 + test.done(); 1.1582 + }); 1.1583 +}; 1.1584 + 1.1585 + 1.1586 +// Tests that we transition between overflowing to non-overflowing to no items 1.1587 +// and back again 1.1588 +exports.testOverflowTransition = function (assert, done) { 1.1589 + let test = new TestHelper(assert, done); 1.1590 + let loader = test.newLoader(); 1.1591 + 1.1592 + let prefs = loader.loader.require("sdk/preferences/service"); 1.1593 + prefs.set(OVERFLOW_THRESH_PREF, 2); 1.1594 + 1.1595 + let pItems = [ 1.1596 + new loader.cm.Item({ 1.1597 + label: "item 0", 1.1598 + context: loader.cm.SelectorContext("p") 1.1599 + }), 1.1600 + new loader.cm.Item({ 1.1601 + label: "item 1", 1.1602 + context: loader.cm.SelectorContext("p") 1.1603 + }) 1.1604 + ]; 1.1605 + 1.1606 + let aItems = [ 1.1607 + new loader.cm.Item({ 1.1608 + label: "item 2", 1.1609 + context: loader.cm.SelectorContext("a") 1.1610 + }), 1.1611 + new loader.cm.Item({ 1.1612 + label: "item 3", 1.1613 + context: loader.cm.SelectorContext("a") 1.1614 + }) 1.1615 + ]; 1.1616 + 1.1617 + let allItems = pItems.concat(aItems); 1.1618 + 1.1619 + test.withTestDoc(function (window, doc) { 1.1620 + test.showMenu(doc.getElementById("link"), function (popup) { 1.1621 + // The menu should contain all items and will overflow 1.1622 + test.checkMenu(allItems, [], []); 1.1623 + popup.hidePopup(); 1.1624 + 1.1625 + test.showMenu(doc.getElementById("text"), function (popup) { 1.1626 + // Only contains hald the items and will not overflow 1.1627 + test.checkMenu(allItems, aItems, []); 1.1628 + popup.hidePopup(); 1.1629 + 1.1630 + test.showMenu(null, function (popup) { 1.1631 + // None of the items will be visible 1.1632 + test.checkMenu(allItems, allItems, []); 1.1633 + popup.hidePopup(); 1.1634 + 1.1635 + test.showMenu(doc.getElementById("text"), function (popup) { 1.1636 + // Only contains hald the items and will not overflow 1.1637 + test.checkMenu(allItems, aItems, []); 1.1638 + popup.hidePopup(); 1.1639 + 1.1640 + test.showMenu(doc.getElementById("link"), function (popup) { 1.1641 + // The menu should contain all items and will overflow 1.1642 + test.checkMenu(allItems, [], []); 1.1643 + popup.hidePopup(); 1.1644 + 1.1645 + test.showMenu(null, function (popup) { 1.1646 + // None of the items will be visible 1.1647 + test.checkMenu(allItems, allItems, []); 1.1648 + popup.hidePopup(); 1.1649 + 1.1650 + test.showMenu(doc.getElementById("link"), function (popup) { 1.1651 + // The menu should contain all items and will overflow 1.1652 + test.checkMenu(allItems, [], []); 1.1653 + test.done(); 1.1654 + }); 1.1655 + }); 1.1656 + }); 1.1657 + }); 1.1658 + }); 1.1659 + }); 1.1660 + }); 1.1661 + }); 1.1662 +}; 1.1663 + 1.1664 + 1.1665 +// An item's command listener should work. 1.1666 +exports.testItemCommand = function (assert, done) { 1.1667 + let test = new TestHelper(assert, done); 1.1668 + let loader = test.newLoader(); 1.1669 + 1.1670 + let item = new loader.cm.Item({ 1.1671 + label: "item", 1.1672 + data: "item data", 1.1673 + contentScript: 'self.on("click", function (node, data) {' + 1.1674 + ' self.postMessage({' + 1.1675 + ' tagName: node.tagName,' + 1.1676 + ' data: data' + 1.1677 + ' });' + 1.1678 + '});', 1.1679 + onMessage: function (data) { 1.1680 + assert.equal(this, item, "`this` inside onMessage should be item"); 1.1681 + assert.equal(data.tagName, "HTML", "node should be an HTML element"); 1.1682 + assert.equal(data.data, item.data, "data should be item data"); 1.1683 + test.done(); 1.1684 + } 1.1685 + }); 1.1686 + 1.1687 + test.showMenu(null, function (popup) { 1.1688 + test.checkMenu([item], [], []); 1.1689 + let elt = test.getItemElt(popup, item); 1.1690 + 1.1691 + // create a command event 1.1692 + let evt = elt.ownerDocument.createEvent('Event'); 1.1693 + evt.initEvent('command', true, true); 1.1694 + elt.dispatchEvent(evt); 1.1695 + }); 1.1696 +}; 1.1697 + 1.1698 + 1.1699 +// A menu's click listener should work and receive bubbling 'command' events from 1.1700 +// sub-items appropriately. This also tests menus and ensures that when a CSS 1.1701 +// selector context matches the clicked node's ancestor, the matching ancestor 1.1702 +// is passed to listeners as the clicked node. 1.1703 +exports.testMenuCommand = function (assert, done) { 1.1704 + // Create a top-level menu, submenu, and item, like this: 1.1705 + // topMenu -> submenu -> item 1.1706 + // Click the item and make sure the click bubbles. 1.1707 + let test = new TestHelper(assert, done); 1.1708 + let loader = test.newLoader(); 1.1709 + 1.1710 + let item = new loader.cm.Item({ 1.1711 + label: "submenu item", 1.1712 + data: "submenu item data", 1.1713 + context: loader.cm.SelectorContext("a"), 1.1714 + }); 1.1715 + 1.1716 + let submenu = new loader.cm.Menu({ 1.1717 + label: "submenu", 1.1718 + context: loader.cm.SelectorContext("a"), 1.1719 + items: [item] 1.1720 + }); 1.1721 + 1.1722 + let topMenu = new loader.cm.Menu({ 1.1723 + label: "top menu", 1.1724 + contentScript: 'self.on("click", function (node, data) {' + 1.1725 + ' self.postMessage({' + 1.1726 + ' tagName: node.tagName,' + 1.1727 + ' data: data' + 1.1728 + ' });' + 1.1729 + '});', 1.1730 + onMessage: function (data) { 1.1731 + assert.equal(this, topMenu, "`this` inside top menu should be menu"); 1.1732 + assert.equal(data.tagName, "A", "Clicked node should be anchor"); 1.1733 + assert.equal(data.data, item.data, 1.1734 + "Clicked item data should be correct"); 1.1735 + test.done(); 1.1736 + }, 1.1737 + items: [submenu], 1.1738 + context: loader.cm.SelectorContext("a") 1.1739 + }); 1.1740 + 1.1741 + test.withTestDoc(function (window, doc) { 1.1742 + test.showMenu(doc.getElementById("span-link"), function (popup) { 1.1743 + test.checkMenu([topMenu], [], []); 1.1744 + let topMenuElt = test.getItemElt(popup, topMenu); 1.1745 + let topMenuPopup = topMenuElt.firstChild; 1.1746 + let submenuElt = test.getItemElt(topMenuPopup, submenu); 1.1747 + let submenuPopup = submenuElt.firstChild; 1.1748 + let itemElt = test.getItemElt(submenuPopup, item); 1.1749 + 1.1750 + // create a command event 1.1751 + let evt = itemElt.ownerDocument.createEvent('Event'); 1.1752 + evt.initEvent('command', true, true); 1.1753 + itemElt.dispatchEvent(evt); 1.1754 + }); 1.1755 + }); 1.1756 +}; 1.1757 + 1.1758 + 1.1759 +// Click listeners should work when multiple modules are loaded. 1.1760 +exports.testItemCommandMultipleModules = function (assert, done) { 1.1761 + let test = new TestHelper(assert, done); 1.1762 + let loader0 = test.newLoader(); 1.1763 + let loader1 = test.newLoader(); 1.1764 + 1.1765 + let item0 = loader0.cm.Item({ 1.1766 + label: "loader 0 item", 1.1767 + contentScript: 'self.on("click", self.postMessage);', 1.1768 + onMessage: function () { 1.1769 + test.fail("loader 0 item should not emit click event"); 1.1770 + } 1.1771 + }); 1.1772 + let item1 = loader1.cm.Item({ 1.1773 + label: "loader 1 item", 1.1774 + contentScript: 'self.on("click", self.postMessage);', 1.1775 + onMessage: function () { 1.1776 + test.pass("loader 1 item clicked as expected"); 1.1777 + test.done(); 1.1778 + } 1.1779 + }); 1.1780 + 1.1781 + test.showMenu(null, function (popup) { 1.1782 + test.checkMenu([item0, item1], [], []); 1.1783 + let item1Elt = test.getItemElt(popup, item1); 1.1784 + 1.1785 + // create a command event 1.1786 + let evt = item1Elt.ownerDocument.createEvent('Event'); 1.1787 + evt.initEvent('command', true, true); 1.1788 + item1Elt.dispatchEvent(evt); 1.1789 + }); 1.1790 +}; 1.1791 + 1.1792 + 1.1793 + 1.1794 + 1.1795 +// An item's click listener should work. 1.1796 +exports.testItemClick = function (assert, done) { 1.1797 + let test = new TestHelper(assert, done); 1.1798 + let loader = test.newLoader(); 1.1799 + 1.1800 + let item = new loader.cm.Item({ 1.1801 + label: "item", 1.1802 + data: "item data", 1.1803 + contentScript: 'self.on("click", function (node, data) {' + 1.1804 + ' self.postMessage({' + 1.1805 + ' tagName: node.tagName,' + 1.1806 + ' data: data' + 1.1807 + ' });' + 1.1808 + '});', 1.1809 + onMessage: function (data) { 1.1810 + assert.equal(this, item, "`this` inside onMessage should be item"); 1.1811 + assert.equal(data.tagName, "HTML", "node should be an HTML element"); 1.1812 + assert.equal(data.data, item.data, "data should be item data"); 1.1813 + test.done(); 1.1814 + } 1.1815 + }); 1.1816 + 1.1817 + test.showMenu(null, function (popup) { 1.1818 + test.checkMenu([item], [], []); 1.1819 + let elt = test.getItemElt(popup, item); 1.1820 + elt.click(); 1.1821 + }); 1.1822 +}; 1.1823 + 1.1824 + 1.1825 +// A menu's click listener should work and receive bubbling clicks from 1.1826 +// sub-items appropriately. This also tests menus and ensures that when a CSS 1.1827 +// selector context matches the clicked node's ancestor, the matching ancestor 1.1828 +// is passed to listeners as the clicked node. 1.1829 +exports.testMenuClick = function (assert, done) { 1.1830 + // Create a top-level menu, submenu, and item, like this: 1.1831 + // topMenu -> submenu -> item 1.1832 + // Click the item and make sure the click bubbles. 1.1833 + let test = new TestHelper(assert, done); 1.1834 + let loader = test.newLoader(); 1.1835 + 1.1836 + let item = new loader.cm.Item({ 1.1837 + label: "submenu item", 1.1838 + data: "submenu item data", 1.1839 + context: loader.cm.SelectorContext("a"), 1.1840 + }); 1.1841 + 1.1842 + let submenu = new loader.cm.Menu({ 1.1843 + label: "submenu", 1.1844 + context: loader.cm.SelectorContext("a"), 1.1845 + items: [item] 1.1846 + }); 1.1847 + 1.1848 + let topMenu = new loader.cm.Menu({ 1.1849 + label: "top menu", 1.1850 + contentScript: 'self.on("click", function (node, data) {' + 1.1851 + ' self.postMessage({' + 1.1852 + ' tagName: node.tagName,' + 1.1853 + ' data: data' + 1.1854 + ' });' + 1.1855 + '});', 1.1856 + onMessage: function (data) { 1.1857 + assert.equal(this, topMenu, "`this` inside top menu should be menu"); 1.1858 + assert.equal(data.tagName, "A", "Clicked node should be anchor"); 1.1859 + assert.equal(data.data, item.data, 1.1860 + "Clicked item data should be correct"); 1.1861 + test.done(); 1.1862 + }, 1.1863 + items: [submenu], 1.1864 + context: loader.cm.SelectorContext("a") 1.1865 + }); 1.1866 + 1.1867 + test.withTestDoc(function (window, doc) { 1.1868 + test.showMenu(doc.getElementById("span-link"), function (popup) { 1.1869 + test.checkMenu([topMenu], [], []); 1.1870 + let topMenuElt = test.getItemElt(popup, topMenu); 1.1871 + let topMenuPopup = topMenuElt.firstChild; 1.1872 + let submenuElt = test.getItemElt(topMenuPopup, submenu); 1.1873 + let submenuPopup = submenuElt.firstChild; 1.1874 + let itemElt = test.getItemElt(submenuPopup, item); 1.1875 + itemElt.click(); 1.1876 + }); 1.1877 + }); 1.1878 +}; 1.1879 + 1.1880 +// Click listeners should work when multiple modules are loaded. 1.1881 +exports.testItemClickMultipleModules = function (assert, done) { 1.1882 + let test = new TestHelper(assert, done); 1.1883 + let loader0 = test.newLoader(); 1.1884 + let loader1 = test.newLoader(); 1.1885 + 1.1886 + let item0 = loader0.cm.Item({ 1.1887 + label: "loader 0 item", 1.1888 + contentScript: 'self.on("click", self.postMessage);', 1.1889 + onMessage: function () { 1.1890 + test.fail("loader 0 item should not emit click event"); 1.1891 + } 1.1892 + }); 1.1893 + let item1 = loader1.cm.Item({ 1.1894 + label: "loader 1 item", 1.1895 + contentScript: 'self.on("click", self.postMessage);', 1.1896 + onMessage: function () { 1.1897 + test.pass("loader 1 item clicked as expected"); 1.1898 + test.done(); 1.1899 + } 1.1900 + }); 1.1901 + 1.1902 + test.showMenu(null, function (popup) { 1.1903 + test.checkMenu([item0, item1], [], []); 1.1904 + let item1Elt = test.getItemElt(popup, item1); 1.1905 + item1Elt.click(); 1.1906 + }); 1.1907 +}; 1.1908 + 1.1909 + 1.1910 +// Adding a separator to a submenu should work OK. 1.1911 +exports.testSeparator = function (assert, done) { 1.1912 + let test = new TestHelper(assert, done); 1.1913 + let loader = test.newLoader(); 1.1914 + 1.1915 + let menu = new loader.cm.Menu({ 1.1916 + label: "submenu", 1.1917 + items: [new loader.cm.Separator()] 1.1918 + }); 1.1919 + 1.1920 + test.showMenu(null, function (popup) { 1.1921 + test.checkMenu([menu], [], []); 1.1922 + test.done(); 1.1923 + }); 1.1924 +}; 1.1925 + 1.1926 + 1.1927 +// The parentMenu option should work 1.1928 +exports.testParentMenu = function (assert, done) { 1.1929 + let test = new TestHelper(assert, done); 1.1930 + let loader = test.newLoader(); 1.1931 + 1.1932 + let menu = new loader.cm.Menu({ 1.1933 + label: "submenu", 1.1934 + items: [loader.cm.Item({ label: "item 1" })], 1.1935 + parentMenu: loader.cm.contentContextMenu 1.1936 + }); 1.1937 + 1.1938 + let item = loader.cm.Item({ 1.1939 + label: "item 2", 1.1940 + parentMenu: menu, 1.1941 + }); 1.1942 + 1.1943 + assert.equal(menu.items[1], item, "Item should be in the sub menu"); 1.1944 + 1.1945 + test.showMenu(null, function (popup) { 1.1946 + test.checkMenu([menu], [], []); 1.1947 + test.done(); 1.1948 + }); 1.1949 +}; 1.1950 + 1.1951 + 1.1952 +// Existing context menu modifications should apply to new windows. 1.1953 +exports.testNewWindow = function (assert, done) { 1.1954 + let test = new TestHelper(assert, done); 1.1955 + let loader = test.newLoader(); 1.1956 + 1.1957 + let item = new loader.cm.Item({ label: "item" }); 1.1958 + 1.1959 + test.withNewWindow(function () { 1.1960 + test.showMenu(null, function (popup) { 1.1961 + test.checkMenu([item], [], []); 1.1962 + test.done(); 1.1963 + }); 1.1964 + }); 1.1965 +}; 1.1966 + 1.1967 + 1.1968 +// When a new window is opened, items added by an unloaded module should not 1.1969 +// be present in the menu. 1.1970 +exports.testNewWindowMultipleModules = function (assert, done) { 1.1971 + let test = new TestHelper(assert, done); 1.1972 + let loader = test.newLoader(); 1.1973 + let item = new loader.cm.Item({ label: "item" }); 1.1974 + 1.1975 + test.showMenu(null, function (popup) { 1.1976 + test.checkMenu([item], [], []); 1.1977 + popup.hidePopup(); 1.1978 + loader.unload(); 1.1979 + test.withNewWindow(function () { 1.1980 + test.showMenu(null, function (popup) { 1.1981 + test.checkMenu([item], [], [item]); 1.1982 + test.done(); 1.1983 + }); 1.1984 + }); 1.1985 + }); 1.1986 +}; 1.1987 + 1.1988 + 1.1989 +// Existing context menu modifications should not apply to new private windows. 1.1990 +exports.testNewPrivateWindow = function (assert, done) { 1.1991 + let test = new TestHelper(assert, done); 1.1992 + let loader = test.newLoader(); 1.1993 + 1.1994 + let item = new loader.cm.Item({ label: "item" }); 1.1995 + 1.1996 + test.showMenu(null, function (popup) { 1.1997 + test.checkMenu([item], [], []); 1.1998 + popup.hidePopup(); 1.1999 + 1.2000 + test.withNewPrivateWindow(function () { 1.2001 + test.showMenu(null, function (popup) { 1.2002 + test.checkMenu([], [], []); 1.2003 + test.done(); 1.2004 + }); 1.2005 + }); 1.2006 + }); 1.2007 +}; 1.2008 + 1.2009 + 1.2010 +// Existing context menu modifications should apply to new private windows when 1.2011 +// private browsing support is enabled. 1.2012 +exports.testNewPrivateEnabledWindow = function (assert, done) { 1.2013 + let test = new TestHelper(assert, done); 1.2014 + let loader = test.newPrivateLoader(); 1.2015 + 1.2016 + let item = new loader.cm.Item({ label: "item" }); 1.2017 + 1.2018 + test.showMenu(null, function (popup) { 1.2019 + test.checkMenu([item], [], []); 1.2020 + popup.hidePopup(); 1.2021 + 1.2022 + test.withNewPrivateWindow(function () { 1.2023 + test.showMenu(null, function (popup) { 1.2024 + test.checkMenu([item], [], []); 1.2025 + test.done(); 1.2026 + }); 1.2027 + }); 1.2028 + }); 1.2029 +}; 1.2030 + 1.2031 + 1.2032 +// Existing context menu modifications should apply to new private windows when 1.2033 +// private browsing support is enabled unless unloaded. 1.2034 +exports.testNewPrivateEnabledWindowUnloaded = function (assert, done) { 1.2035 + let test = new TestHelper(assert, done); 1.2036 + let loader = test.newPrivateLoader(); 1.2037 + 1.2038 + let item = new loader.cm.Item({ label: "item" }); 1.2039 + 1.2040 + test.showMenu(null, function (popup) { 1.2041 + test.checkMenu([item], [], []); 1.2042 + popup.hidePopup(); 1.2043 + 1.2044 + loader.unload(); 1.2045 + 1.2046 + test.withNewPrivateWindow(function () { 1.2047 + test.showMenu(null, function (popup) { 1.2048 + test.checkMenu([], [], []); 1.2049 + test.done(); 1.2050 + }); 1.2051 + }); 1.2052 + }); 1.2053 +}; 1.2054 + 1.2055 + 1.2056 +// Items in the context menu should be sorted according to locale. 1.2057 +exports.testSorting = function (assert, done) { 1.2058 + let test = new TestHelper(assert, done); 1.2059 + let loader = test.newLoader(); 1.2060 + 1.2061 + // Make an unsorted items list. It'll look like this: 1.2062 + // item 1, item 0, item 3, item 2, item 5, item 4, ... 1.2063 + let items = []; 1.2064 + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i += 2) { 1.2065 + items.push(new loader.cm.Item({ label: "item " + (i + 1) })); 1.2066 + items.push(new loader.cm.Item({ label: "item " + i })); 1.2067 + } 1.2068 + 1.2069 + test.showMenu(null, function (popup) { 1.2070 + test.checkMenu(items, [], []); 1.2071 + test.done(); 1.2072 + }); 1.2073 +}; 1.2074 + 1.2075 + 1.2076 +// Items in the overflow menu should be sorted according to locale. 1.2077 +exports.testSortingOverflow = function (assert, done) { 1.2078 + let test = new TestHelper(assert, done); 1.2079 + let loader = test.newLoader(); 1.2080 + 1.2081 + // Make an unsorted items list. It'll look like this: 1.2082 + // item 1, item 0, item 3, item 2, item 5, item 4, ... 1.2083 + let items = []; 1.2084 + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT * 2; i += 2) { 1.2085 + items.push(new loader.cm.Item({ label: "item " + (i + 1) })); 1.2086 + items.push(new loader.cm.Item({ label: "item " + i })); 1.2087 + } 1.2088 + 1.2089 + test.showMenu(null, function (popup) { 1.2090 + test.checkMenu(items, [], []); 1.2091 + test.done(); 1.2092 + }); 1.2093 +}; 1.2094 + 1.2095 + 1.2096 +// Multiple modules shouldn't interfere with sorting. 1.2097 +exports.testSortingMultipleModules = function (assert, done) { 1.2098 + let test = new TestHelper(assert, done); 1.2099 + let loader0 = test.newLoader(); 1.2100 + let loader1 = test.newLoader(); 1.2101 + 1.2102 + let items0 = []; 1.2103 + let items1 = []; 1.2104 + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { 1.2105 + if (i % 2) { 1.2106 + let item = new loader0.cm.Item({ label: "item " + i }); 1.2107 + items0.push(item); 1.2108 + } 1.2109 + else { 1.2110 + let item = new loader1.cm.Item({ label: "item " + i }); 1.2111 + items1.push(item); 1.2112 + } 1.2113 + } 1.2114 + let allItems = items0.concat(items1); 1.2115 + 1.2116 + test.showMenu(null, function (popup) { 1.2117 + 1.2118 + // All items should be present and sorted. 1.2119 + test.checkMenu(allItems, [], []); 1.2120 + popup.hidePopup(); 1.2121 + loader0.unload(); 1.2122 + loader1.unload(); 1.2123 + test.showMenu(null, function (popup) { 1.2124 + 1.2125 + // All items should be removed. 1.2126 + test.checkMenu(allItems, [], allItems); 1.2127 + test.done(); 1.2128 + }); 1.2129 + }); 1.2130 +}; 1.2131 + 1.2132 + 1.2133 +// Content click handlers and context handlers should be able to communicate, 1.2134 +// i.e., they're eval'ed in the same worker and sandbox. 1.2135 +exports.testContentCommunication = function (assert, done) { 1.2136 + let test = new TestHelper(assert, done); 1.2137 + let loader = test.newLoader(); 1.2138 + 1.2139 + let item = new loader.cm.Item({ 1.2140 + label: "item", 1.2141 + contentScript: 'var potato;' + 1.2142 + 'self.on("context", function () {' + 1.2143 + ' potato = "potato";' + 1.2144 + ' return true;' + 1.2145 + '});' + 1.2146 + 'self.on("click", function () {' + 1.2147 + ' self.postMessage(potato);' + 1.2148 + '});', 1.2149 + }); 1.2150 + 1.2151 + item.on("message", function (data) { 1.2152 + assert.equal(data, "potato", "That's a lot of potatoes!"); 1.2153 + test.done(); 1.2154 + }); 1.2155 + 1.2156 + test.showMenu(null, function (popup) { 1.2157 + test.checkMenu([item], [], []); 1.2158 + let elt = test.getItemElt(popup, item); 1.2159 + elt.click(); 1.2160 + }); 1.2161 +}; 1.2162 + 1.2163 + 1.2164 +// When the context menu is invoked on a tab that was already open when the 1.2165 +// module was loaded, it should contain the expected items and content workers 1.2166 +// should function as expected. 1.2167 +exports.testLoadWithOpenTab = function (assert, done) { 1.2168 + let test = new TestHelper(assert, done); 1.2169 + test.withTestDoc(function (window, doc) { 1.2170 + let loader = test.newLoader(); 1.2171 + let item = new loader.cm.Item({ 1.2172 + label: "item", 1.2173 + contentScript: 1.2174 + 'self.on("click", function () self.postMessage("click"));', 1.2175 + onMessage: function (msg) { 1.2176 + if (msg === "click") 1.2177 + test.done(); 1.2178 + } 1.2179 + }); 1.2180 + test.showMenu(null, function (popup) { 1.2181 + test.checkMenu([item], [], []); 1.2182 + test.getItemElt(popup, item).click(); 1.2183 + }); 1.2184 + }); 1.2185 +}; 1.2186 + 1.2187 +// Bug 732716: Ensure that the node given in `click` event works fine 1.2188 +// (i.e. is correctly wrapped) 1.2189 +exports.testDrawImageOnClickNode = function (assert, done) { 1.2190 + let test = new TestHelper(assert, done); 1.2191 + test.withTestDoc(function (window, doc) { 1.2192 + let loader = test.newLoader(); 1.2193 + let item = new loader.cm.Item({ 1.2194 + label: "item", 1.2195 + context: loader.cm.SelectorContext("img"), 1.2196 + contentScript: "new " + function() { 1.2197 + self.on("click", function (img, data) { 1.2198 + let ctx = document.createElement("canvas").getContext("2d"); 1.2199 + ctx.drawImage(img, 1, 1, 1, 1); 1.2200 + self.postMessage("done"); 1.2201 + }); 1.2202 + }, 1.2203 + onMessage: function (msg) { 1.2204 + if (msg === "done") 1.2205 + test.done(); 1.2206 + } 1.2207 + }); 1.2208 + test.showMenu(doc.getElementById("image"), function (popup) { 1.2209 + test.checkMenu([item], [], []); 1.2210 + test.getItemElt(popup, item).click(); 1.2211 + }); 1.2212 + }); 1.2213 +}; 1.2214 + 1.2215 + 1.2216 +// Setting an item's label before the menu is ever shown should correctly change 1.2217 +// its label. 1.2218 +exports.testSetLabelBeforeShow = function (assert, done) { 1.2219 + let test = new TestHelper(assert, done); 1.2220 + let loader = test.newLoader(); 1.2221 + 1.2222 + let items = [ 1.2223 + new loader.cm.Item({ label: "a" }), 1.2224 + new loader.cm.Item({ label: "b" }) 1.2225 + ] 1.2226 + items[0].label = "z"; 1.2227 + assert.equal(items[0].label, "z"); 1.2228 + 1.2229 + test.showMenu(null, function (popup) { 1.2230 + test.checkMenu(items, [], []); 1.2231 + test.done(); 1.2232 + }); 1.2233 +}; 1.2234 + 1.2235 + 1.2236 +// Setting an item's label after the menu is shown should correctly change its 1.2237 +// label. 1.2238 +exports.testSetLabelAfterShow = function (assert, done) { 1.2239 + let test = new TestHelper(assert, done); 1.2240 + let loader = test.newLoader(); 1.2241 + 1.2242 + let items = [ 1.2243 + new loader.cm.Item({ label: "a" }), 1.2244 + new loader.cm.Item({ label: "b" }) 1.2245 + ]; 1.2246 + 1.2247 + test.showMenu(null, function (popup) { 1.2248 + test.checkMenu(items, [], []); 1.2249 + popup.hidePopup(); 1.2250 + 1.2251 + items[0].label = "z"; 1.2252 + assert.equal(items[0].label, "z"); 1.2253 + test.showMenu(null, function (popup) { 1.2254 + test.checkMenu(items, [], []); 1.2255 + test.done(); 1.2256 + }); 1.2257 + }); 1.2258 +}; 1.2259 + 1.2260 + 1.2261 +// Setting an item's label before the menu is ever shown should correctly change 1.2262 +// its label. 1.2263 +exports.testSetLabelBeforeShowOverflow = function (assert, done) { 1.2264 + let test = new TestHelper(assert, done); 1.2265 + let loader = test.newLoader(); 1.2266 + 1.2267 + let prefs = loader.loader.require("sdk/preferences/service"); 1.2268 + prefs.set(OVERFLOW_THRESH_PREF, 0); 1.2269 + 1.2270 + let items = [ 1.2271 + new loader.cm.Item({ label: "a" }), 1.2272 + new loader.cm.Item({ label: "b" }) 1.2273 + ] 1.2274 + items[0].label = "z"; 1.2275 + assert.equal(items[0].label, "z"); 1.2276 + 1.2277 + test.showMenu(null, function (popup) { 1.2278 + test.checkMenu(items, [], []); 1.2279 + test.done(); 1.2280 + }); 1.2281 +}; 1.2282 + 1.2283 + 1.2284 +// Setting an item's label after the menu is shown should correctly change its 1.2285 +// label. 1.2286 +exports.testSetLabelAfterShowOverflow = function (assert, done) { 1.2287 + let test = new TestHelper(assert, done); 1.2288 + let loader = test.newLoader(); 1.2289 + 1.2290 + let prefs = loader.loader.require("sdk/preferences/service"); 1.2291 + prefs.set(OVERFLOW_THRESH_PREF, 0); 1.2292 + 1.2293 + let items = [ 1.2294 + new loader.cm.Item({ label: "a" }), 1.2295 + new loader.cm.Item({ label: "b" }) 1.2296 + ]; 1.2297 + 1.2298 + test.showMenu(null, function (popup) { 1.2299 + test.checkMenu(items, [], []); 1.2300 + popup.hidePopup(); 1.2301 + 1.2302 + items[0].label = "z"; 1.2303 + assert.equal(items[0].label, "z"); 1.2304 + test.showMenu(null, function (popup) { 1.2305 + test.checkMenu(items, [], []); 1.2306 + test.done(); 1.2307 + }); 1.2308 + }); 1.2309 +}; 1.2310 + 1.2311 + 1.2312 +// Setting the label of an item in a Menu should work. 1.2313 +exports.testSetLabelMenuItem = function (assert, done) { 1.2314 + let test = new TestHelper(assert, done); 1.2315 + let loader = test.newLoader(); 1.2316 + 1.2317 + let menu = loader.cm.Menu({ 1.2318 + label: "menu", 1.2319 + items: [loader.cm.Item({ label: "a" })] 1.2320 + }); 1.2321 + menu.items[0].label = "z"; 1.2322 + 1.2323 + assert.equal(menu.items[0].label, "z"); 1.2324 + 1.2325 + test.showMenu(null, function (popup) { 1.2326 + test.checkMenu([menu], [], []); 1.2327 + test.done(); 1.2328 + }); 1.2329 +}; 1.2330 + 1.2331 + 1.2332 +// Menu.addItem() should work. 1.2333 +exports.testMenuAddItem = function (assert, done) { 1.2334 + let test = new TestHelper(assert, done); 1.2335 + let loader = test.newLoader(); 1.2336 + 1.2337 + let menu = loader.cm.Menu({ 1.2338 + label: "menu", 1.2339 + items: [ 1.2340 + loader.cm.Item({ label: "item 0" }) 1.2341 + ] 1.2342 + }); 1.2343 + menu.addItem(loader.cm.Item({ label: "item 1" })); 1.2344 + menu.addItem(loader.cm.Item({ label: "item 2" })); 1.2345 + 1.2346 + assert.equal(menu.items.length, 3, 1.2347 + "menu should have correct number of items"); 1.2348 + for (let i = 0; i < 3; i++) { 1.2349 + assert.equal(menu.items[i].label, "item " + i, 1.2350 + "item label should be correct"); 1.2351 + assert.equal(menu.items[i].parentMenu, menu, 1.2352 + "item's parent menu should be correct"); 1.2353 + } 1.2354 + 1.2355 + test.showMenu(null, function (popup) { 1.2356 + test.checkMenu([menu], [], []); 1.2357 + test.done(); 1.2358 + }); 1.2359 +}; 1.2360 + 1.2361 + 1.2362 +// Adding the same item twice to a menu should work as expected. 1.2363 +exports.testMenuAddItemTwice = function (assert, done) { 1.2364 + let test = new TestHelper(assert, done); 1.2365 + let loader = test.newLoader(); 1.2366 + 1.2367 + let menu = loader.cm.Menu({ 1.2368 + label: "menu", 1.2369 + items: [] 1.2370 + }); 1.2371 + let subitem = loader.cm.Item({ label: "item 1" }) 1.2372 + menu.addItem(subitem); 1.2373 + menu.addItem(loader.cm.Item({ label: "item 0" })); 1.2374 + menu.addItem(subitem); 1.2375 + 1.2376 + assert.equal(menu.items.length, 2, 1.2377 + "menu should have correct number of items"); 1.2378 + for (let i = 0; i < 2; i++) { 1.2379 + assert.equal(menu.items[i].label, "item " + i, 1.2380 + "item label should be correct"); 1.2381 + } 1.2382 + 1.2383 + test.showMenu(null, function (popup) { 1.2384 + test.checkMenu([menu], [], []); 1.2385 + test.done(); 1.2386 + }); 1.2387 +}; 1.2388 + 1.2389 + 1.2390 +// Menu.removeItem() should work. 1.2391 +exports.testMenuRemoveItem = function (assert, done) { 1.2392 + let test = new TestHelper(assert, done); 1.2393 + let loader = test.newLoader(); 1.2394 + 1.2395 + let subitem = loader.cm.Item({ label: "item 1" }); 1.2396 + let menu = loader.cm.Menu({ 1.2397 + label: "menu", 1.2398 + items: [ 1.2399 + loader.cm.Item({ label: "item 0" }), 1.2400 + subitem, 1.2401 + loader.cm.Item({ label: "item 2" }) 1.2402 + ] 1.2403 + }); 1.2404 + 1.2405 + // Removing twice should be harmless. 1.2406 + menu.removeItem(subitem); 1.2407 + menu.removeItem(subitem); 1.2408 + 1.2409 + assert.equal(subitem.parentMenu, null, 1.2410 + "item's parent menu should be correct"); 1.2411 + 1.2412 + assert.equal(menu.items.length, 2, 1.2413 + "menu should have correct number of items"); 1.2414 + assert.equal(menu.items[0].label, "item 0", 1.2415 + "item label should be correct"); 1.2416 + assert.equal(menu.items[1].label, "item 2", 1.2417 + "item label should be correct"); 1.2418 + 1.2419 + test.showMenu(null, function (popup) { 1.2420 + test.checkMenu([menu], [], []); 1.2421 + test.done(); 1.2422 + }); 1.2423 +}; 1.2424 + 1.2425 + 1.2426 +// Adding an item currently contained in one menu to another menu should work. 1.2427 +exports.testMenuItemSwap = function (assert, done) { 1.2428 + let test = new TestHelper(assert, done); 1.2429 + let loader = test.newLoader(); 1.2430 + 1.2431 + let subitem = loader.cm.Item({ label: "item" }); 1.2432 + let menu0 = loader.cm.Menu({ 1.2433 + label: "menu 0", 1.2434 + items: [subitem] 1.2435 + }); 1.2436 + let menu1 = loader.cm.Menu({ 1.2437 + label: "menu 1", 1.2438 + items: [] 1.2439 + }); 1.2440 + menu1.addItem(subitem); 1.2441 + 1.2442 + assert.equal(menu0.items.length, 0, 1.2443 + "menu should have correct number of items"); 1.2444 + 1.2445 + assert.equal(menu1.items.length, 1, 1.2446 + "menu should have correct number of items"); 1.2447 + assert.equal(menu1.items[0].label, "item", 1.2448 + "item label should be correct"); 1.2449 + 1.2450 + assert.equal(subitem.parentMenu, menu1, 1.2451 + "item's parent menu should be correct"); 1.2452 + 1.2453 + test.showMenu(null, function (popup) { 1.2454 + test.checkMenu([menu0, menu1], [menu0], []); 1.2455 + test.done(); 1.2456 + }); 1.2457 +}; 1.2458 + 1.2459 + 1.2460 +// Destroying an item should remove it from its parent menu. 1.2461 +exports.testMenuItemDestroy = function (assert, done) { 1.2462 + let test = new TestHelper(assert, done); 1.2463 + let loader = test.newLoader(); 1.2464 + 1.2465 + let subitem = loader.cm.Item({ label: "item" }); 1.2466 + let menu = loader.cm.Menu({ 1.2467 + label: "menu", 1.2468 + items: [subitem] 1.2469 + }); 1.2470 + subitem.destroy(); 1.2471 + 1.2472 + assert.equal(menu.items.length, 0, 1.2473 + "menu should have correct number of items"); 1.2474 + assert.equal(subitem.parentMenu, null, 1.2475 + "item's parent menu should be correct"); 1.2476 + 1.2477 + test.showMenu(null, function (popup) { 1.2478 + test.checkMenu([menu], [menu], []); 1.2479 + test.done(); 1.2480 + }); 1.2481 +}; 1.2482 + 1.2483 + 1.2484 +// Setting Menu.items should work. 1.2485 +exports.testMenuItemsSetter = function (assert, done) { 1.2486 + let test = new TestHelper(assert, done); 1.2487 + let loader = test.newLoader(); 1.2488 + 1.2489 + let menu = loader.cm.Menu({ 1.2490 + label: "menu", 1.2491 + items: [ 1.2492 + loader.cm.Item({ label: "old item 0" }), 1.2493 + loader.cm.Item({ label: "old item 1" }) 1.2494 + ] 1.2495 + }); 1.2496 + menu.items = [ 1.2497 + loader.cm.Item({ label: "new item 0" }), 1.2498 + loader.cm.Item({ label: "new item 1" }), 1.2499 + loader.cm.Item({ label: "new item 2" }) 1.2500 + ]; 1.2501 + 1.2502 + assert.equal(menu.items.length, 3, 1.2503 + "menu should have correct number of items"); 1.2504 + for (let i = 0; i < 3; i++) { 1.2505 + assert.equal(menu.items[i].label, "new item " + i, 1.2506 + "item label should be correct"); 1.2507 + assert.equal(menu.items[i].parentMenu, menu, 1.2508 + "item's parent menu should be correct"); 1.2509 + } 1.2510 + 1.2511 + test.showMenu(null, function (popup) { 1.2512 + test.checkMenu([menu], [], []); 1.2513 + test.done(); 1.2514 + }); 1.2515 +}; 1.2516 + 1.2517 + 1.2518 +// Setting Item.data should work. 1.2519 +exports.testItemDataSetter = function (assert, done) { 1.2520 + let test = new TestHelper(assert, done); 1.2521 + let loader = test.newLoader(); 1.2522 + 1.2523 + let item = loader.cm.Item({ label: "old item 0", data: "old" }); 1.2524 + item.data = "new"; 1.2525 + 1.2526 + assert.equal(item.data, "new", "item should have correct data"); 1.2527 + 1.2528 + test.showMenu(null, function (popup) { 1.2529 + test.checkMenu([item], [], []); 1.2530 + test.done(); 1.2531 + }); 1.2532 +}; 1.2533 + 1.2534 + 1.2535 +// Open the test doc, load the module, make sure items appear when context- 1.2536 +// clicking the iframe. 1.2537 +exports.testAlreadyOpenIframe = function (assert, done) { 1.2538 + let test = new TestHelper(assert, done); 1.2539 + test.withTestDoc(function (window, doc) { 1.2540 + let loader = test.newLoader(); 1.2541 + let item = new loader.cm.Item({ 1.2542 + label: "item" 1.2543 + }); 1.2544 + test.showMenu(doc.getElementById("iframe"), function (popup) { 1.2545 + test.checkMenu([item], [], []); 1.2546 + test.done(); 1.2547 + }); 1.2548 + }); 1.2549 +}; 1.2550 + 1.2551 + 1.2552 +// Tests that a missing label throws an exception 1.2553 +exports.testItemNoLabel = function (assert, done) { 1.2554 + let test = new TestHelper(assert, done); 1.2555 + let loader = test.newLoader(); 1.2556 + 1.2557 + try { 1.2558 + new loader.cm.Item({}); 1.2559 + assert.ok(false, "Should have seen exception"); 1.2560 + } 1.2561 + catch (e) { 1.2562 + assert.ok(true, "Should have seen exception"); 1.2563 + } 1.2564 + 1.2565 + try { 1.2566 + new loader.cm.Item({ label: null }); 1.2567 + assert.ok(false, "Should have seen exception"); 1.2568 + } 1.2569 + catch (e) { 1.2570 + assert.ok(true, "Should have seen exception"); 1.2571 + } 1.2572 + 1.2573 + try { 1.2574 + new loader.cm.Item({ label: undefined }); 1.2575 + assert.ok(false, "Should have seen exception"); 1.2576 + } 1.2577 + catch (e) { 1.2578 + assert.ok(true, "Should have seen exception"); 1.2579 + } 1.2580 + 1.2581 + try { 1.2582 + new loader.cm.Item({ label: "" }); 1.2583 + assert.ok(false, "Should have seen exception"); 1.2584 + } 1.2585 + catch (e) { 1.2586 + assert.ok(true, "Should have seen exception"); 1.2587 + } 1.2588 + 1.2589 + test.done(); 1.2590 +} 1.2591 + 1.2592 + 1.2593 +// Tests that items can have an empty data property 1.2594 +exports.testItemNoData = function (assert, done) { 1.2595 + let test = new TestHelper(assert, done); 1.2596 + let loader = test.newLoader(); 1.2597 + 1.2598 + function checkData(data) { 1.2599 + assert.equal(data, undefined, "Data should be undefined"); 1.2600 + } 1.2601 + 1.2602 + let item1 = new loader.cm.Item({ 1.2603 + label: "item 1", 1.2604 + contentScript: 'self.on("click", function(node, data) self.postMessage(data))', 1.2605 + onMessage: checkData 1.2606 + }); 1.2607 + let item2 = new loader.cm.Item({ 1.2608 + label: "item 2", 1.2609 + data: null, 1.2610 + contentScript: 'self.on("click", function(node, data) self.postMessage(data))', 1.2611 + onMessage: checkData 1.2612 + }); 1.2613 + let item3 = new loader.cm.Item({ 1.2614 + label: "item 3", 1.2615 + data: undefined, 1.2616 + contentScript: 'self.on("click", function(node, data) self.postMessage(data))', 1.2617 + onMessage: checkData 1.2618 + }); 1.2619 + 1.2620 + assert.equal(item1.data, undefined, "Should be no defined data"); 1.2621 + assert.equal(item2.data, null, "Should be no defined data"); 1.2622 + assert.equal(item3.data, undefined, "Should be no defined data"); 1.2623 + 1.2624 + test.showMenu(null, function (popup) { 1.2625 + test.checkMenu([item1, item2, item3], [], []); 1.2626 + 1.2627 + let itemElt = test.getItemElt(popup, item1); 1.2628 + itemElt.click(); 1.2629 + 1.2630 + test.hideMenu(function() { 1.2631 + test.showMenu(null, function (popup) { 1.2632 + let itemElt = test.getItemElt(popup, item2); 1.2633 + itemElt.click(); 1.2634 + 1.2635 + test.hideMenu(function() { 1.2636 + test.showMenu(null, function (popup) { 1.2637 + let itemElt = test.getItemElt(popup, item3); 1.2638 + itemElt.click(); 1.2639 + 1.2640 + test.done(); 1.2641 + }); 1.2642 + }); 1.2643 + }); 1.2644 + }); 1.2645 + }); 1.2646 +} 1.2647 + 1.2648 + 1.2649 +// Tests that items without an image don't attempt to show one 1.2650 +exports.testItemNoImage = function (assert, done) { 1.2651 + let test = new TestHelper(assert, done); 1.2652 + let loader = test.newLoader(); 1.2653 + 1.2654 + let item1 = new loader.cm.Item({ label: "item 1" }); 1.2655 + let item2 = new loader.cm.Item({ label: "item 2", image: null }); 1.2656 + let item3 = new loader.cm.Item({ label: "item 3", image: undefined }); 1.2657 + 1.2658 + assert.equal(item1.image, undefined, "Should be no defined image"); 1.2659 + assert.equal(item2.image, null, "Should be no defined image"); 1.2660 + assert.equal(item3.image, undefined, "Should be no defined image"); 1.2661 + 1.2662 + test.showMenu(null, function (popup) { 1.2663 + test.checkMenu([item1, item2, item3], [], []); 1.2664 + 1.2665 + test.done(); 1.2666 + }); 1.2667 +} 1.2668 + 1.2669 + 1.2670 +// Test image support. 1.2671 +exports.testItemImage = function (assert, done) { 1.2672 + let test = new TestHelper(assert, done); 1.2673 + let loader = test.newLoader(); 1.2674 + 1.2675 + let imageURL = data.url("moz_favicon.ico"); 1.2676 + let item = new loader.cm.Item({ label: "item", image: imageURL }); 1.2677 + let menu = new loader.cm.Menu({ label: "menu", image: imageURL, items: [ 1.2678 + loader.cm.Item({ label: "subitem" }) 1.2679 + ]}); 1.2680 + assert.equal(item.image, imageURL, "Should have set the image correctly"); 1.2681 + assert.equal(menu.image, imageURL, "Should have set the image correctly"); 1.2682 + 1.2683 + test.showMenu(null, function (popup) { 1.2684 + test.checkMenu([item, menu], [], []); 1.2685 + 1.2686 + let imageURL2 = data.url("dummy.ico"); 1.2687 + item.image = imageURL2; 1.2688 + menu.image = imageURL2; 1.2689 + assert.equal(item.image, imageURL2, "Should have set the image correctly"); 1.2690 + assert.equal(menu.image, imageURL2, "Should have set the image correctly"); 1.2691 + test.checkMenu([item, menu], [], []); 1.2692 + 1.2693 + item.image = null; 1.2694 + menu.image = null; 1.2695 + assert.equal(item.image, null, "Should have set the image correctly"); 1.2696 + assert.equal(menu.image, null, "Should have set the image correctly"); 1.2697 + test.checkMenu([item, menu], [], []); 1.2698 + 1.2699 + test.done(); 1.2700 + }); 1.2701 +}; 1.2702 + 1.2703 +// Test image URL validation. 1.2704 +exports.testItemImageValidURL = function (assert, done) { 1.2705 + let test = new TestHelper(assert, done); 1.2706 + let loader = test.newLoader(); 1.2707 + 1.2708 + assert.throws(function(){ 1.2709 + new loader.cm.Item({ 1.2710 + label: "item 1", 1.2711 + image: "foo" 1.2712 + }) 1.2713 + }, /Image URL validation failed/ 1.2714 + ); 1.2715 + 1.2716 + assert.throws(function(){ 1.2717 + new loader.cm.Item({ 1.2718 + label: "item 2", 1.2719 + image: false 1.2720 + }) 1.2721 + }, /Image URL validation failed/ 1.2722 + ); 1.2723 + 1.2724 + assert.throws(function(){ 1.2725 + new loader.cm.Item({ 1.2726 + label: "item 3", 1.2727 + image: 0 1.2728 + }) 1.2729 + }, /Image URL validation failed/ 1.2730 + ); 1.2731 + 1.2732 + let imageURL = data.url("moz_favicon.ico"); 1.2733 + let item4 = new loader.cm.Item({ label: "item 4", image: imageURL }); 1.2734 + let item5 = new loader.cm.Item({ label: "item 5", image: null }); 1.2735 + let item6 = new loader.cm.Item({ label: "item 6", image: undefined }); 1.2736 + 1.2737 + assert.equal(item4.image, imageURL, "Should be proper image URL"); 1.2738 + assert.equal(item5.image, null, "Should be null image"); 1.2739 + assert.equal(item6.image, undefined, "Should be undefined image"); 1.2740 + 1.2741 + test.done(); 1.2742 +}; 1.2743 + 1.2744 + 1.2745 +// Menu.destroy should destroy the item tree rooted at that menu. 1.2746 +exports.testMenuDestroy = function (assert, done) { 1.2747 + let test = new TestHelper(assert, done); 1.2748 + let loader = test.newLoader(); 1.2749 + 1.2750 + let menu = loader.cm.Menu({ 1.2751 + label: "menu", 1.2752 + items: [ 1.2753 + loader.cm.Item({ label: "item 0" }), 1.2754 + loader.cm.Menu({ 1.2755 + label: "item 1", 1.2756 + items: [ 1.2757 + loader.cm.Item({ label: "subitem 0" }), 1.2758 + loader.cm.Item({ label: "subitem 1" }), 1.2759 + loader.cm.Item({ label: "subitem 2" }) 1.2760 + ] 1.2761 + }), 1.2762 + loader.cm.Item({ label: "item 2" }) 1.2763 + ] 1.2764 + }); 1.2765 + menu.destroy(); 1.2766 + 1.2767 + /*let numRegistryEntries = 0; 1.2768 + loader.globalScope.browserManager.browserWins.forEach(function (bwin) { 1.2769 + for (let itemID in bwin.items) 1.2770 + numRegistryEntries++; 1.2771 + }); 1.2772 + assert.equal(numRegistryEntries, 0, "All items should be unregistered.");*/ 1.2773 + 1.2774 + test.showMenu(null, function (popup) { 1.2775 + test.checkMenu([menu], [], [menu]); 1.2776 + test.done(); 1.2777 + }); 1.2778 +}; 1.2779 + 1.2780 +// Checks that if a menu contains sub items that are hidden then the menu is 1.2781 +// hidden too. Also checks that content scripts and contexts work for sub items. 1.2782 +exports.testSubItemContextNoMatchHideMenu = function (assert, done) { 1.2783 + let test = new TestHelper(assert, done); 1.2784 + let loader = test.newLoader(); 1.2785 + 1.2786 + let items = [ 1.2787 + loader.cm.Menu({ 1.2788 + label: "menu 1", 1.2789 + items: [ 1.2790 + loader.cm.Item({ 1.2791 + label: "subitem 1", 1.2792 + context: loader.cm.SelectorContext(".foo") 1.2793 + }) 1.2794 + ] 1.2795 + }), 1.2796 + loader.cm.Menu({ 1.2797 + label: "menu 2", 1.2798 + items: [ 1.2799 + loader.cm.Item({ 1.2800 + label: "subitem 2", 1.2801 + contentScript: 'self.on("context", function () false);' 1.2802 + }) 1.2803 + ] 1.2804 + }), 1.2805 + loader.cm.Menu({ 1.2806 + label: "menu 3", 1.2807 + items: [ 1.2808 + loader.cm.Item({ 1.2809 + label: "subitem 3", 1.2810 + context: loader.cm.SelectorContext(".foo") 1.2811 + }), 1.2812 + loader.cm.Item({ 1.2813 + label: "subitem 4", 1.2814 + contentScript: 'self.on("context", function () false);' 1.2815 + }) 1.2816 + ] 1.2817 + }) 1.2818 + ]; 1.2819 + 1.2820 + test.showMenu(null, function (popup) { 1.2821 + test.checkMenu(items, items, []); 1.2822 + test.done(); 1.2823 + }); 1.2824 +}; 1.2825 + 1.2826 + 1.2827 +// Checks that if a menu contains a combination of hidden and visible sub items 1.2828 +// then the menu is still visible too. 1.2829 +exports.testSubItemContextMatch = function (assert, done) { 1.2830 + let test = new TestHelper(assert, done); 1.2831 + let loader = test.newLoader(); 1.2832 + 1.2833 + let hiddenItems = [ 1.2834 + loader.cm.Item({ 1.2835 + label: "subitem 3", 1.2836 + context: loader.cm.SelectorContext(".foo") 1.2837 + }), 1.2838 + loader.cm.Item({ 1.2839 + label: "subitem 6", 1.2840 + contentScript: 'self.on("context", function () false);' 1.2841 + }) 1.2842 + ]; 1.2843 + 1.2844 + let items = [ 1.2845 + loader.cm.Menu({ 1.2846 + label: "menu 1", 1.2847 + items: [ 1.2848 + loader.cm.Item({ 1.2849 + label: "subitem 1", 1.2850 + context: loader.cm.URLContext(TEST_DOC_URL) 1.2851 + }) 1.2852 + ] 1.2853 + }), 1.2854 + loader.cm.Menu({ 1.2855 + label: "menu 2", 1.2856 + items: [ 1.2857 + loader.cm.Item({ 1.2858 + label: "subitem 2", 1.2859 + contentScript: 'self.on("context", function () true);' 1.2860 + }) 1.2861 + ] 1.2862 + }), 1.2863 + loader.cm.Menu({ 1.2864 + label: "menu 3", 1.2865 + items: [ 1.2866 + hiddenItems[0], 1.2867 + loader.cm.Item({ 1.2868 + label: "subitem 4", 1.2869 + contentScript: 'self.on("context", function () true);' 1.2870 + }) 1.2871 + ] 1.2872 + }), 1.2873 + loader.cm.Menu({ 1.2874 + label: "menu 4", 1.2875 + items: [ 1.2876 + loader.cm.Item({ 1.2877 + label: "subitem 5", 1.2878 + context: loader.cm.URLContext(TEST_DOC_URL) 1.2879 + }), 1.2880 + hiddenItems[1] 1.2881 + ] 1.2882 + }), 1.2883 + loader.cm.Menu({ 1.2884 + label: "menu 5", 1.2885 + items: [ 1.2886 + loader.cm.Item({ 1.2887 + label: "subitem 7", 1.2888 + context: loader.cm.URLContext(TEST_DOC_URL) 1.2889 + }), 1.2890 + loader.cm.Item({ 1.2891 + label: "subitem 8", 1.2892 + contentScript: 'self.on("context", function () true);' 1.2893 + }) 1.2894 + ] 1.2895 + }) 1.2896 + ]; 1.2897 + 1.2898 + test.withTestDoc(function (window, doc) { 1.2899 + test.showMenu(null, function (popup) { 1.2900 + test.checkMenu(items, hiddenItems, []); 1.2901 + test.done(); 1.2902 + }); 1.2903 + }); 1.2904 +}; 1.2905 + 1.2906 + 1.2907 +// Child items should default to visible, not to PageContext 1.2908 +exports.testSubItemDefaultVisible = function (assert, done) { 1.2909 + let test = new TestHelper(assert, done); 1.2910 + let loader = test.newLoader(); 1.2911 + 1.2912 + let items = [ 1.2913 + loader.cm.Menu({ 1.2914 + label: "menu 1", 1.2915 + context: loader.cm.SelectorContext("img"), 1.2916 + items: [ 1.2917 + loader.cm.Item({ 1.2918 + label: "subitem 1" 1.2919 + }), 1.2920 + loader.cm.Item({ 1.2921 + label: "subitem 2", 1.2922 + context: loader.cm.SelectorContext("img") 1.2923 + }), 1.2924 + loader.cm.Item({ 1.2925 + label: "subitem 3", 1.2926 + context: loader.cm.SelectorContext("a") 1.2927 + }) 1.2928 + ] 1.2929 + }) 1.2930 + ]; 1.2931 + 1.2932 + // subitem 3 will be hidden 1.2933 + let hiddenItems = [items[0].items[2]]; 1.2934 + 1.2935 + test.withTestDoc(function (window, doc) { 1.2936 + test.showMenu(doc.getElementById("image"), function (popup) { 1.2937 + test.checkMenu(items, hiddenItems, []); 1.2938 + test.done(); 1.2939 + }); 1.2940 + }); 1.2941 +}; 1.2942 + 1.2943 +// Tests that the click event on sub menuitem 1.2944 +// tiggers the click event for the sub menuitem and the parent menu 1.2945 +exports.testSubItemClick = function (assert, done) { 1.2946 + let test = new TestHelper(assert, done); 1.2947 + let loader = test.newLoader(); 1.2948 + 1.2949 + let state = 0; 1.2950 + 1.2951 + let items = [ 1.2952 + loader.cm.Menu({ 1.2953 + label: "menu 1", 1.2954 + items: [ 1.2955 + loader.cm.Item({ 1.2956 + label: "subitem 1", 1.2957 + data: "foobar", 1.2958 + contentScript: 'self.on("click", function (node, data) {' + 1.2959 + ' self.postMessage({' + 1.2960 + ' tagName: node.tagName,' + 1.2961 + ' data: data' + 1.2962 + ' });' + 1.2963 + '});', 1.2964 + onMessage: function(msg) { 1.2965 + assert.equal(msg.tagName, "HTML", "should have seen the right node"); 1.2966 + assert.equal(msg.data, "foobar", "should have seen the right data"); 1.2967 + assert.equal(state, 0, "should have seen the event at the right time"); 1.2968 + state++; 1.2969 + } 1.2970 + }) 1.2971 + ], 1.2972 + contentScript: 'self.on("click", function (node, data) {' + 1.2973 + ' self.postMessage({' + 1.2974 + ' tagName: node.tagName,' + 1.2975 + ' data: data' + 1.2976 + ' });' + 1.2977 + '});', 1.2978 + onMessage: function(msg) { 1.2979 + assert.equal(msg.tagName, "HTML", "should have seen the right node"); 1.2980 + assert.equal(msg.data, "foobar", "should have seen the right data"); 1.2981 + assert.equal(state, 1, "should have seen the event at the right time"); 1.2982 + 1.2983 + test.done(); 1.2984 + } 1.2985 + }) 1.2986 + ]; 1.2987 + 1.2988 + test.withTestDoc(function (window, doc) { 1.2989 + test.showMenu(null, function (popup) { 1.2990 + test.checkMenu(items, [], []); 1.2991 + 1.2992 + let topMenuElt = test.getItemElt(popup, items[0]); 1.2993 + let topMenuPopup = topMenuElt.firstChild; 1.2994 + let itemElt = test.getItemElt(topMenuPopup, items[0].items[0]); 1.2995 + itemElt.click(); 1.2996 + }); 1.2997 + }); 1.2998 +}; 1.2999 + 1.3000 +// Tests that the command event on sub menuitem 1.3001 +// tiggers the click event for the sub menuitem and the parent menu 1.3002 +exports.testSubItemCommand = function (assert, done) { 1.3003 + let test = new TestHelper(assert, done); 1.3004 + let loader = test.newLoader(); 1.3005 + 1.3006 + let state = 0; 1.3007 + 1.3008 + let items = [ 1.3009 + loader.cm.Menu({ 1.3010 + label: "menu 1", 1.3011 + items: [ 1.3012 + loader.cm.Item({ 1.3013 + label: "subitem 1", 1.3014 + data: "foobar", 1.3015 + contentScript: 'self.on("click", function (node, data) {' + 1.3016 + ' self.postMessage({' + 1.3017 + ' tagName: node.tagName,' + 1.3018 + ' data: data' + 1.3019 + ' });' + 1.3020 + '});', 1.3021 + onMessage: function(msg) { 1.3022 + assert.equal(msg.tagName, "HTML", "should have seen the right node"); 1.3023 + assert.equal(msg.data, "foobar", "should have seen the right data"); 1.3024 + assert.equal(state, 0, "should have seen the event at the right time"); 1.3025 + state++; 1.3026 + } 1.3027 + }) 1.3028 + ], 1.3029 + contentScript: 'self.on("click", function (node, data) {' + 1.3030 + ' self.postMessage({' + 1.3031 + ' tagName: node.tagName,' + 1.3032 + ' data: data' + 1.3033 + ' });' + 1.3034 + '});', 1.3035 + onMessage: function(msg) { 1.3036 + assert.equal(msg.tagName, "HTML", "should have seen the right node"); 1.3037 + assert.equal(msg.data, "foobar", "should have seen the right data"); 1.3038 + assert.equal(state, 1, "should have seen the event at the right time"); 1.3039 + state++ 1.3040 + 1.3041 + test.done(); 1.3042 + } 1.3043 + }) 1.3044 + ]; 1.3045 + 1.3046 + test.withTestDoc(function (window, doc) { 1.3047 + test.showMenu(null, function (popup) { 1.3048 + test.checkMenu(items, [], []); 1.3049 + 1.3050 + let topMenuElt = test.getItemElt(popup, items[0]); 1.3051 + let topMenuPopup = topMenuElt.firstChild; 1.3052 + let itemElt = test.getItemElt(topMenuPopup, items[0].items[0]); 1.3053 + 1.3054 + // create a command event 1.3055 + let evt = itemElt.ownerDocument.createEvent('Event'); 1.3056 + evt.initEvent('command', true, true); 1.3057 + itemElt.dispatchEvent(evt); 1.3058 + }); 1.3059 + }); 1.3060 +}; 1.3061 + 1.3062 +// Tests that opening a context menu for an outer frame when an inner frame 1.3063 +// has a selection doesn't activate the SelectionContext 1.3064 +exports.testSelectionInInnerFrameNoMatch = function (assert, done) { 1.3065 + let test = new TestHelper(assert, done); 1.3066 + let loader = test.newLoader(); 1.3067 + 1.3068 + let state = 0; 1.3069 + 1.3070 + let items = [ 1.3071 + loader.cm.Item({ 1.3072 + label: "test item", 1.3073 + context: loader.cm.SelectionContext() 1.3074 + }) 1.3075 + ]; 1.3076 + 1.3077 + test.withTestDoc(function (window, doc) { 1.3078 + let frame = doc.getElementById("iframe"); 1.3079 + frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body); 1.3080 + 1.3081 + test.showMenu(null, function (popup) { 1.3082 + test.checkMenu(items, items, []); 1.3083 + test.done(); 1.3084 + }); 1.3085 + }); 1.3086 +}; 1.3087 + 1.3088 +// Tests that opening a context menu for an inner frame when the inner frame 1.3089 +// has a selection does activate the SelectionContext 1.3090 +exports.testSelectionInInnerFrameMatch = function (assert, done) { 1.3091 + let test = new TestHelper(assert, done); 1.3092 + let loader = test.newLoader(); 1.3093 + 1.3094 + let state = 0; 1.3095 + 1.3096 + let items = [ 1.3097 + loader.cm.Item({ 1.3098 + label: "test item", 1.3099 + context: loader.cm.SelectionContext() 1.3100 + }) 1.3101 + ]; 1.3102 + 1.3103 + test.withTestDoc(function (window, doc) { 1.3104 + let frame = doc.getElementById("iframe"); 1.3105 + frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body); 1.3106 + 1.3107 + test.showMenu(frame.contentDocument.getElementById("text"), function (popup) { 1.3108 + test.checkMenu(items, [], []); 1.3109 + test.done(); 1.3110 + }); 1.3111 + }); 1.3112 +}; 1.3113 + 1.3114 +// Tests that opening a context menu for an inner frame when the outer frame 1.3115 +// has a selection doesn't activate the SelectionContext 1.3116 +exports.testSelectionInOuterFrameNoMatch = function (assert, done) { 1.3117 + let test = new TestHelper(assert, done); 1.3118 + let loader = test.newLoader(); 1.3119 + 1.3120 + let state = 0; 1.3121 + 1.3122 + let items = [ 1.3123 + loader.cm.Item({ 1.3124 + label: "test item", 1.3125 + context: loader.cm.SelectionContext() 1.3126 + }) 1.3127 + ]; 1.3128 + 1.3129 + test.withTestDoc(function (window, doc) { 1.3130 + let frame = doc.getElementById("iframe"); 1.3131 + window.getSelection().selectAllChildren(doc.body); 1.3132 + 1.3133 + test.showMenu(frame.contentDocument.getElementById("text"), function (popup) { 1.3134 + test.checkMenu(items, items, []); 1.3135 + test.done(); 1.3136 + }); 1.3137 + }); 1.3138 +}; 1.3139 + 1.3140 + 1.3141 +// Test that the return value of the predicate function determines if 1.3142 +// item is shown 1.3143 +exports.testPredicateContextControl = function (assert, done) { 1.3144 + let test = new TestHelper(assert, done); 1.3145 + let loader = test.newLoader(); 1.3146 + 1.3147 + let itemTrue = loader.cm.Item({ 1.3148 + label: "visible", 1.3149 + context: loader.cm.PredicateContext(function () { return true; }) 1.3150 + }); 1.3151 + 1.3152 + let itemFalse = loader.cm.Item({ 1.3153 + label: "hidden", 1.3154 + context: loader.cm.PredicateContext(function () { return false; }) 1.3155 + }); 1.3156 + 1.3157 + test.showMenu(null, function (popup) { 1.3158 + test.checkMenu([itemTrue, itemFalse], [itemFalse], []); 1.3159 + test.done(); 1.3160 + }); 1.3161 +}; 1.3162 + 1.3163 +// Test that the data object has the correct document type 1.3164 +exports.testPredicateContextDocumentType = function (assert, done) { 1.3165 + let test = new TestHelper(assert, done); 1.3166 + let loader = test.newLoader(); 1.3167 + 1.3168 + let items = [loader.cm.Item({ 1.3169 + label: "item", 1.3170 + context: loader.cm.PredicateContext(function (data) { 1.3171 + assert.equal(data.documentType, 'text/html'); 1.3172 + return true; 1.3173 + }) 1.3174 + })]; 1.3175 + 1.3176 + test.withTestDoc(function (window, doc) { 1.3177 + test.showMenu(null, function (popup) { 1.3178 + test.checkMenu(items, [], []); 1.3179 + test.done(); 1.3180 + }); 1.3181 + }); 1.3182 +}; 1.3183 + 1.3184 +// Test that the data object has the correct document URL 1.3185 +exports.testPredicateContextDocumentURL = function (assert, done) { 1.3186 + let test = new TestHelper(assert, done); 1.3187 + let loader = test.newLoader(); 1.3188 + 1.3189 + let items = [loader.cm.Item({ 1.3190 + label: "item", 1.3191 + context: loader.cm.PredicateContext(function (data) { 1.3192 + assert.equal(data.documentURL, TEST_DOC_URL); 1.3193 + return true; 1.3194 + }) 1.3195 + })]; 1.3196 + 1.3197 + test.withTestDoc(function (window, doc) { 1.3198 + test.showMenu(null, function (popup) { 1.3199 + test.checkMenu(items, [], []); 1.3200 + test.done(); 1.3201 + }); 1.3202 + }); 1.3203 +}; 1.3204 + 1.3205 + 1.3206 +// Test that the data object has the correct element name 1.3207 +exports.testPredicateContextTargetName = function (assert, done) { 1.3208 + let test = new TestHelper(assert, done); 1.3209 + let loader = test.newLoader(); 1.3210 + 1.3211 + let items = [loader.cm.Item({ 1.3212 + label: "item", 1.3213 + context: loader.cm.PredicateContext(function (data) { 1.3214 + assert.strictEqual(data.targetName, "input"); 1.3215 + return true; 1.3216 + }) 1.3217 + })]; 1.3218 + 1.3219 + test.withTestDoc(function (window, doc) { 1.3220 + test.showMenu(doc.getElementById("button"), function (popup) { 1.3221 + test.checkMenu(items, [], []); 1.3222 + test.done(); 1.3223 + }); 1.3224 + }); 1.3225 +}; 1.3226 + 1.3227 + 1.3228 +// Test that the data object has the correct ID 1.3229 +exports.testPredicateContextTargetIDSet = function (assert, done) { 1.3230 + let test = new TestHelper(assert, done); 1.3231 + let loader = test.newLoader(); 1.3232 + 1.3233 + let items = [loader.cm.Item({ 1.3234 + label: "item", 1.3235 + context: loader.cm.PredicateContext(function (data) { 1.3236 + assert.strictEqual(data.targetID, "button"); 1.3237 + return true; 1.3238 + }) 1.3239 + })]; 1.3240 + 1.3241 + test.withTestDoc(function (window, doc) { 1.3242 + test.showMenu(doc.getElementById("button"), function (popup) { 1.3243 + test.checkMenu(items, [], []); 1.3244 + test.done(); 1.3245 + }); 1.3246 + }); 1.3247 +}; 1.3248 + 1.3249 +// Test that the data object has the correct ID 1.3250 +exports.testPredicateContextTargetIDNotSet = function (assert, done) { 1.3251 + let test = new TestHelper(assert, done); 1.3252 + let loader = test.newLoader(); 1.3253 + 1.3254 + let items = [loader.cm.Item({ 1.3255 + label: "item", 1.3256 + context: loader.cm.PredicateContext(function (data) { 1.3257 + assert.strictEqual(data.targetID, null); 1.3258 + return true; 1.3259 + }) 1.3260 + })]; 1.3261 + 1.3262 + test.withTestDoc(function (window, doc) { 1.3263 + test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) { 1.3264 + test.checkMenu(items, [], []); 1.3265 + test.done(); 1.3266 + }); 1.3267 + }); 1.3268 +}; 1.3269 + 1.3270 +// Test that the data object is showing editable correctly for regular text inputs 1.3271 +exports.testPredicateContextTextBoxIsEditable = function (assert, done) { 1.3272 + let test = new TestHelper(assert, done); 1.3273 + let loader = test.newLoader(); 1.3274 + 1.3275 + let items = [loader.cm.Item({ 1.3276 + label: "item", 1.3277 + context: loader.cm.PredicateContext(function (data) { 1.3278 + assert.strictEqual(data.isEditable, true); 1.3279 + return true; 1.3280 + }) 1.3281 + })]; 1.3282 + 1.3283 + test.withTestDoc(function (window, doc) { 1.3284 + test.showMenu(doc.getElementById("textbox"), function (popup) { 1.3285 + test.checkMenu(items, [], []); 1.3286 + test.done(); 1.3287 + }); 1.3288 + }); 1.3289 +}; 1.3290 + 1.3291 +// Test that the data object is showing editable correctly for readonly text inputs 1.3292 +exports.testPredicateContextReadonlyTextBoxIsNotEditable = function (assert, done) { 1.3293 + let test = new TestHelper(assert, done); 1.3294 + let loader = test.newLoader(); 1.3295 + 1.3296 + let items = [loader.cm.Item({ 1.3297 + label: "item", 1.3298 + context: loader.cm.PredicateContext(function (data) { 1.3299 + assert.strictEqual(data.isEditable, false); 1.3300 + return true; 1.3301 + }) 1.3302 + })]; 1.3303 + 1.3304 + test.withTestDoc(function (window, doc) { 1.3305 + test.showMenu(doc.getElementById("readonly-textbox"), function (popup) { 1.3306 + test.checkMenu(items, [], []); 1.3307 + test.done(); 1.3308 + }); 1.3309 + }); 1.3310 +}; 1.3311 + 1.3312 +// Test that the data object is showing editable correctly for disabled text inputs 1.3313 +exports.testPredicateContextDisabledTextBoxIsNotEditable = function (assert, done) { 1.3314 + let test = new TestHelper(assert, done); 1.3315 + let loader = test.newLoader(); 1.3316 + 1.3317 + let items = [loader.cm.Item({ 1.3318 + label: "item", 1.3319 + context: loader.cm.PredicateContext(function (data) { 1.3320 + assert.strictEqual(data.isEditable, false); 1.3321 + return true; 1.3322 + }) 1.3323 + })]; 1.3324 + 1.3325 + test.withTestDoc(function (window, doc) { 1.3326 + test.showMenu(doc.getElementById("disabled-textbox"), function (popup) { 1.3327 + test.checkMenu(items, [], []); 1.3328 + test.done(); 1.3329 + }); 1.3330 + }); 1.3331 +}; 1.3332 + 1.3333 +// Test that the data object is showing editable correctly for text areas 1.3334 +exports.testPredicateContextTextAreaIsEditable = function (assert, done) { 1.3335 + let test = new TestHelper(assert, done); 1.3336 + let loader = test.newLoader(); 1.3337 + 1.3338 + let items = [loader.cm.Item({ 1.3339 + label: "item", 1.3340 + context: loader.cm.PredicateContext(function (data) { 1.3341 + assert.strictEqual(data.isEditable, true); 1.3342 + return true; 1.3343 + }) 1.3344 + })]; 1.3345 + 1.3346 + test.withTestDoc(function (window, doc) { 1.3347 + test.showMenu(doc.getElementById("textfield"), function (popup) { 1.3348 + test.checkMenu(items, [], []); 1.3349 + test.done(); 1.3350 + }); 1.3351 + }); 1.3352 +}; 1.3353 + 1.3354 +// Test that non-text inputs are not considered editable 1.3355 +exports.testPredicateContextButtonIsNotEditable = function (assert, done) { 1.3356 + let test = new TestHelper(assert, done); 1.3357 + let loader = test.newLoader(); 1.3358 + 1.3359 + let items = [loader.cm.Item({ 1.3360 + label: "item", 1.3361 + context: loader.cm.PredicateContext(function (data) { 1.3362 + assert.strictEqual(data.isEditable, false); 1.3363 + return true; 1.3364 + }) 1.3365 + })]; 1.3366 + 1.3367 + test.withTestDoc(function (window, doc) { 1.3368 + test.showMenu(doc.getElementById("button"), function (popup) { 1.3369 + test.checkMenu(items, [], []); 1.3370 + test.done(); 1.3371 + }); 1.3372 + }); 1.3373 +}; 1.3374 + 1.3375 + 1.3376 +// Test that the data object is showing editable correctly 1.3377 +exports.testPredicateContextNonInputIsNotEditable = function (assert, done) { 1.3378 + let test = new TestHelper(assert, done); 1.3379 + let loader = test.newLoader(); 1.3380 + 1.3381 + let items = [loader.cm.Item({ 1.3382 + label: "item", 1.3383 + context: loader.cm.PredicateContext(function (data) { 1.3384 + assert.strictEqual(data.isEditable, false); 1.3385 + return true; 1.3386 + }) 1.3387 + })]; 1.3388 + 1.3389 + test.withTestDoc(function (window, doc) { 1.3390 + test.showMenu(doc.getElementById("image"), function (popup) { 1.3391 + test.checkMenu(items, [], []); 1.3392 + test.done(); 1.3393 + }); 1.3394 + }); 1.3395 +}; 1.3396 + 1.3397 + 1.3398 +// Test that the data object is showing editable correctly for HTML contenteditable elements 1.3399 +exports.testPredicateContextEditableElement = function (assert, done) { 1.3400 + let test = new TestHelper(assert, done); 1.3401 + let loader = test.newLoader(); 1.3402 + 1.3403 + let items = [loader.cm.Item({ 1.3404 + label: "item", 1.3405 + context: loader.cm.PredicateContext(function (data) { 1.3406 + assert.strictEqual(data.isEditable, true); 1.3407 + return true; 1.3408 + }) 1.3409 + })]; 1.3410 + 1.3411 + test.withTestDoc(function (window, doc) { 1.3412 + test.showMenu(doc.getElementById("editable"), function (popup) { 1.3413 + test.checkMenu(items, [], []); 1.3414 + test.done(); 1.3415 + }); 1.3416 + }); 1.3417 +}; 1.3418 + 1.3419 + 1.3420 +// Test that the data object does not have a selection when there is none 1.3421 +exports.testPredicateContextNoSelectionInPage = function (assert, done) { 1.3422 + let test = new TestHelper(assert, done); 1.3423 + let loader = test.newLoader(); 1.3424 + 1.3425 + let items = [loader.cm.Item({ 1.3426 + label: "item", 1.3427 + context: loader.cm.PredicateContext(function (data) { 1.3428 + assert.strictEqual(data.selectionText, null); 1.3429 + return true; 1.3430 + }) 1.3431 + })]; 1.3432 + 1.3433 + test.withTestDoc(function (window, doc) { 1.3434 + test.showMenu(null, function (popup) { 1.3435 + test.checkMenu(items, [], []); 1.3436 + test.done(); 1.3437 + }); 1.3438 + }); 1.3439 +}; 1.3440 + 1.3441 +// Test that the data object includes the selected page text 1.3442 +exports.testPredicateContextSelectionInPage = function (assert, done) { 1.3443 + let test = new TestHelper(assert, done); 1.3444 + let loader = test.newLoader(); 1.3445 + 1.3446 + let items = [loader.cm.Item({ 1.3447 + label: "item", 1.3448 + context: loader.cm.PredicateContext(function (data) { 1.3449 + // since we might get whitespace 1.3450 + assert.ok(data.selectionText && data.selectionText.search(/^\s*Some text.\s*$/) != -1, 1.3451 + 'Expected "Some text.", got "' + data.selectionText + '"'); 1.3452 + return true; 1.3453 + }) 1.3454 + })]; 1.3455 + 1.3456 + test.withTestDoc(function (window, doc) { 1.3457 + window.getSelection().selectAllChildren(doc.getElementById("text")); 1.3458 + test.showMenu(null, function (popup) { 1.3459 + test.checkMenu(items, [], []); 1.3460 + test.done(); 1.3461 + }); 1.3462 + }); 1.3463 +}; 1.3464 + 1.3465 +// Test that the data object includes the selected input text 1.3466 +exports.testPredicateContextSelectionInTextBox = function (assert, done) { 1.3467 + let test = new TestHelper(assert, done); 1.3468 + let loader = test.newLoader(); 1.3469 + 1.3470 + let items = [loader.cm.Item({ 1.3471 + label: "item", 1.3472 + context: loader.cm.PredicateContext(function (data) { 1.3473 + // since we might get whitespace 1.3474 + assert.strictEqual(data.selectionText, "t v"); 1.3475 + return true; 1.3476 + }) 1.3477 + })]; 1.3478 + 1.3479 + test.withTestDoc(function (window, doc) { 1.3480 + let textbox = doc.getElementById("textbox"); 1.3481 + textbox.focus(); 1.3482 + textbox.setSelectionRange(3, 6); 1.3483 + test.showMenu(textbox, function (popup) { 1.3484 + test.checkMenu(items, [], []); 1.3485 + test.done(); 1.3486 + }); 1.3487 + }); 1.3488 +}; 1.3489 + 1.3490 +// Test that the data object has the correct src for an image 1.3491 +exports.testPredicateContextTargetSrcSet = function (assert, done) { 1.3492 + let test = new TestHelper(assert, done); 1.3493 + let loader = test.newLoader(); 1.3494 + let image; 1.3495 + 1.3496 + let items = [loader.cm.Item({ 1.3497 + label: "item", 1.3498 + context: loader.cm.PredicateContext(function (data) { 1.3499 + assert.strictEqual(data.srcURL, image.src); 1.3500 + return true; 1.3501 + }) 1.3502 + })]; 1.3503 + 1.3504 + test.withTestDoc(function (window, doc) { 1.3505 + image = doc.getElementById("image"); 1.3506 + test.showMenu(image, function (popup) { 1.3507 + test.checkMenu(items, [], []); 1.3508 + test.done(); 1.3509 + }); 1.3510 + }); 1.3511 +}; 1.3512 + 1.3513 +// Test that the data object has no src for a link 1.3514 +exports.testPredicateContextTargetSrcNotSet = function (assert, done) { 1.3515 + let test = new TestHelper(assert, done); 1.3516 + let loader = test.newLoader(); 1.3517 + 1.3518 + let items = [loader.cm.Item({ 1.3519 + label: "item", 1.3520 + context: loader.cm.PredicateContext(function (data) { 1.3521 + assert.strictEqual(data.srcURL, null); 1.3522 + return true; 1.3523 + }) 1.3524 + })]; 1.3525 + 1.3526 + test.withTestDoc(function (window, doc) { 1.3527 + test.showMenu(doc.getElementById("link"), function (popup) { 1.3528 + test.checkMenu(items, [], []); 1.3529 + test.done(); 1.3530 + }); 1.3531 + }); 1.3532 +}; 1.3533 + 1.3534 + 1.3535 +// Test that the data object has the correct link set 1.3536 +exports.testPredicateContextTargetLinkSet = function (assert, done) { 1.3537 + let test = new TestHelper(assert, done); 1.3538 + let loader = test.newLoader(); 1.3539 + let image; 1.3540 + 1.3541 + let items = [loader.cm.Item({ 1.3542 + label: "item", 1.3543 + context: loader.cm.PredicateContext(function (data) { 1.3544 + assert.strictEqual(data.linkURL, TEST_DOC_URL + "#test"); 1.3545 + return true; 1.3546 + }) 1.3547 + })]; 1.3548 + 1.3549 + test.withTestDoc(function (window, doc) { 1.3550 + test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) { 1.3551 + test.checkMenu(items, [], []); 1.3552 + test.done(); 1.3553 + }); 1.3554 + }); 1.3555 +}; 1.3556 + 1.3557 +// Test that the data object has no link for an image 1.3558 +exports.testPredicateContextTargetLinkNotSet = function (assert, done) { 1.3559 + let test = new TestHelper(assert, done); 1.3560 + let loader = test.newLoader(); 1.3561 + 1.3562 + let items = [loader.cm.Item({ 1.3563 + label: "item", 1.3564 + context: loader.cm.PredicateContext(function (data) { 1.3565 + assert.strictEqual(data.linkURL, null); 1.3566 + return true; 1.3567 + }) 1.3568 + })]; 1.3569 + 1.3570 + test.withTestDoc(function (window, doc) { 1.3571 + test.showMenu(doc.getElementById("image"), function (popup) { 1.3572 + test.checkMenu(items, [], []); 1.3573 + test.done(); 1.3574 + }); 1.3575 + }); 1.3576 +}; 1.3577 + 1.3578 +// Test that the data object has the value for an input textbox 1.3579 +exports.testPredicateContextTargetValueSet = function (assert, done) { 1.3580 + let test = new TestHelper(assert, done); 1.3581 + let loader = test.newLoader(); 1.3582 + let image; 1.3583 + 1.3584 + let items = [loader.cm.Item({ 1.3585 + label: "item", 1.3586 + context: loader.cm.PredicateContext(function (data) { 1.3587 + assert.strictEqual(data.value, "test value"); 1.3588 + return true; 1.3589 + }) 1.3590 + })]; 1.3591 + 1.3592 + test.withTestDoc(function (window, doc) { 1.3593 + test.showMenu(doc.getElementById("textbox"), function (popup) { 1.3594 + test.checkMenu(items, [], []); 1.3595 + test.done(); 1.3596 + }); 1.3597 + }); 1.3598 +}; 1.3599 + 1.3600 +// Test that the data object has no value for an image 1.3601 +exports.testPredicateContextTargetValueNotSet = function (assert, done) { 1.3602 + let test = new TestHelper(assert, done); 1.3603 + let loader = test.newLoader(); 1.3604 + 1.3605 + let items = [loader.cm.Item({ 1.3606 + label: "item", 1.3607 + context: loader.cm.PredicateContext(function (data) { 1.3608 + assert.strictEqual(data.value, null); 1.3609 + return true; 1.3610 + }) 1.3611 + })]; 1.3612 + 1.3613 + test.withTestDoc(function (window, doc) { 1.3614 + test.showMenu(doc.getElementById("image"), function (popup) { 1.3615 + test.checkMenu(items, [], []); 1.3616 + test.done(); 1.3617 + }); 1.3618 + }); 1.3619 +}; 1.3620 + 1.3621 + 1.3622 +// NO TESTS BELOW THIS LINE! /////////////////////////////////////////////////// 1.3623 + 1.3624 +// This makes it easier to run tests by handling things like opening the menu, 1.3625 +// opening new windows, making assertions, etc. Methods on |test| can be called 1.3626 +// on instances of this class. Don't forget to call done() to end the test! 1.3627 +// WARNING: This looks up items in popups by comparing labels, so don't give two 1.3628 +// items the same label. 1.3629 +function TestHelper(assert, done) { 1.3630 + this.assert = assert; 1.3631 + this.end = done; 1.3632 + this.loaders = []; 1.3633 + this.browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. 1.3634 + getService(Ci.nsIWindowMediator). 1.3635 + getMostRecentWindow("navigator:browser"); 1.3636 + this.overflowThreshValue = require("sdk/preferences/service"). 1.3637 + get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT); 1.3638 +} 1.3639 + 1.3640 +TestHelper.prototype = { 1.3641 + get contextMenuPopup() { 1.3642 + return this.browserWindow.document.getElementById("contentAreaContextMenu"); 1.3643 + }, 1.3644 + 1.3645 + get contextMenuSeparator() { 1.3646 + return this.browserWindow.document.querySelector("." + SEPARATOR_CLASS); 1.3647 + }, 1.3648 + 1.3649 + get overflowPopup() { 1.3650 + return this.browserWindow.document.querySelector("." + OVERFLOW_POPUP_CLASS); 1.3651 + }, 1.3652 + 1.3653 + get overflowSubmenu() { 1.3654 + return this.browserWindow.document.querySelector("." + OVERFLOW_MENU_CLASS); 1.3655 + }, 1.3656 + 1.3657 + get tabBrowser() { 1.3658 + return this.browserWindow.gBrowser; 1.3659 + }, 1.3660 + 1.3661 + // Methods on the wrapped test can be called on this object. 1.3662 + __noSuchMethod__: function (methodName, args) { 1.3663 + this.assert[methodName].apply(this.assert, args); 1.3664 + }, 1.3665 + 1.3666 + // Asserts that elt, a DOM element representing item, looks OK. 1.3667 + checkItemElt: function (elt, item) { 1.3668 + let itemType = this.getItemType(item); 1.3669 + 1.3670 + switch (itemType) { 1.3671 + case "Item": 1.3672 + this.assert.equal(elt.localName, "menuitem", 1.3673 + "Item DOM element should be a xul:menuitem"); 1.3674 + if (typeof(item.data) === "string") { 1.3675 + this.assert.equal(elt.getAttribute("value"), item.data, 1.3676 + "Item should have correct data"); 1.3677 + } 1.3678 + break 1.3679 + case "Menu": 1.3680 + this.assert.equal(elt.localName, "menu", 1.3681 + "Menu DOM element should be a xul:menu"); 1.3682 + let subPopup = elt.firstChild; 1.3683 + this.assert.ok(subPopup, "xul:menu should have a child"); 1.3684 + this.assert.equal(subPopup.localName, "menupopup", 1.3685 + "xul:menu's first child should be a menupopup"); 1.3686 + break; 1.3687 + case "Separator": 1.3688 + this.assert.equal(elt.localName, "menuseparator", 1.3689 + "Separator DOM element should be a xul:menuseparator"); 1.3690 + break; 1.3691 + } 1.3692 + 1.3693 + if (itemType === "Item" || itemType === "Menu") { 1.3694 + this.assert.equal(elt.getAttribute("label"), item.label, 1.3695 + "Item should have correct title"); 1.3696 + if (typeof(item.image) === "string") { 1.3697 + this.assert.equal(elt.getAttribute("image"), item.image, 1.3698 + "Item should have correct image"); 1.3699 + if (itemType === "Menu") 1.3700 + this.assert.ok(elt.classList.contains("menu-iconic"), 1.3701 + "Menus with images should have the correct class") 1.3702 + else 1.3703 + this.assert.ok(elt.classList.contains("menuitem-iconic"), 1.3704 + "Items with images should have the correct class") 1.3705 + } 1.3706 + else { 1.3707 + this.assert.ok(!elt.getAttribute("image"), 1.3708 + "Item should not have image"); 1.3709 + this.assert.ok(!elt.classList.contains("menu-iconic") && !elt.classList.contains("menuitem-iconic"), 1.3710 + "The iconic classes should not be present") 1.3711 + } 1.3712 + } 1.3713 + }, 1.3714 + 1.3715 + // Asserts that the context menu looks OK given the arguments. presentItems 1.3716 + // are items that have been added to the menu. absentItems are items that 1.3717 + // shouldn't match the current context. removedItems are items that have been 1.3718 + // removed from the menu. 1.3719 + checkMenu: function (presentItems, absentItems, removedItems) { 1.3720 + // Count up how many top-level items there are 1.3721 + let total = 0; 1.3722 + for (let item of presentItems) { 1.3723 + if (absentItems.indexOf(item) < 0 && removedItems.indexOf(item) < 0) 1.3724 + total++; 1.3725 + } 1.3726 + 1.3727 + let separator = this.contextMenuSeparator; 1.3728 + if (total == 0) { 1.3729 + this.assert.ok(!separator || separator.hidden, 1.3730 + "separator should not be present"); 1.3731 + } 1.3732 + else { 1.3733 + this.assert.ok(separator && !separator.hidden, 1.3734 + "separator should be present"); 1.3735 + } 1.3736 + 1.3737 + let mainNodes = this.browserWindow.document.querySelectorAll("#contentAreaContextMenu > ." + ITEM_CLASS); 1.3738 + let overflowNodes = this.browserWindow.document.querySelectorAll("." + OVERFLOW_POPUP_CLASS + " > ." + ITEM_CLASS); 1.3739 + 1.3740 + this.assert.ok(mainNodes.length == 0 || overflowNodes.length == 0, 1.3741 + "Should only see nodes at the top level or in overflow"); 1.3742 + 1.3743 + let overflow = this.overflowSubmenu; 1.3744 + if (this.shouldOverflow(total)) { 1.3745 + this.assert.ok(overflow && !overflow.hidden, 1.3746 + "overflow menu should be present"); 1.3747 + this.assert.equal(mainNodes.length, 0, 1.3748 + "should be no items in the main context menu"); 1.3749 + } 1.3750 + else { 1.3751 + this.assert.ok(!overflow || overflow.hidden, 1.3752 + "overflow menu should not be present"); 1.3753 + // When visible nodes == 0 they could be in overflow or top level 1.3754 + if (total > 0) { 1.3755 + this.assert.equal(overflowNodes.length, 0, 1.3756 + "should be no items in the overflow context menu"); 1.3757 + } 1.3758 + } 1.3759 + 1.3760 + // Iterate over wherever the nodes have ended up 1.3761 + let nodes = mainNodes.length ? mainNodes : overflowNodes; 1.3762 + this.checkNodes(nodes, presentItems, absentItems, removedItems) 1.3763 + let pos = 0; 1.3764 + }, 1.3765 + 1.3766 + // Recurses through the item hierarchy of presentItems comparing it to the 1.3767 + // node hierarchy of nodes. Any items in removedItems will be skipped (so 1.3768 + // should not exist in the XUL), any items in absentItems must exist and be 1.3769 + // hidden 1.3770 + checkNodes: function (nodes, presentItems, absentItems, removedItems) { 1.3771 + let pos = 0; 1.3772 + for (let item of presentItems) { 1.3773 + // Removed items shouldn't be in the list 1.3774 + if (removedItems.indexOf(item) >= 0) 1.3775 + continue; 1.3776 + 1.3777 + if (nodes.length <= pos) { 1.3778 + this.assert.ok(false, "Not enough nodes"); 1.3779 + return; 1.3780 + } 1.3781 + 1.3782 + let hidden = absentItems.indexOf(item) >= 0; 1.3783 + 1.3784 + this.checkItemElt(nodes[pos], item); 1.3785 + this.assert.equal(nodes[pos].hidden, hidden, 1.3786 + "hidden should be set correctly"); 1.3787 + 1.3788 + // The contents of hidden menus doesn't matter so much 1.3789 + if (!hidden && this.getItemType(item) == "Menu") { 1.3790 + this.assert.equal(nodes[pos].firstChild.localName, "menupopup", 1.3791 + "menu XUL should contain a menupopup"); 1.3792 + this.checkNodes(nodes[pos].firstChild.childNodes, item.items, absentItems, removedItems); 1.3793 + } 1.3794 + 1.3795 + if (pos > 0) 1.3796 + this.assert.equal(nodes[pos].previousSibling, nodes[pos - 1], 1.3797 + "nodes should all be in the same group"); 1.3798 + pos++; 1.3799 + } 1.3800 + 1.3801 + this.assert.equal(nodes.length, pos, 1.3802 + "should have checked all the XUL nodes"); 1.3803 + }, 1.3804 + 1.3805 + // Attaches an event listener to node. The listener is automatically removed 1.3806 + // when it's fired (so it's assumed it will fire), and callback is called 1.3807 + // after a short delay. Since the module we're testing relies on the same 1.3808 + // event listeners to do its work, this is to give them a little breathing 1.3809 + // room before callback runs. Inside callback |this| is this object. 1.3810 + // Optionally you can pass a function to test if the event is the event you 1.3811 + // want. 1.3812 + delayedEventListener: function (node, event, callback, useCapture, isValid) { 1.3813 + const self = this; 1.3814 + node.addEventListener(event, function handler(evt) { 1.3815 + if (isValid && !isValid(evt)) 1.3816 + return; 1.3817 + node.removeEventListener(event, handler, useCapture); 1.3818 + timer.setTimeout(function () { 1.3819 + try { 1.3820 + callback.call(self, evt); 1.3821 + } 1.3822 + catch (err) { 1.3823 + self.assert.fail(err); 1.3824 + self.end(); 1.3825 + } 1.3826 + }, 20); 1.3827 + }, useCapture); 1.3828 + }, 1.3829 + 1.3830 + // Call to finish the test. 1.3831 + done: function () { 1.3832 + const self = this; 1.3833 + function commonDone() { 1.3834 + this.closeTab(); 1.3835 + 1.3836 + while (this.loaders.length) { 1.3837 + this.loaders[0].unload(); 1.3838 + } 1.3839 + 1.3840 + require("sdk/preferences/service").set(OVERFLOW_THRESH_PREF, self.overflowThreshValue); 1.3841 + 1.3842 + this.end(); 1.3843 + } 1.3844 + 1.3845 + function closeBrowserWindow() { 1.3846 + if (this.oldBrowserWindow) { 1.3847 + this.delayedEventListener(this.browserWindow, "unload", commonDone, 1.3848 + false); 1.3849 + this.browserWindow.close(); 1.3850 + this.browserWindow = this.oldBrowserWindow; 1.3851 + delete this.oldBrowserWindow; 1.3852 + } 1.3853 + else { 1.3854 + commonDone.call(this); 1.3855 + } 1.3856 + }; 1.3857 + 1.3858 + if (this.contextMenuPopup.state == "closed") { 1.3859 + closeBrowserWindow.call(this); 1.3860 + } 1.3861 + else { 1.3862 + this.delayedEventListener(this.contextMenuPopup, "popuphidden", 1.3863 + function () closeBrowserWindow.call(this), 1.3864 + false); 1.3865 + this.contextMenuPopup.hidePopup(); 1.3866 + } 1.3867 + }, 1.3868 + 1.3869 + closeTab: function() { 1.3870 + if (this.tab) { 1.3871 + this.tabBrowser.removeTab(this.tab); 1.3872 + this.tabBrowser.selectedTab = this.oldSelectedTab; 1.3873 + this.tab = null; 1.3874 + } 1.3875 + }, 1.3876 + 1.3877 + // Returns the DOM element in popup corresponding to item. 1.3878 + // WARNING: The element is found by comparing labels, so don't give two items 1.3879 + // the same label. 1.3880 + getItemElt: function (popup, item) { 1.3881 + let nodes = popup.childNodes; 1.3882 + for (let i = nodes.length - 1; i >= 0; i--) { 1.3883 + if (this.getItemType(item) === "Separator") { 1.3884 + if (nodes[i].localName === "menuseparator") 1.3885 + return nodes[i]; 1.3886 + } 1.3887 + else if (nodes[i].getAttribute("label") === item.label) 1.3888 + return nodes[i]; 1.3889 + } 1.3890 + return null; 1.3891 + }, 1.3892 + 1.3893 + // Returns "Item", "Menu", or "Separator". 1.3894 + getItemType: function (item) { 1.3895 + // Could use instanceof here, but that would require accessing the loader 1.3896 + // that created the item, and I don't want to A) somehow search through the 1.3897 + // this.loaders list to find it, and B) assume there are any live loaders at 1.3898 + // all. 1.3899 + return /^\[object (Item|Menu|Separator)/.exec(item.toString())[1]; 1.3900 + }, 1.3901 + 1.3902 + // Returns a wrapper around a new loader: { loader, cm, unload, globalScope }. 1.3903 + // loader is a Cuddlefish sandboxed loader, cm is the context menu module, 1.3904 + // globalScope is the context menu module's global scope, and unload is a 1.3905 + // function that unloads the loader and associated resources. 1.3906 + newLoader: function () { 1.3907 + const self = this; 1.3908 + let loader = Loader(module); 1.3909 + let wrapper = { 1.3910 + loader: loader, 1.3911 + cm: loader.require("sdk/context-menu"), 1.3912 + globalScope: loader.sandbox("sdk/context-menu"), 1.3913 + unload: function () { 1.3914 + loader.unload(); 1.3915 + let idx = self.loaders.indexOf(wrapper); 1.3916 + if (idx < 0) 1.3917 + throw new Error("Test error: tried to unload nonexistent loader"); 1.3918 + self.loaders.splice(idx, 1); 1.3919 + } 1.3920 + }; 1.3921 + this.loaders.push(wrapper); 1.3922 + return wrapper; 1.3923 + }, 1.3924 + 1.3925 + // As above but the loader has private-browsing support enabled. 1.3926 + newPrivateLoader: function() { 1.3927 + let base = require("@loader/options"); 1.3928 + 1.3929 + // Clone current loader's options adding the private-browsing permission 1.3930 + let options = merge({}, base, { 1.3931 + metadata: merge({}, base.metadata || {}, { 1.3932 + permissions: merge({}, base.metadata.permissions || {}, { 1.3933 + 'private-browsing': true 1.3934 + }) 1.3935 + }) 1.3936 + }); 1.3937 + 1.3938 + const self = this; 1.3939 + let loader = Loader(module, null, options); 1.3940 + let wrapper = { 1.3941 + loader: loader, 1.3942 + cm: loader.require("sdk/context-menu"), 1.3943 + globalScope: loader.sandbox("sdk/context-menu"), 1.3944 + unload: function () { 1.3945 + loader.unload(); 1.3946 + let idx = self.loaders.indexOf(wrapper); 1.3947 + if (idx < 0) 1.3948 + throw new Error("Test error: tried to unload nonexistent loader"); 1.3949 + self.loaders.splice(idx, 1); 1.3950 + } 1.3951 + }; 1.3952 + this.loaders.push(wrapper); 1.3953 + return wrapper; 1.3954 + }, 1.3955 + 1.3956 + // Returns true if the count crosses the overflow threshold. 1.3957 + shouldOverflow: function (count) { 1.3958 + return count > 1.3959 + (this.loaders.length ? 1.3960 + this.loaders[0].loader.require("sdk/preferences/service"). 1.3961 + get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) : 1.3962 + OVERFLOW_THRESH_DEFAULT); 1.3963 + }, 1.3964 + 1.3965 + // Opens the context menu on the current page. If targetNode is null, the 1.3966 + // menu is opened in the top-left corner. onShowncallback is passed the 1.3967 + // popup. 1.3968 + showMenu: function(targetNode, onshownCallback) { 1.3969 + function sendEvent() { 1.3970 + this.delayedEventListener(this.browserWindow, "popupshowing", 1.3971 + function (e) { 1.3972 + let popup = e.target; 1.3973 + onshownCallback.call(this, popup); 1.3974 + }, false); 1.3975 + 1.3976 + let rect = targetNode ? 1.3977 + targetNode.getBoundingClientRect() : 1.3978 + { left: 0, top: 0, width: 0, height: 0 }; 1.3979 + let contentWin = targetNode ? targetNode.ownerDocument.defaultView 1.3980 + : this.browserWindow.content; 1.3981 + contentWin. 1.3982 + QueryInterface(Ci.nsIInterfaceRequestor). 1.3983 + getInterface(Ci.nsIDOMWindowUtils). 1.3984 + sendMouseEvent("contextmenu", 1.3985 + rect.left + (rect.width / 2), 1.3986 + rect.top + (rect.height / 2), 1.3987 + 2, 1, 0); 1.3988 + } 1.3989 + 1.3990 + // If a new tab or window has not yet been opened, open a new tab now. For 1.3991 + // some reason using the tab already opened when the test starts causes 1.3992 + // leaks. See bug 566351 for details. 1.3993 + if (!targetNode && !this.oldSelectedTab && !this.oldBrowserWindow) { 1.3994 + this.oldSelectedTab = this.tabBrowser.selectedTab; 1.3995 + this.tab = this.tabBrowser.addTab("about:blank"); 1.3996 + let browser = this.tabBrowser.getBrowserForTab(this.tab); 1.3997 + 1.3998 + this.delayedEventListener(browser, "load", function () { 1.3999 + this.tabBrowser.selectedTab = this.tab; 1.4000 + sendEvent.call(this); 1.4001 + }, true); 1.4002 + } 1.4003 + else 1.4004 + sendEvent.call(this); 1.4005 + }, 1.4006 + 1.4007 + hideMenu: function(onhiddenCallback) { 1.4008 + this.delayedEventListener(this.browserWindow, "popuphidden", onhiddenCallback); 1.4009 + 1.4010 + this.contextMenuPopup.hidePopup(); 1.4011 + }, 1.4012 + 1.4013 + // Opens a new browser window. The window will be closed automatically when 1.4014 + // done() is called. 1.4015 + withNewWindow: function (onloadCallback) { 1.4016 + let win = this.browserWindow.OpenBrowserWindow(); 1.4017 + this.delayedEventListener(win, "load", onloadCallback, true); 1.4018 + this.oldBrowserWindow = this.browserWindow; 1.4019 + this.browserWindow = win; 1.4020 + }, 1.4021 + 1.4022 + // Opens a new private browser window. The window will be closed 1.4023 + // automatically when done() is called. 1.4024 + withNewPrivateWindow: function (onloadCallback) { 1.4025 + let win = this.browserWindow.OpenBrowserWindow({private: true}); 1.4026 + this.delayedEventListener(win, "load", onloadCallback, true); 1.4027 + this.oldBrowserWindow = this.browserWindow; 1.4028 + this.browserWindow = win; 1.4029 + }, 1.4030 + 1.4031 + // Opens a new tab with our test page in the current window. The tab will 1.4032 + // be closed automatically when done() is called. 1.4033 + withTestDoc: function (onloadCallback) { 1.4034 + this.oldSelectedTab = this.tabBrowser.selectedTab; 1.4035 + this.tab = this.tabBrowser.addTab(TEST_DOC_URL); 1.4036 + let browser = this.tabBrowser.getBrowserForTab(this.tab); 1.4037 + 1.4038 + this.delayedEventListener(browser, "load", function () { 1.4039 + this.tabBrowser.selectedTab = this.tab; 1.4040 + onloadCallback.call(this, browser.contentWindow, browser.contentDocument); 1.4041 + }, true, function(evt) { 1.4042 + return evt.target.location == TEST_DOC_URL; 1.4043 + }); 1.4044 + } 1.4045 +}; 1.4046 + 1.4047 +require('sdk/test').run(exports);