addon-sdk/source/test/test-context-menu.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     4  'use strict';
     6 let { Cc, Ci } = require("chrome");
     8 require("sdk/context-menu");
    10 const { Loader } = require('sdk/test/loader');
    11 const timer = require("sdk/timers");
    12 const { merge } = require("sdk/util/object");
    14 // These should match the same constants in the module.
    15 const ITEM_CLASS = "addon-context-menu-item";
    16 const SEPARATOR_CLASS = "addon-context-menu-separator";
    17 const OVERFLOW_THRESH_DEFAULT = 10;
    18 const OVERFLOW_THRESH_PREF =
    19   "extensions.addon-sdk.context-menu.overflowThreshold";
    20 const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";
    21 const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup";
    23 const TEST_DOC_URL = module.uri.replace(/\.js$/, ".html");
    24 const data = require("./fixtures");
    26 // Tests that when present the separator is placed before the separator from
    27 // the old context-menu module
    28 exports.testSeparatorPosition = function (assert, done) {
    29   let test = new TestHelper(assert, done);
    30   let loader = test.newLoader();
    32   // Create the old separator
    33   let oldSeparator = test.contextMenuPopup.ownerDocument.createElement("menuseparator");
    34   oldSeparator.id = "jetpack-context-menu-separator";
    35   test.contextMenuPopup.appendChild(oldSeparator);
    37   // Create an item.
    38   let item = new loader.cm.Item({ label: "item" });
    40   test.showMenu(null, function (popup) {
    41     assert.equal(test.contextMenuSeparator.nextSibling.nextSibling, oldSeparator,
    42                      "New separator should appear before the old one");
    43     test.contextMenuPopup.removeChild(oldSeparator);
    44     test.done();
    45   });
    46 };
    48 // Destroying items that were previously created should cause them to be absent
    49 // from the menu.
    50 exports.testConstructDestroy = function (assert, done) {
    51   let test = new TestHelper(assert, done);
    52   let loader = test.newLoader();
    54   // Create an item.
    55   let item = new loader.cm.Item({ label: "item" });
    56   assert.equal(item.parentMenu, loader.cm.contentContextMenu,
    57                    "item's parent menu should be correct");
    59   test.showMenu(null, function (popup) {
    61     // It should be present when the menu is shown.
    62     test.checkMenu([item], [], []);
    63     popup.hidePopup();
    65     // Destroy the item.  Multiple destroys should be harmless.
    66     item.destroy();
    67     item.destroy();
    68     test.showMenu(null, function (popup) {
    70       // It should be removed from the menu.
    71       test.checkMenu([item], [], [item]);
    72       test.done();
    73     });
    74   });
    75 };
    78 // Destroying an item twice should not cause an error.
    79 exports.testDestroyTwice = function (assert, done) {
    80   let test = new TestHelper(assert, done);
    81   let loader = test.newLoader();
    83   let item = new loader.cm.Item({ label: "item" });
    84   item.destroy();
    85   item.destroy();
    87   test.pass("Destroying an item twice should not cause an error.");
    88   test.done();
    89 };
    92 // CSS selector contexts should cause their items to be present in the menu
    93 // when the menu is invoked on nodes that match the selectors.
    94 exports.testSelectorContextMatch = function (assert, done) {
    95   let test = new TestHelper(assert, done);
    96   let loader = test.newLoader();
    98   let item = new loader.cm.Item({
    99     label: "item",
   100     data: "item",
   101     context: loader.cm.SelectorContext("img")
   102   });
   104   test.withTestDoc(function (window, doc) {
   105     test.showMenu(doc.getElementById("image"), function (popup) {
   106       test.checkMenu([item], [], []);
   107       test.done();
   108     });
   109   });
   110 };
   113 // CSS selector contexts should cause their items to be present in the menu
   114 // when the menu is invoked on nodes that have ancestors that match the
   115 // selectors.
   116 exports.testSelectorAncestorContextMatch = function (assert, done) {
   117   let test = new TestHelper(assert, done);
   118   let loader = test.newLoader();
   120   let item = new loader.cm.Item({
   121     label: "item",
   122     data: "item",
   123     context: loader.cm.SelectorContext("a[href]")
   124   });
   126   test.withTestDoc(function (window, doc) {
   127     test.showMenu(doc.getElementById("span-link"), function (popup) {
   128       test.checkMenu([item], [], []);
   129       test.done();
   130     });
   131   });
   132 };
   135 // CSS selector contexts should cause their items to be absent from the menu
   136 // when the menu is not invoked on nodes that match or have ancestors that
   137 // match the selectors.
   138 exports.testSelectorContextNoMatch = function (assert, done) {
   139   let test = new TestHelper(assert, done);
   140   let loader = test.newLoader();
   142   let item = new loader.cm.Item({
   143     label: "item",
   144     data: "item",
   145     context: loader.cm.SelectorContext("img")
   146   });
   148   test.showMenu(null, function (popup) {
   149     test.checkMenu([item], [item], []);
   150     test.done();
   151   });
   152 };
   155 // Page contexts should cause their items to be present in the menu when the
   156 // menu is not invoked on an active element.
   157 exports.testPageContextMatch = function (assert, done) {
   158   let test = new TestHelper(assert, done);
   159   let loader = test.newLoader();
   161   let items = [
   162     new loader.cm.Item({
   163       label: "item 0"
   164     }),
   165     new loader.cm.Item({
   166       label: "item 1",
   167       context: undefined
   168     }),
   169     new loader.cm.Item({
   170       label: "item 2",
   171       context: loader.cm.PageContext()
   172     }),
   173     new loader.cm.Item({
   174       label: "item 3",
   175       context: [loader.cm.PageContext()]
   176     })
   177   ];
   179   test.showMenu(null, function (popup) {
   180     test.checkMenu(items, [], []);
   181     test.done();
   182   });
   183 };
   186 // Page contexts should cause their items to be absent from the menu when the
   187 // menu is invoked on an active element.
   188 exports.testPageContextNoMatch = function (assert, done) {
   189   let test = new TestHelper(assert, done);
   190   let loader = test.newLoader();
   192   let items = [
   193     new loader.cm.Item({
   194       label: "item 0"
   195     }),
   196     new loader.cm.Item({
   197       label: "item 1",
   198       context: undefined
   199     }),
   200     new loader.cm.Item({
   201       label: "item 2",
   202       context: loader.cm.PageContext()
   203     }),
   204     new loader.cm.Item({
   205       label: "item 3",
   206       context: [loader.cm.PageContext()]
   207     })
   208   ];
   210   test.withTestDoc(function (window, doc) {
   211     test.showMenu(doc.getElementById("image"), function (popup) {
   212       test.checkMenu(items, items, []);
   213       test.done();
   214     });
   215   });
   216 };
   219 // Selection contexts should cause items to appear when a selection exists.
   220 exports.testSelectionContextMatch = function (assert, done) {
   221   let test = new TestHelper(assert, done);
   222   let loader = test.newLoader();
   224   let item = loader.cm.Item({
   225     label: "item",
   226     context: loader.cm.SelectionContext()
   227   });
   229   test.withTestDoc(function (window, doc) {
   230     window.getSelection().selectAllChildren(doc.body);
   231     test.showMenu(null, function (popup) {
   232       test.checkMenu([item], [], []);
   233       test.done();
   234     });
   235   });
   236 };
   239 // Selection contexts should cause items to appear when a selection exists in
   240 // a text field.
   241 exports.testSelectionContextMatchInTextField = function (assert, done) {
   242   let test = new TestHelper(assert, done);
   243   let loader = test.newLoader();
   245   let item = loader.cm.Item({
   246     label: "item",
   247     context: loader.cm.SelectionContext()
   248   });
   250   test.withTestDoc(function (window, doc) {
   251     let textfield = doc.getElementById("textfield");
   252     textfield.setSelectionRange(0, textfield.value.length);
   253     test.showMenu(textfield, function (popup) {
   254       test.checkMenu([item], [], []);
   255       test.done();
   256     });
   257   });
   258 };
   261 // Selection contexts should not cause items to appear when a selection does
   262 // not exist in a text field.
   263 exports.testSelectionContextNoMatchInTextField = function (assert, done) {
   264   let test = new TestHelper(assert, done);
   265   let loader = test.newLoader();
   267   let item = loader.cm.Item({
   268     label: "item",
   269     context: loader.cm.SelectionContext()
   270   });
   272   test.withTestDoc(function (window, doc) {
   273     let textfield = doc.getElementById("textfield");
   274     textfield.setSelectionRange(0, 0);
   275     test.showMenu(textfield, function (popup) {
   276       test.checkMenu([item], [item], []);
   277       test.done();
   278     });
   279   });
   280 };
   283 // Selection contexts should not cause items to appear when a selection does
   284 // not exist.
   285 exports.testSelectionContextNoMatch = function (assert, done) {
   286   let test = new TestHelper(assert, done);
   287   let loader = test.newLoader();
   289   let item = loader.cm.Item({
   290     label: "item",
   291     context: loader.cm.SelectionContext()
   292   });
   294   test.showMenu(null, function (popup) {
   295     test.checkMenu([item], [item], []);
   296     test.done();
   297   });
   298 };
   301 // Selection contexts should cause items to appear when a selection exists even
   302 // for newly opened pages
   303 exports.testSelectionContextInNewTab = function (assert, done) {
   304   let test = new TestHelper(assert, done);
   305   let loader = test.newLoader();
   307   let item = loader.cm.Item({
   308     label: "item",
   309     context: loader.cm.SelectionContext()
   310   });
   312   test.withTestDoc(function (window, doc) {
   313     let link = doc.getElementById("targetlink");
   314     link.click();
   316     test.delayedEventListener(this.tabBrowser, "load", function () {
   317       let browser = test.tabBrowser.selectedBrowser;
   318       let window = browser.contentWindow;
   319       let doc = browser.contentDocument;
   320       window.getSelection().selectAllChildren(doc.body);
   322       test.showMenu(null, function (popup) {
   323         test.checkMenu([item], [], []);
   324         popup.hidePopup();
   326         test.tabBrowser.removeTab(test.tabBrowser.selectedTab);
   327         test.tabBrowser.selectedTab = test.tab;
   329         test.showMenu(null, function (popup) {
   330           test.checkMenu([item], [item], []);
   331           test.done();
   332         });
   333       });
   334     }, true);
   335   });
   336 };
   339 // Selection contexts should work when right clicking a form button
   340 exports.testSelectionContextButtonMatch = function (assert, done) {
   341   let test = new TestHelper(assert, done);
   342   let loader = test.newLoader();
   344   let item = loader.cm.Item({
   345     label: "item",
   346     context: loader.cm.SelectionContext()
   347   });
   349   test.withTestDoc(function (window, doc) {
   350     window.getSelection().selectAllChildren(doc.body);
   351     let button = doc.getElementById("button");
   352     test.showMenu(button, function (popup) {
   353       test.checkMenu([item], [], []);
   354       test.done();
   355     });
   356   });
   357 };
   360 //Selection contexts should work when right clicking a form button
   361 exports.testSelectionContextButtonNoMatch = function (assert, done) {
   362   let test = new TestHelper(assert, done);
   363   let loader = test.newLoader();
   365   let item = loader.cm.Item({
   366     label: "item",
   367     context: loader.cm.SelectionContext()
   368   });
   370   test.withTestDoc(function (window, doc) {
   371     let button = doc.getElementById("button");
   372     test.showMenu(button, function (popup) {
   373       test.checkMenu([item], [item], []);
   374       test.done();
   375     });
   376   });
   377 };
   380 // URL contexts should cause items to appear on pages that match.
   381 exports.testURLContextMatch = function (assert, done) {
   382   let test = new TestHelper(assert, done);
   383   let loader = test.newLoader();
   385   let items = [
   386     loader.cm.Item({
   387       label: "item 0",
   388       context: loader.cm.URLContext(TEST_DOC_URL)
   389     }),
   390     loader.cm.Item({
   391       label: "item 1",
   392       context: loader.cm.URLContext([TEST_DOC_URL, "*.bogus.com"])
   393     }),
   394     loader.cm.Item({
   395       label: "item 2",
   396       context: loader.cm.URLContext([new RegExp(".*\\.html")])
   397     })
   398   ];
   400   test.withTestDoc(function (window, doc) {
   401     test.showMenu(null, function (popup) {
   402       test.checkMenu(items, [], []);
   403       test.done();
   404     });
   405   });
   406 };
   409 // URL contexts should not cause items to appear on pages that do not match.
   410 exports.testURLContextNoMatch = function (assert, done) {
   411   let test = new TestHelper(assert, done);
   412   let loader = test.newLoader();
   414   let items = [
   415     loader.cm.Item({
   416       label: "item 0",
   417       context: loader.cm.URLContext("*.bogus.com")
   418     }),
   419     loader.cm.Item({
   420       label: "item 1",
   421       context: loader.cm.URLContext(["*.bogus.com", "*.gnarly.com"])
   422     }),
   423     loader.cm.Item({
   424       label: "item 2",
   425       context: loader.cm.URLContext([new RegExp(".*\\.js")])
   426     })
   427   ];
   429   test.withTestDoc(function (window, doc) {
   430     test.showMenu(null, function (popup) {
   431       test.checkMenu(items, items, []);
   432       test.done();
   433     });
   434   });
   435 };
   438 // Removing a non-matching URL context after its item is created and the page is
   439 // loaded should cause the item's content script to be evaluated when the
   440 // context menu is next opened.
   441 exports.testURLContextRemove = function (assert, done) {
   442   let test = new TestHelper(assert, done);
   443   let loader = test.newLoader();
   445   let shouldBeEvaled = false;
   446   let context = loader.cm.URLContext("*.bogus.com");
   447   let item = loader.cm.Item({
   448     label: "item",
   449     context: context,
   450     contentScript: 'self.postMessage("ok"); self.on("context", function () true);',
   451     onMessage: function (msg) {
   452       assert.ok(shouldBeEvaled,
   453                   "content script should be evaluated when expected");
   454       assert.equal(msg, "ok", "Should have received the right message");
   455       shouldBeEvaled = false;
   456     }
   457   });
   459   test.withTestDoc(function (window, doc) {
   460     test.showMenu(null, function (popup) {
   461       test.checkMenu([item], [item], []);
   463       item.context.remove(context);
   465       shouldBeEvaled = true;
   467       test.hideMenu(function () {
   468         test.showMenu(null, function (popup) {
   469           test.checkMenu([item], [], []);
   471           assert.ok(!shouldBeEvaled,
   472                       "content script should have been evaluated");
   474           test.hideMenu(function () {
   475             // Shouldn't get evaluated again
   476             test.showMenu(null, function (popup) {
   477               test.checkMenu([item], [], []);
   478               test.done();
   479             });
   480           });
   481         });
   482       });
   483     });
   484   });
   485 };
   487 // Loading a new page in the same tab should correctly start a new worker for
   488 // any content scripts
   489 exports.testPageReload = function (assert, done) {
   490   let test = new TestHelper(assert, done);
   491   let loader = test.newLoader();
   493   let item = loader.cm.Item({
   494     label: "Item",
   495     contentScript: "var doc = document; self.on('context', function(node) doc.body.getAttribute('showItem') == 'true');"
   496   });
   498   test.withTestDoc(function (window, doc) {
   499     // Set a flag on the document that the item uses
   500     doc.body.setAttribute("showItem", "true");
   502     test.showMenu(null, function (popup) {
   503       // With the attribute true the item should be visible in the menu
   504       test.checkMenu([item], [], []);
   505       test.hideMenu(function() {
   506         let browser = this.tabBrowser.getBrowserForTab(this.tab)
   507         test.delayedEventListener(browser, "load", function() {
   508           test.delayedEventListener(browser, "load", function() {
   509             window = browser.contentWindow;
   510             doc = window.document;
   512             // Set a flag on the document that the item uses
   513             doc.body.setAttribute("showItem", "false");
   515             test.showMenu(null, function (popup) {
   516               // In the new document with the attribute false the item should be
   517               // hidden, but if the contentScript hasn't been reloaded it will
   518               // still see the old value
   519               test.checkMenu([item], [item], []);
   521               test.done();
   522             });
   523           }, true);
   524           browser.loadURI(TEST_DOC_URL, null, null);
   525         }, true);
   526         // Required to make sure we load a new page in history rather than
   527         // just reloading the current page which would unload it
   528         browser.loadURI("about:blank", null, null);
   529       });
   530     });
   531   });
   532 };
   534 // Closing a page after it's been used with a worker should cause the worker
   535 // to be destroyed
   536 /*exports.testWorkerDestroy = function (assert, done) {
   537   let test = new TestHelper(assert, done);
   538   let loader = test.newLoader();
   540   let loadExpected = false;
   542   let item = loader.cm.Item({
   543     label: "item",
   544     contentScript: 'self.postMessage("loaded"); self.on("detach", function () { console.log("saw detach"); self.postMessage("detach") });',
   545     onMessage: function (msg) {
   546       switch (msg) {
   547       case "loaded":
   548         assert.ok(loadExpected, "Should have seen the load event at the right time");
   549         loadExpected = false;
   550         break;
   551       case "detach":
   552         test.done();
   553         break;
   554       }
   555     }
   556   });
   558   test.withTestDoc(function (window, doc) {
   559     loadExpected = true;
   560     test.showMenu(null, function (popup) {
   561       assert.ok(!loadExpected, "Should have seen a message");
   563       test.checkMenu([item], [], []);
   565       test.closeTab();
   566     });
   567   });
   568 };*/
   571 // Content contexts that return true should cause their items to be present
   572 // in the menu.
   573 exports.testContentContextMatch = function (assert, done) {
   574   let test = new TestHelper(assert, done);
   575   let loader = test.newLoader();
   577   let item = new loader.cm.Item({
   578     label: "item",
   579     contentScript: 'self.on("context", function () true);'
   580   });
   582   test.showMenu(null, function (popup) {
   583     test.checkMenu([item], [], []);
   584     test.done();
   585   });
   586 };
   589 // Content contexts that return false should cause their items to be absent
   590 // from the menu.
   591 exports.testContentContextNoMatch = function (assert, done) {
   592   let test = new TestHelper(assert, done);
   593   let loader = test.newLoader();
   595   let item = new loader.cm.Item({
   596     label: "item",
   597     contentScript: 'self.on("context", function () false);'
   598   });
   600   test.showMenu(null, function (popup) {
   601     test.checkMenu([item], [item], []);
   602     test.done();
   603   });
   604 };
   607 // Content contexts that return undefined should cause their items to be absent
   608 // from the menu.
   609 exports.testContentContextUndefined = function (assert, done) {
   610   let test = new TestHelper(assert, done);
   611   let loader = test.newLoader();
   613   let item = new loader.cm.Item({
   614     label: "item",
   615     contentScript: 'self.on("context", function () {});'
   616   });
   618   test.showMenu(null, function (popup) {
   619     test.checkMenu([item], [item], []);
   620     test.done();
   621   });
   622 };
   625 // Content contexts that return an empty string should cause their items to be
   626 // absent from the menu and shouldn't wipe the label
   627 exports.testContentContextEmptyString = function (assert, done) {
   628   let test = new TestHelper(assert, done);
   629   let loader = test.newLoader();
   631   let item = new loader.cm.Item({
   632     label: "item",
   633     contentScript: 'self.on("context", function () "");'
   634   });
   636   test.showMenu(null, function (popup) {
   637     test.checkMenu([item], [item], []);
   638     assert.equal(item.label, "item", "Label should still be correct");
   639     test.done();
   640   });
   641 };
   644 // If any content contexts returns true then their items should be present in
   645 // the menu.
   646 exports.testMultipleContentContextMatch1 = function (assert, done) {
   647   let test = new TestHelper(assert, done);
   648   let loader = test.newLoader();
   650   let item = new loader.cm.Item({
   651     label: "item",
   652     contentScript: 'self.on("context", function () true); ' +
   653                    'self.on("context", function () false);',
   654     onMessage: function() {
   655       test.fail("Should not have called the second context listener");
   656     }
   657   });
   659   test.showMenu(null, function (popup) {
   660     test.checkMenu([item], [], []);
   661     test.done();
   662   });
   663 };
   666 // If any content contexts returns true then their items should be present in
   667 // the menu.
   668 exports.testMultipleContentContextMatch2 = function (assert, done) {
   669   let test = new TestHelper(assert, done);
   670   let loader = test.newLoader();
   672   let item = new loader.cm.Item({
   673     label: "item",
   674     contentScript: 'self.on("context", function () false); ' +
   675                    'self.on("context", function () true);'
   676   });
   678   test.showMenu(null, function (popup) {
   679     test.checkMenu([item], [], []);
   680     test.done();
   681   });
   682 };
   685 // If any content contexts returns a string then their items should be present
   686 // in the menu.
   687 exports.testMultipleContentContextString1 = function (assert, done) {
   688   let test = new TestHelper(assert, done);
   689   let loader = test.newLoader();
   691   let item = new loader.cm.Item({
   692     label: "item",
   693     contentScript: 'self.on("context", function () "new label"); ' +
   694                    'self.on("context", function () false);'
   695   });
   697   test.showMenu(null, function (popup) {
   698     test.checkMenu([item], [], []);
   699     assert.equal(item.label, "new label", "Label should have changed");
   700     test.done();
   701   });
   702 };
   705 // If any content contexts returns a string then their items should be present
   706 // in the menu.
   707 exports.testMultipleContentContextString2 = function (assert, done) {
   708   let test = new TestHelper(assert, done);
   709   let loader = test.newLoader();
   711   let item = new loader.cm.Item({
   712     label: "item",
   713     contentScript: 'self.on("context", function () false); ' +
   714                    'self.on("context", function () "new label");'
   715   });
   717   test.showMenu(null, function (popup) {
   718     test.checkMenu([item], [], []);
   719     assert.equal(item.label, "new label", "Label should have changed");
   720     test.done();
   721   });
   722 };
   725 // If many content contexts returns a string then the first should take effect
   726 exports.testMultipleContentContextString3 = function (assert, done) {
   727   let test = new TestHelper(assert, done);
   728   let loader = test.newLoader();
   730   let item = new loader.cm.Item({
   731     label: "item",
   732     contentScript: 'self.on("context", function () "new label 1"); ' +
   733                    'self.on("context", function () "new label 2");'
   734   });
   736   test.showMenu(null, function (popup) {
   737     test.checkMenu([item], [], []);
   738     assert.equal(item.label, "new label 1", "Label should have changed");
   739     test.done();
   740   });
   741 };
   744 // Content contexts that return true should cause their items to be present
   745 // in the menu when context clicking an active element.
   746 exports.testContentContextMatchActiveElement = function (assert, done) {
   747   let test = new TestHelper(assert, done);
   748   let loader = test.newLoader();
   750   let items = [
   751     new loader.cm.Item({
   752       label: "item 1",
   753       contentScript: 'self.on("context", function () true);'
   754     }),
   755     new loader.cm.Item({
   756       label: "item 2",
   757       context: undefined,
   758       contentScript: 'self.on("context", function () true);'
   759     }),
   760     // These items will always be hidden by the declarative usage of PageContext
   761     new loader.cm.Item({
   762       label: "item 3",
   763       context: loader.cm.PageContext(),
   764       contentScript: 'self.on("context", function () true);'
   765     }),
   766     new loader.cm.Item({
   767       label: "item 4",
   768       context: [loader.cm.PageContext()],
   769       contentScript: 'self.on("context", function () true);'
   770     })
   771   ];
   773   test.withTestDoc(function (window, doc) {
   774     test.showMenu(doc.getElementById("image"), function (popup) {
   775       test.checkMenu(items, [items[2], items[3]], []);
   776       test.done();
   777     });
   778   });
   779 };
   782 // Content contexts that return false should cause their items to be absent
   783 // from the menu when context clicking an active element.
   784 exports.testContentContextNoMatchActiveElement = function (assert, done) {
   785   let test = new TestHelper(assert, done);
   786   let loader = test.newLoader();
   788   let items = [
   789     new loader.cm.Item({
   790       label: "item 1",
   791       contentScript: 'self.on("context", function () false);'
   792     }),
   793     new loader.cm.Item({
   794       label: "item 2",
   795       context: undefined,
   796       contentScript: 'self.on("context", function () false);'
   797     }),
   798     // These items will always be hidden by the declarative usage of PageContext
   799     new loader.cm.Item({
   800       label: "item 3",
   801       context: loader.cm.PageContext(),
   802       contentScript: 'self.on("context", function () false);'
   803     }),
   804     new loader.cm.Item({
   805       label: "item 4",
   806       context: [loader.cm.PageContext()],
   807       contentScript: 'self.on("context", function () false);'
   808     })
   809   ];
   811   test.withTestDoc(function (window, doc) {
   812     test.showMenu(doc.getElementById("image"), function (popup) {
   813       test.checkMenu(items, items, []);
   814       test.done();
   815     });
   816   });
   817 };
   820 // Content contexts that return undefined should cause their items to be absent
   821 // from the menu when context clicking an active element.
   822 exports.testContentContextNoMatchActiveElement = function (assert, done) {
   823   let test = new TestHelper(assert, done);
   824   let loader = test.newLoader();
   826   let items = [
   827     new loader.cm.Item({
   828       label: "item 1",
   829       contentScript: 'self.on("context", function () {});'
   830     }),
   831     new loader.cm.Item({
   832       label: "item 2",
   833       context: undefined,
   834       contentScript: 'self.on("context", function () {});'
   835     }),
   836     // These items will always be hidden by the declarative usage of PageContext
   837     new loader.cm.Item({
   838       label: "item 3",
   839       context: loader.cm.PageContext(),
   840       contentScript: 'self.on("context", function () {});'
   841     }),
   842     new loader.cm.Item({
   843       label: "item 4",
   844       context: [loader.cm.PageContext()],
   845       contentScript: 'self.on("context", function () {});'
   846     })
   847   ];
   849   test.withTestDoc(function (window, doc) {
   850     test.showMenu(doc.getElementById("image"), function (popup) {
   851       test.checkMenu(items, items, []);
   852       test.done();
   853     });
   854   });
   855 };
   858 // Content contexts that return a string should cause their items to be present
   859 // in the menu and the items' labels to be updated.
   860 exports.testContentContextMatchString = function (assert, done) {
   861   let test = new TestHelper(assert, done);
   862   let loader = test.newLoader();
   864   let item = new loader.cm.Item({
   865     label: "first label",
   866     contentScript: 'self.on("context", function () "second label");'
   867   });
   869   test.showMenu(null, function (popup) {
   870     test.checkMenu([item], [], []);
   871     assert.equal(item.label, "second label",
   872                      "item's label should be updated");
   873     test.done();
   874   });
   875 };
   878 // Ensure that contentScriptFile is working correctly
   879 exports.testContentScriptFile = function (assert, done) {
   880   let test = new TestHelper(assert, done);
   881   let loader = test.newLoader();
   883   // Reject remote files
   884   assert.throws(function() {
   885       new loader.cm.Item({
   886         label: "item",
   887         contentScriptFile: "http://mozilla.com/context-menu.js"
   888       });
   889     },
   890     new RegExp("The 'contentScriptFile' option must be a local file URL " +
   891     "or an array of local file URLs."),
   892     "Item throws when contentScriptFile is a remote URL");
   894   // But accept files from data folder
   895   let item = new loader.cm.Item({
   896     label: "item",
   897     contentScriptFile: data.url("test-context-menu.js")
   898   });
   900   test.showMenu(null, function (popup) {
   901     test.checkMenu([item], [], []);
   902     test.done();
   903   });
   904 };
   907 // The args passed to context listeners should be correct.
   908 exports.testContentContextArgs = function (assert, done) {
   909   let test = new TestHelper(assert, done);
   910   let loader = test.newLoader();
   911   let callbacks = 0;
   913   let item = new loader.cm.Item({
   914     label: "item",
   915     contentScript: 'self.on("context", function (node) {' +
   916                    '  self.postMessage(node.tagName);' +
   917                    '  return false;' +
   918                    '});',
   919     onMessage: function (tagName) {
   920       assert.equal(tagName, "HTML", "node should be an HTML element");
   921       if (++callbacks == 2) test.done();
   922     }
   923   });
   925   test.showMenu(null, function () {
   926     if (++callbacks == 2) test.done();
   927   });
   928 };
   930 // Multiple contexts imply intersection, not union, and content context
   931 // listeners should not be called if all declarative contexts are not current.
   932 exports.testMultipleContexts = function (assert, done) {
   933   let test = new TestHelper(assert, done);
   934   let loader = test.newLoader();
   936   let item = new loader.cm.Item({
   937     label: "item",
   938     context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()],
   939     contentScript: 'self.on("context", function () self.postMessage());',
   940     onMessage: function () {
   941       test.fail("Context listener should not be called");
   942     }
   943   });
   945   test.withTestDoc(function (window, doc) {
   946     test.showMenu(doc.getElementById("span-link"), function (popup) {
   947       test.checkMenu([item], [item], []);
   948       test.done();
   949     });
   950   });
   951 };
   953 // Once a context is removed, it should no longer cause its item to appear.
   954 exports.testRemoveContext = function (assert, done) {
   955   let test = new TestHelper(assert, done);
   956   let loader = test.newLoader();
   958   let ctxt = loader.cm.SelectorContext("img");
   959   let item = new loader.cm.Item({
   960     label: "item",
   961     context: ctxt
   962   });
   964   test.withTestDoc(function (window, doc) {
   965     test.showMenu(doc.getElementById("image"), function (popup) {
   967       // The item should be present at first.
   968       test.checkMenu([item], [], []);
   969       popup.hidePopup();
   971       // Remove the img context and check again.
   972       item.context.remove(ctxt);
   973       test.showMenu(doc.getElementById("image"), function (popup) {
   974         test.checkMenu([item], [item], []);
   975         test.done();
   976       });
   977     });
   978   });
   979 };
   982 // Lots of items should overflow into the overflow submenu.
   983 exports.testOverflow = function (assert, done) {
   984   let test = new TestHelper(assert, done);
   985   let loader = test.newLoader();
   987   let items = [];
   988   for (let i = 0; i < OVERFLOW_THRESH_DEFAULT + 1; i++) {
   989     let item = new loader.cm.Item({ label: "item " + i });
   990     items.push(item);
   991   }
   993   test.showMenu(null, function (popup) {
   994     test.checkMenu(items, [], []);
   995     test.done();
   996   });
   997 };
  1000 // Module unload should cause all items to be removed.
  1001 exports.testUnload = function (assert, done) {
  1002   let test = new TestHelper(assert, done);
  1003   let loader = test.newLoader();
  1005   let item = new loader.cm.Item({ label: "item" });
  1007   test.showMenu(null, function (popup) {
  1009     // The menu should contain the item.
  1010     test.checkMenu([item], [], []);
  1011     popup.hidePopup();
  1013     // Unload the module.
  1014     loader.unload();
  1015     test.showMenu(null, function (popup) {
  1017       // The item should be removed from the menu.
  1018       test.checkMenu([item], [], [item]);
  1019       test.done();
  1020     });
  1021   });
  1022 };
  1025 // Using multiple module instances to add items without causing overflow should
  1026 // work OK.  Assumes OVERFLOW_THRESH_DEFAULT >= 2.
  1027 exports.testMultipleModulesAdd = function (assert, done) {
  1028   let test = new TestHelper(assert, done);
  1029   let loader0 = test.newLoader();
  1030   let loader1 = test.newLoader();
  1032   // Use each module to add an item, then unload each module in turn.
  1033   let item0 = new loader0.cm.Item({ label: "item 0" });
  1034   let item1 = new loader1.cm.Item({ label: "item 1" });
  1036   test.showMenu(null, function (popup) {
  1038     // The menu should contain both items.
  1039     test.checkMenu([item0, item1], [], []);
  1040     popup.hidePopup();
  1042     // Unload the first module.
  1043     loader0.unload();
  1044     test.showMenu(null, function (popup) {
  1046       // The first item should be removed from the menu.
  1047       test.checkMenu([item0, item1], [], [item0]);
  1048       popup.hidePopup();
  1050       // Unload the second module.
  1051       loader1.unload();
  1052       test.showMenu(null, function (popup) {
  1054         // Both items should be removed from the menu.
  1055         test.checkMenu([item0, item1], [], [item0, item1]);
  1056         test.done();
  1057       });
  1058     });
  1059   });
  1060 };
  1063 // Using multiple module instances to add items causing overflow should work OK.
  1064 exports.testMultipleModulesAddOverflow = function (assert, done) {
  1065   let test = new TestHelper(assert, done);
  1066   let loader0 = test.newLoader();
  1067   let loader1 = test.newLoader();
  1069   // Use module 0 to add OVERFLOW_THRESH_DEFAULT items.
  1070   let items0 = [];
  1071   for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) {
  1072     let item = new loader0.cm.Item({ label: "item 0 " + i });
  1073     items0.push(item);
  1076   // Use module 1 to add one item.
  1077   let item1 = new loader1.cm.Item({ label: "item 1" });
  1079   let allItems = items0.concat(item1);
  1081   test.showMenu(null, function (popup) {
  1083     // The menu should contain all items in overflow.
  1084     test.checkMenu(allItems, [], []);
  1085     popup.hidePopup();
  1087     // Unload the first module.
  1088     loader0.unload();
  1089     test.showMenu(null, function (popup) {
  1091       // The first items should be removed from the menu, which should not
  1092       // overflow.
  1093       test.checkMenu(allItems, [], items0);
  1094       popup.hidePopup();
  1096       // Unload the second module.
  1097       loader1.unload();
  1098       test.showMenu(null, function (popup) {
  1100         // All items should be removed from the menu.
  1101         test.checkMenu(allItems, [], allItems);
  1102         test.done();
  1103       });
  1104     });
  1105   });
  1106 };
  1109 // Using multiple module instances to modify the menu without causing overflow
  1110 // should work OK.  This test creates two loaders and:
  1111 // loader0 create item -> loader1 create item -> loader0.unload ->
  1112 // loader1.unload
  1113 exports.testMultipleModulesDiffContexts1 = function (assert, done) {
  1114   let test = new TestHelper(assert, done);
  1115   let loader0 = test.newLoader();
  1116   let loader1 = test.newLoader();
  1118   let item0 = new loader0.cm.Item({
  1119     label: "item 0",
  1120     context: loader0.cm.SelectorContext("img")
  1121   });
  1123   let item1 = new loader1.cm.Item({ label: "item 1" });
  1125   test.showMenu(null, function (popup) {
  1127     // The menu should contain item1.
  1128     test.checkMenu([item0, item1], [item0], []);
  1129     popup.hidePopup();
  1131     // Unload module 0.
  1132     loader0.unload();
  1133     test.showMenu(null, function (popup) {
  1135       // item0 should be removed from the menu.
  1136       test.checkMenu([item0, item1], [], [item0]);
  1137       popup.hidePopup();
  1139       // Unload module 1.
  1140       loader1.unload();
  1141       test.showMenu(null, function (popup) {
  1143         // Both items should be removed from the menu.
  1144         test.checkMenu([item0, item1], [], [item0, item1]);
  1145         test.done();
  1146       });
  1147     });
  1148   });
  1149 };
  1152 // Using multiple module instances to modify the menu without causing overflow
  1153 // should work OK.  This test creates two loaders and:
  1154 // loader1 create item -> loader0 create item -> loader0.unload ->
  1155 // loader1.unload
  1156 exports.testMultipleModulesDiffContexts2 = function (assert, done) {
  1157   let test = new TestHelper(assert, done);
  1158   let loader0 = test.newLoader();
  1159   let loader1 = test.newLoader();
  1161   let item1 = new loader1.cm.Item({ label: "item 1" });
  1163   let item0 = new loader0.cm.Item({
  1164     label: "item 0",
  1165     context: loader0.cm.SelectorContext("img")
  1166   });
  1168   test.showMenu(null, function (popup) {
  1170     // The menu should contain item1.
  1171     test.checkMenu([item0, item1], [item0], []);
  1172     popup.hidePopup();
  1174     // Unload module 0.
  1175     loader0.unload();
  1176     test.showMenu(null, function (popup) {
  1178       // item0 should be removed from the menu.
  1179       test.checkMenu([item0, item1], [], [item0]);
  1180       popup.hidePopup();
  1182       // Unload module 1.
  1183       loader1.unload();
  1184       test.showMenu(null, function (popup) {
  1186         // Both items should be removed from the menu.
  1187         test.checkMenu([item0, item1], [], [item0, item1]);
  1188         test.done();
  1189       });
  1190     });
  1191   });
  1192 };
  1195 // Using multiple module instances to modify the menu without causing overflow
  1196 // should work OK.  This test creates two loaders and:
  1197 // loader0 create item -> loader1 create item -> loader1.unload ->
  1198 // loader0.unload
  1199 exports.testMultipleModulesDiffContexts3 = function (assert, done) {
  1200   let test = new TestHelper(assert, done);
  1201   let loader0 = test.newLoader();
  1202   let loader1 = test.newLoader();
  1204   let item0 = new loader0.cm.Item({
  1205     label: "item 0",
  1206     context: loader0.cm.SelectorContext("img")
  1207   });
  1209   let item1 = new loader1.cm.Item({ label: "item 1" });
  1211   test.showMenu(null, function (popup) {
  1213     // The menu should contain item1.
  1214     test.checkMenu([item0, item1], [item0], []);
  1215     popup.hidePopup();
  1217     // Unload module 1.
  1218     loader1.unload();
  1219     test.showMenu(null, function (popup) {
  1221       // item1 should be removed from the menu.
  1222       test.checkMenu([item0, item1], [item0], [item1]);
  1223       popup.hidePopup();
  1225       // Unload module 0.
  1226       loader0.unload();
  1227       test.showMenu(null, function (popup) {
  1229         // Both items should be removed from the menu.
  1230         test.checkMenu([item0, item1], [], [item0, item1]);
  1231         test.done();
  1232       });
  1233     });
  1234   });
  1235 };
  1238 // Using multiple module instances to modify the menu without causing overflow
  1239 // should work OK.  This test creates two loaders and:
  1240 // loader1 create item -> loader0 create item -> loader1.unload ->
  1241 // loader0.unload
  1242 exports.testMultipleModulesDiffContexts4 = function (assert, done) {
  1243   let test = new TestHelper(assert, done);
  1244   let loader0 = test.newLoader();
  1245   let loader1 = test.newLoader();
  1247   let item1 = new loader1.cm.Item({ label: "item 1" });
  1249   let item0 = new loader0.cm.Item({
  1250     label: "item 0",
  1251     context: loader0.cm.SelectorContext("img")
  1252   });
  1254   test.showMenu(null, function (popup) {
  1256     // The menu should contain item1.
  1257     test.checkMenu([item0, item1], [item0], []);
  1258     popup.hidePopup();
  1260     // Unload module 1.
  1261     loader1.unload();
  1262     test.showMenu(null, function (popup) {
  1264       // item1 should be removed from the menu.
  1265       test.checkMenu([item0, item1], [item0], [item1]);
  1266       popup.hidePopup();
  1268       // Unload module 0.
  1269       loader0.unload();
  1270       test.showMenu(null, function (popup) {
  1272         // Both items should be removed from the menu.
  1273         test.checkMenu([item0, item1], [], [item0, item1]);
  1274         test.done();
  1275       });
  1276     });
  1277   });
  1278 };
  1281 // Test interactions between a loaded module, unloading another module, and the
  1282 // menu separator and overflow submenu.
  1283 exports.testMultipleModulesAddRemove = function (assert, done) {
  1284   let test = new TestHelper(assert, done);
  1285   let loader0 = test.newLoader();
  1286   let loader1 = test.newLoader();
  1288   let item = new loader0.cm.Item({ label: "item" });
  1290   test.showMenu(null, function (popup) {
  1292     // The menu should contain the item.
  1293     test.checkMenu([item], [], []);
  1294     popup.hidePopup();
  1296     // Remove the item.
  1297     item.destroy();
  1298     test.showMenu(null, function (popup) {
  1300       // The item should be removed from the menu.
  1301       test.checkMenu([item], [], [item]);
  1302       popup.hidePopup();
  1304       // Unload module 1.
  1305       loader1.unload();
  1306       test.showMenu(null, function (popup) {
  1308         // There shouldn't be any errors involving the menu separator or
  1309         // overflow submenu.
  1310         test.checkMenu([item], [], [item]);
  1311         test.done();
  1312       });
  1313     });
  1314   });
  1315 };
  1318 // Checks that the order of menu items is correct when adding/removing across
  1319 // multiple modules. All items from a single module should remain in a group
  1320 exports.testMultipleModulesOrder = function (assert, done) {
  1321   let test = new TestHelper(assert, done);
  1322   let loader0 = test.newLoader();
  1323   let loader1 = test.newLoader();
  1325   // Use each module to add an item, then unload each module in turn.
  1326   let item0 = new loader0.cm.Item({ label: "item 0" });
  1327   let item1 = new loader1.cm.Item({ label: "item 1" });
  1329   test.showMenu(null, function (popup) {
  1331     // The menu should contain both items.
  1332     test.checkMenu([item0, item1], [], []);
  1333     popup.hidePopup();
  1335     let item2 = new loader0.cm.Item({ label: "item 2" });
  1337     test.showMenu(null, function (popup) {
  1339       // The new item should be grouped with the same items from loader0.
  1340       test.checkMenu([item0, item2, item1], [], []);
  1341       popup.hidePopup();
  1343       let item3 = new loader1.cm.Item({ label: "item 3" });
  1345       test.showMenu(null, function (popup) {
  1347         // Same again
  1348         test.checkMenu([item0, item2, item1, item3], [], []);
  1349         test.done();
  1350       });
  1351     });
  1352   });
  1353 };
  1356 // Checks that the order of menu items is correct when adding/removing across
  1357 // multiple modules when overflowing. All items from a single module should
  1358 // remain in a group
  1359 exports.testMultipleModulesOrderOverflow = function (assert, done) {
  1360   let test = new TestHelper(assert, done);
  1361   let loader0 = test.newLoader();
  1362   let loader1 = test.newLoader();
  1364   let prefs = loader0.loader.require("sdk/preferences/service");
  1365   prefs.set(OVERFLOW_THRESH_PREF, 0);
  1367   // Use each module to add an item, then unload each module in turn.
  1368   let item0 = new loader0.cm.Item({ label: "item 0" });
  1369   let item1 = new loader1.cm.Item({ label: "item 1" });
  1371   test.showMenu(null, function (popup) {
  1373     // The menu should contain both items.
  1374     test.checkMenu([item0, item1], [], []);
  1375     popup.hidePopup();
  1377     let item2 = new loader0.cm.Item({ label: "item 2" });
  1379     test.showMenu(null, function (popup) {
  1381       // The new item should be grouped with the same items from loader0.
  1382       test.checkMenu([item0, item2, item1], [], []);
  1383       popup.hidePopup();
  1385       let item3 = new loader1.cm.Item({ label: "item 3" });
  1387       test.showMenu(null, function (popup) {
  1389         // Same again
  1390         test.checkMenu([item0, item2, item1, item3], [], []);
  1391         test.done();
  1392       });
  1393     });
  1394   });
  1395 };
  1398 // Checks that if a module's items are all hidden then the overflow menu doesn't
  1399 // get hidden
  1400 exports.testMultipleModulesOverflowHidden = function (assert, done) {
  1401   let test = new TestHelper(assert, done);
  1402   let loader0 = test.newLoader();
  1403   let loader1 = test.newLoader();
  1405   let prefs = loader0.loader.require("sdk/preferences/service");
  1406   prefs.set(OVERFLOW_THRESH_PREF, 0);
  1408   // Use each module to add an item, then unload each module in turn.
  1409   let item0 = new loader0.cm.Item({ label: "item 0" });
  1410   let item1 = new loader1.cm.Item({
  1411     label: "item 1",
  1412     context: loader1.cm.SelectorContext("a")
  1413   });
  1415   test.showMenu(null, function (popup) {
  1416     // One should be hidden
  1417     test.checkMenu([item0, item1], [item1], []);
  1418     test.done();
  1419   });
  1420 };
  1423 // Checks that if a module's items are all hidden then the overflow menu doesn't
  1424 // get hidden (reverse order to above)
  1425 exports.testMultipleModulesOverflowHidden2 = function (assert, done) {
  1426   let test = new TestHelper(assert, done);
  1427   let loader0 = test.newLoader();
  1428   let loader1 = test.newLoader();
  1430   let prefs = loader0.loader.require("sdk/preferences/service");
  1431   prefs.set(OVERFLOW_THRESH_PREF, 0);
  1433   // Use each module to add an item, then unload each module in turn.
  1434   let item0 = new loader0.cm.Item({
  1435     label: "item 0",
  1436     context: loader0.cm.SelectorContext("a")
  1437   });
  1438   let item1 = new loader1.cm.Item({ label: "item 1" });
  1440   test.showMenu(null, function (popup) {
  1441     // One should be hidden
  1442     test.checkMenu([item0, item1], [item0], []);
  1443     test.done();
  1444   });
  1445 };
  1448 // Checks that we don't overflow if there are more items than the overflow
  1449 // threshold but not all of them are visible
  1450 exports.testOverflowIgnoresHidden = function (assert, done) {
  1451   let test = new TestHelper(assert, done);
  1452   let loader = test.newLoader();
  1454   let prefs = loader.loader.require("sdk/preferences/service");
  1455   prefs.set(OVERFLOW_THRESH_PREF, 2);
  1457   let allItems = [
  1458     new loader.cm.Item({
  1459       label: "item 0"
  1460     }),
  1461     new loader.cm.Item({
  1462       label: "item 1"
  1463     }),
  1464     new loader.cm.Item({
  1465       label: "item 2",
  1466       context: loader.cm.SelectorContext("a")
  1467     })
  1468   ];
  1470   test.showMenu(null, function (popup) {
  1471     // One should be hidden
  1472     test.checkMenu(allItems, [allItems[2]], []);
  1473     test.done();
  1474   });
  1475 };
  1478 // Checks that we don't overflow if there are more items than the overflow
  1479 // threshold but not all of them are visible
  1480 exports.testOverflowIgnoresHiddenMultipleModules1 = function (assert, done) {
  1481   let test = new TestHelper(assert, done);
  1482   let loader0 = test.newLoader();
  1483   let loader1 = test.newLoader();
  1485   let prefs = loader0.loader.require("sdk/preferences/service");
  1486   prefs.set(OVERFLOW_THRESH_PREF, 2);
  1488   let allItems = [
  1489     new loader0.cm.Item({
  1490       label: "item 0"
  1491     }),
  1492     new loader0.cm.Item({
  1493       label: "item 1"
  1494     }),
  1495     new loader1.cm.Item({
  1496       label: "item 2",
  1497       context: loader1.cm.SelectorContext("a")
  1498     }),
  1499     new loader1.cm.Item({
  1500       label: "item 3",
  1501       context: loader1.cm.SelectorContext("a")
  1502     })
  1503   ];
  1505   test.showMenu(null, function (popup) {
  1506     // One should be hidden
  1507     test.checkMenu(allItems, [allItems[2], allItems[3]], []);
  1508     test.done();
  1509   });
  1510 };
  1513 // Checks that we don't overflow if there are more items than the overflow
  1514 // threshold but not all of them are visible
  1515 exports.testOverflowIgnoresHiddenMultipleModules2 = function (assert, done) {
  1516   let test = new TestHelper(assert, done);
  1517   let loader0 = test.newLoader();
  1518   let loader1 = test.newLoader();
  1520   let prefs = loader0.loader.require("sdk/preferences/service");
  1521   prefs.set(OVERFLOW_THRESH_PREF, 2);
  1523   let allItems = [
  1524     new loader0.cm.Item({
  1525       label: "item 0"
  1526     }),
  1527     new loader0.cm.Item({
  1528       label: "item 1",
  1529       context: loader0.cm.SelectorContext("a")
  1530     }),
  1531     new loader1.cm.Item({
  1532       label: "item 2"
  1533     }),
  1534     new loader1.cm.Item({
  1535       label: "item 3",
  1536       context: loader1.cm.SelectorContext("a")
  1537     })
  1538   ];
  1540   test.showMenu(null, function (popup) {
  1541     // One should be hidden
  1542     test.checkMenu(allItems, [allItems[1], allItems[3]], []);
  1543     test.done();
  1544   });
  1545 };
  1548 // Checks that we don't overflow if there are more items than the overflow
  1549 // threshold but not all of them are visible
  1550 exports.testOverflowIgnoresHiddenMultipleModules3 = function (assert, done) {
  1551   let test = new TestHelper(assert, done);
  1552   let loader0 = test.newLoader();
  1553   let loader1 = test.newLoader();
  1555   let prefs = loader0.loader.require("sdk/preferences/service");
  1556   prefs.set(OVERFLOW_THRESH_PREF, 2);
  1558   let allItems = [
  1559     new loader0.cm.Item({
  1560       label: "item 0",
  1561       context: loader0.cm.SelectorContext("a")
  1562     }),
  1563     new loader0.cm.Item({
  1564       label: "item 1",
  1565       context: loader0.cm.SelectorContext("a")
  1566     }),
  1567     new loader1.cm.Item({
  1568       label: "item 2"
  1569     }),
  1570     new loader1.cm.Item({
  1571       label: "item 3"
  1572     })
  1573   ];
  1575   test.showMenu(null, function (popup) {
  1576     // One should be hidden
  1577     test.checkMenu(allItems, [allItems[0], allItems[1]], []);
  1578     test.done();
  1579   });
  1580 };
  1583 // Tests that we transition between overflowing to non-overflowing to no items
  1584 // and back again
  1585 exports.testOverflowTransition = function (assert, done) {
  1586   let test = new TestHelper(assert, done);
  1587   let loader = test.newLoader();
  1589   let prefs = loader.loader.require("sdk/preferences/service");
  1590   prefs.set(OVERFLOW_THRESH_PREF, 2);
  1592   let pItems = [
  1593     new loader.cm.Item({
  1594       label: "item 0",
  1595       context: loader.cm.SelectorContext("p")
  1596     }),
  1597     new loader.cm.Item({
  1598       label: "item 1",
  1599       context: loader.cm.SelectorContext("p")
  1600     })
  1601   ];
  1603   let aItems = [
  1604     new loader.cm.Item({
  1605       label: "item 2",
  1606       context: loader.cm.SelectorContext("a")
  1607     }),
  1608     new loader.cm.Item({
  1609       label: "item 3",
  1610       context: loader.cm.SelectorContext("a")
  1611     })
  1612   ];
  1614   let allItems = pItems.concat(aItems);
  1616   test.withTestDoc(function (window, doc) {
  1617     test.showMenu(doc.getElementById("link"), function (popup) {
  1618       // The menu should contain all items and will overflow
  1619       test.checkMenu(allItems, [], []);
  1620       popup.hidePopup();
  1622       test.showMenu(doc.getElementById("text"), function (popup) {
  1623         // Only contains hald the items and will not overflow
  1624         test.checkMenu(allItems, aItems, []);
  1625         popup.hidePopup();
  1627         test.showMenu(null, function (popup) {
  1628           // None of the items will be visible
  1629           test.checkMenu(allItems, allItems, []);
  1630           popup.hidePopup();
  1632           test.showMenu(doc.getElementById("text"), function (popup) {
  1633             // Only contains hald the items and will not overflow
  1634             test.checkMenu(allItems, aItems, []);
  1635             popup.hidePopup();
  1637             test.showMenu(doc.getElementById("link"), function (popup) {
  1638               // The menu should contain all items and will overflow
  1639               test.checkMenu(allItems, [], []);
  1640               popup.hidePopup();
  1642               test.showMenu(null, function (popup) {
  1643                 // None of the items will be visible
  1644                 test.checkMenu(allItems, allItems, []);
  1645                 popup.hidePopup();
  1647                 test.showMenu(doc.getElementById("link"), function (popup) {
  1648                   // The menu should contain all items and will overflow
  1649                   test.checkMenu(allItems, [], []);
  1650                   test.done();
  1651                 });
  1652               });
  1653             });
  1654           });
  1655         });
  1656       });
  1657     });
  1658   });
  1659 };
  1662 // An item's command listener should work.
  1663 exports.testItemCommand = function (assert, done) {
  1664   let test = new TestHelper(assert, done);
  1665   let loader = test.newLoader();
  1667   let item = new loader.cm.Item({
  1668     label: "item",
  1669     data: "item data",
  1670     contentScript: 'self.on("click", function (node, data) {' +
  1671                    '  self.postMessage({' +
  1672                    '    tagName: node.tagName,' +
  1673                    '    data: data' +
  1674                    '  });' +
  1675                    '});',
  1676     onMessage: function (data) {
  1677       assert.equal(this, item, "`this` inside onMessage should be item");
  1678       assert.equal(data.tagName, "HTML", "node should be an HTML element");
  1679       assert.equal(data.data, item.data, "data should be item data");
  1680       test.done();
  1682   });
  1684   test.showMenu(null, function (popup) {
  1685     test.checkMenu([item], [], []);
  1686     let elt = test.getItemElt(popup, item);
  1688     // create a command event
  1689     let evt = elt.ownerDocument.createEvent('Event');
  1690     evt.initEvent('command', true, true);
  1691     elt.dispatchEvent(evt);
  1692   });
  1693 };
  1696 // A menu's click listener should work and receive bubbling 'command' events from
  1697 // sub-items appropriately.  This also tests menus and ensures that when a CSS
  1698 // selector context matches the clicked node's ancestor, the matching ancestor
  1699 // is passed to listeners as the clicked node.
  1700 exports.testMenuCommand = function (assert, done) {
  1701   // Create a top-level menu, submenu, and item, like this:
  1702   // topMenu -> submenu -> item
  1703   // Click the item and make sure the click bubbles.
  1704   let test = new TestHelper(assert, done);
  1705   let loader = test.newLoader();
  1707   let item = new loader.cm.Item({
  1708     label: "submenu item",
  1709     data: "submenu item data",
  1710     context: loader.cm.SelectorContext("a"),
  1711   });
  1713   let submenu = new loader.cm.Menu({
  1714     label: "submenu",
  1715     context: loader.cm.SelectorContext("a"),
  1716     items: [item]
  1717   });
  1719   let topMenu = new loader.cm.Menu({
  1720     label: "top menu",
  1721     contentScript: 'self.on("click", function (node, data) {' +
  1722                    '  self.postMessage({' +
  1723                    '    tagName: node.tagName,' +
  1724                    '    data: data' +
  1725                    '  });' +
  1726                    '});',
  1727     onMessage: function (data) {
  1728       assert.equal(this, topMenu, "`this` inside top menu should be menu");
  1729       assert.equal(data.tagName, "A", "Clicked node should be anchor");
  1730       assert.equal(data.data, item.data,
  1731                        "Clicked item data should be correct");
  1732       test.done();
  1733     },
  1734     items: [submenu],
  1735     context: loader.cm.SelectorContext("a")
  1736   });
  1738   test.withTestDoc(function (window, doc) {
  1739     test.showMenu(doc.getElementById("span-link"), function (popup) {
  1740       test.checkMenu([topMenu], [], []);
  1741       let topMenuElt = test.getItemElt(popup, topMenu);
  1742       let topMenuPopup = topMenuElt.firstChild;
  1743       let submenuElt = test.getItemElt(topMenuPopup, submenu);
  1744       let submenuPopup = submenuElt.firstChild;
  1745       let itemElt = test.getItemElt(submenuPopup, item);
  1747       // create a command event
  1748       let evt = itemElt.ownerDocument.createEvent('Event');
  1749       evt.initEvent('command', true, true);
  1750       itemElt.dispatchEvent(evt);
  1751     });
  1752   });
  1753 };
  1756 // Click listeners should work when multiple modules are loaded.
  1757 exports.testItemCommandMultipleModules = function (assert, done) {
  1758   let test = new TestHelper(assert, done);
  1759   let loader0 = test.newLoader();
  1760   let loader1 = test.newLoader();
  1762   let item0 = loader0.cm.Item({
  1763     label: "loader 0 item",
  1764     contentScript: 'self.on("click", self.postMessage);',
  1765     onMessage: function () {
  1766       test.fail("loader 0 item should not emit click event");
  1768   });
  1769   let item1 = loader1.cm.Item({
  1770     label: "loader 1 item",
  1771     contentScript: 'self.on("click", self.postMessage);',
  1772     onMessage: function () {
  1773       test.pass("loader 1 item clicked as expected");
  1774       test.done();
  1776   });
  1778   test.showMenu(null, function (popup) {
  1779     test.checkMenu([item0, item1], [], []);
  1780     let item1Elt = test.getItemElt(popup, item1);
  1782     // create a command event
  1783     let evt = item1Elt.ownerDocument.createEvent('Event');
  1784     evt.initEvent('command', true, true);
  1785     item1Elt.dispatchEvent(evt);
  1786   });
  1787 };
  1792 // An item's click listener should work.
  1793 exports.testItemClick = function (assert, done) {
  1794   let test = new TestHelper(assert, done);
  1795   let loader = test.newLoader();
  1797   let item = new loader.cm.Item({
  1798     label: "item",
  1799     data: "item data",
  1800     contentScript: 'self.on("click", function (node, data) {' +
  1801                    '  self.postMessage({' +
  1802                    '    tagName: node.tagName,' +
  1803                    '    data: data' +
  1804                    '  });' +
  1805                    '});',
  1806     onMessage: function (data) {
  1807       assert.equal(this, item, "`this` inside onMessage should be item");
  1808       assert.equal(data.tagName, "HTML", "node should be an HTML element");
  1809       assert.equal(data.data, item.data, "data should be item data");
  1810       test.done();
  1812   });
  1814   test.showMenu(null, function (popup) {
  1815     test.checkMenu([item], [], []);
  1816     let elt = test.getItemElt(popup, item);
  1817     elt.click();
  1818   });
  1819 };
  1822 // A menu's click listener should work and receive bubbling clicks from
  1823 // sub-items appropriately.  This also tests menus and ensures that when a CSS
  1824 // selector context matches the clicked node's ancestor, the matching ancestor
  1825 // is passed to listeners as the clicked node.
  1826 exports.testMenuClick = function (assert, done) {
  1827   // Create a top-level menu, submenu, and item, like this:
  1828   // topMenu -> submenu -> item
  1829   // Click the item and make sure the click bubbles.
  1830   let test = new TestHelper(assert, done);
  1831   let loader = test.newLoader();
  1833   let item = new loader.cm.Item({
  1834     label: "submenu item",
  1835     data: "submenu item data",
  1836     context: loader.cm.SelectorContext("a"),
  1837   });
  1839   let submenu = new loader.cm.Menu({
  1840     label: "submenu",
  1841     context: loader.cm.SelectorContext("a"),
  1842     items: [item]
  1843   });
  1845   let topMenu = new loader.cm.Menu({
  1846     label: "top menu",
  1847     contentScript: 'self.on("click", function (node, data) {' +
  1848                    '  self.postMessage({' +
  1849                    '    tagName: node.tagName,' +
  1850                    '    data: data' +
  1851                    '  });' +
  1852                    '});',
  1853     onMessage: function (data) {
  1854       assert.equal(this, topMenu, "`this` inside top menu should be menu");
  1855       assert.equal(data.tagName, "A", "Clicked node should be anchor");
  1856       assert.equal(data.data, item.data,
  1857                        "Clicked item data should be correct");
  1858       test.done();
  1859     },
  1860     items: [submenu],
  1861     context: loader.cm.SelectorContext("a")
  1862   });
  1864   test.withTestDoc(function (window, doc) {
  1865     test.showMenu(doc.getElementById("span-link"), function (popup) {
  1866       test.checkMenu([topMenu], [], []);
  1867       let topMenuElt = test.getItemElt(popup, topMenu);
  1868       let topMenuPopup = topMenuElt.firstChild;
  1869       let submenuElt = test.getItemElt(topMenuPopup, submenu);
  1870       let submenuPopup = submenuElt.firstChild;
  1871       let itemElt = test.getItemElt(submenuPopup, item);
  1872       itemElt.click();
  1873     });
  1874   });
  1875 };
  1877 // Click listeners should work when multiple modules are loaded.
  1878 exports.testItemClickMultipleModules = function (assert, done) {
  1879   let test = new TestHelper(assert, done);
  1880   let loader0 = test.newLoader();
  1881   let loader1 = test.newLoader();
  1883   let item0 = loader0.cm.Item({
  1884     label: "loader 0 item",
  1885     contentScript: 'self.on("click", self.postMessage);',
  1886     onMessage: function () {
  1887       test.fail("loader 0 item should not emit click event");
  1889   });
  1890   let item1 = loader1.cm.Item({
  1891     label: "loader 1 item",
  1892     contentScript: 'self.on("click", self.postMessage);',
  1893     onMessage: function () {
  1894       test.pass("loader 1 item clicked as expected");
  1895       test.done();
  1897   });
  1899   test.showMenu(null, function (popup) {
  1900     test.checkMenu([item0, item1], [], []);
  1901     let item1Elt = test.getItemElt(popup, item1);
  1902     item1Elt.click();
  1903   });
  1904 };
  1907 // Adding a separator to a submenu should work OK.
  1908 exports.testSeparator = function (assert, done) {
  1909   let test = new TestHelper(assert, done);
  1910   let loader = test.newLoader();
  1912   let menu = new loader.cm.Menu({
  1913     label: "submenu",
  1914     items: [new loader.cm.Separator()]
  1915   });
  1917   test.showMenu(null, function (popup) {
  1918     test.checkMenu([menu], [], []);
  1919     test.done();
  1920   });
  1921 };
  1924 // The parentMenu option should work
  1925 exports.testParentMenu = function (assert, done) {
  1926   let test = new TestHelper(assert, done);
  1927   let loader = test.newLoader();
  1929   let menu = new loader.cm.Menu({
  1930     label: "submenu",
  1931     items: [loader.cm.Item({ label: "item 1" })],
  1932     parentMenu: loader.cm.contentContextMenu
  1933   });
  1935   let item = loader.cm.Item({
  1936     label: "item 2",
  1937     parentMenu: menu,
  1938   });
  1940   assert.equal(menu.items[1], item, "Item should be in the sub menu");
  1942   test.showMenu(null, function (popup) {
  1943     test.checkMenu([menu], [], []);
  1944     test.done();
  1945   });
  1946 };
  1949 // Existing context menu modifications should apply to new windows.
  1950 exports.testNewWindow = function (assert, done) {
  1951   let test = new TestHelper(assert, done);
  1952   let loader = test.newLoader();
  1954   let item = new loader.cm.Item({ label: "item" });
  1956   test.withNewWindow(function () {
  1957     test.showMenu(null, function (popup) {
  1958       test.checkMenu([item], [], []);
  1959       test.done();
  1960     });
  1961   });
  1962 };
  1965 // When a new window is opened, items added by an unloaded module should not
  1966 // be present in the menu.
  1967 exports.testNewWindowMultipleModules = function (assert, done) {
  1968   let test = new TestHelper(assert, done);
  1969   let loader = test.newLoader();
  1970   let item = new loader.cm.Item({ label: "item" });
  1972   test.showMenu(null, function (popup) {
  1973     test.checkMenu([item], [], []);
  1974     popup.hidePopup();
  1975     loader.unload();
  1976     test.withNewWindow(function () {
  1977       test.showMenu(null, function (popup) {
  1978         test.checkMenu([item], [], [item]);
  1979         test.done();
  1980       });
  1981     });
  1982   });
  1983 };
  1986 // Existing context menu modifications should not apply to new private windows.
  1987 exports.testNewPrivateWindow = function (assert, done) {
  1988   let test = new TestHelper(assert, done);
  1989   let loader = test.newLoader();
  1991   let item = new loader.cm.Item({ label: "item" });
  1993   test.showMenu(null, function (popup) {
  1994     test.checkMenu([item], [], []);
  1995     popup.hidePopup();
  1997     test.withNewPrivateWindow(function () {
  1998       test.showMenu(null, function (popup) {
  1999         test.checkMenu([], [], []);
  2000         test.done();
  2001       });
  2002     });
  2003   });
  2004 };
  2007 // Existing context menu modifications should apply to new private windows when
  2008 // private browsing support is enabled.
  2009 exports.testNewPrivateEnabledWindow = function (assert, done) {
  2010   let test = new TestHelper(assert, done);
  2011   let loader = test.newPrivateLoader();
  2013   let item = new loader.cm.Item({ label: "item" });
  2015   test.showMenu(null, function (popup) {
  2016     test.checkMenu([item], [], []);
  2017     popup.hidePopup();
  2019     test.withNewPrivateWindow(function () {
  2020       test.showMenu(null, function (popup) {
  2021         test.checkMenu([item], [], []);
  2022         test.done();
  2023       });
  2024     });
  2025   });
  2026 };
  2029 // Existing context menu modifications should apply to new private windows when
  2030 // private browsing support is enabled unless unloaded.
  2031 exports.testNewPrivateEnabledWindowUnloaded = function (assert, done) {
  2032   let test = new TestHelper(assert, done);
  2033   let loader = test.newPrivateLoader();
  2035   let item = new loader.cm.Item({ label: "item" });
  2037   test.showMenu(null, function (popup) {
  2038     test.checkMenu([item], [], []);
  2039     popup.hidePopup();
  2041     loader.unload();
  2043     test.withNewPrivateWindow(function () {
  2044       test.showMenu(null, function (popup) {
  2045         test.checkMenu([], [], []);
  2046         test.done();
  2047       });
  2048     });
  2049   });
  2050 };
  2053 // Items in the context menu should be sorted according to locale.
  2054 exports.testSorting = function (assert, done) {
  2055   let test = new TestHelper(assert, done);
  2056   let loader = test.newLoader();
  2058   // Make an unsorted items list.  It'll look like this:
  2059   //   item 1, item 0, item 3, item 2, item 5, item 4, ...
  2060   let items = [];
  2061   for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i += 2) {
  2062     items.push(new loader.cm.Item({ label: "item " + (i + 1) }));
  2063     items.push(new loader.cm.Item({ label: "item " + i }));
  2066   test.showMenu(null, function (popup) {
  2067     test.checkMenu(items, [], []);
  2068     test.done();
  2069   });
  2070 };
  2073 // Items in the overflow menu should be sorted according to locale.
  2074 exports.testSortingOverflow = function (assert, done) {
  2075   let test = new TestHelper(assert, done);
  2076   let loader = test.newLoader();
  2078   // Make an unsorted items list.  It'll look like this:
  2079   //   item 1, item 0, item 3, item 2, item 5, item 4, ...
  2080   let items = [];
  2081   for (let i = 0; i < OVERFLOW_THRESH_DEFAULT * 2; i += 2) {
  2082     items.push(new loader.cm.Item({ label: "item " + (i + 1) }));
  2083     items.push(new loader.cm.Item({ label: "item " + i }));
  2086   test.showMenu(null, function (popup) {
  2087     test.checkMenu(items, [], []);
  2088     test.done();
  2089   });
  2090 };
  2093 // Multiple modules shouldn't interfere with sorting.
  2094 exports.testSortingMultipleModules = function (assert, done) {
  2095   let test = new TestHelper(assert, done);
  2096   let loader0 = test.newLoader();
  2097   let loader1 = test.newLoader();
  2099   let items0 = [];
  2100   let items1 = [];
  2101   for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) {
  2102     if (i % 2) {
  2103       let item = new loader0.cm.Item({ label: "item " + i });
  2104       items0.push(item);
  2106     else {
  2107       let item = new loader1.cm.Item({ label: "item " + i });
  2108       items1.push(item);
  2111   let allItems = items0.concat(items1);
  2113   test.showMenu(null, function (popup) {
  2115     // All items should be present and sorted.
  2116     test.checkMenu(allItems, [], []);
  2117     popup.hidePopup();
  2118     loader0.unload();
  2119     loader1.unload();
  2120     test.showMenu(null, function (popup) {
  2122       // All items should be removed.
  2123       test.checkMenu(allItems, [], allItems);
  2124       test.done();
  2125     });
  2126   });
  2127 };
  2130 // Content click handlers and context handlers should be able to communicate,
  2131 // i.e., they're eval'ed in the same worker and sandbox.
  2132 exports.testContentCommunication = function (assert, done) {
  2133   let test = new TestHelper(assert, done);
  2134   let loader = test.newLoader();
  2136   let item = new loader.cm.Item({
  2137     label: "item",
  2138     contentScript: 'var potato;' +
  2139                    'self.on("context", function () {' +
  2140                    '  potato = "potato";' +
  2141                    '  return true;' +
  2142                    '});' +
  2143                    'self.on("click", function () {' +
  2144                    '  self.postMessage(potato);' +
  2145                    '});',
  2146   });
  2148   item.on("message", function (data) {
  2149     assert.equal(data, "potato", "That's a lot of potatoes!");
  2150     test.done();
  2151   });
  2153   test.showMenu(null, function (popup) {
  2154     test.checkMenu([item], [], []);
  2155     let elt = test.getItemElt(popup, item);
  2156     elt.click();
  2157   });
  2158 };
  2161 // When the context menu is invoked on a tab that was already open when the
  2162 // module was loaded, it should contain the expected items and content workers
  2163 // should function as expected.
  2164 exports.testLoadWithOpenTab = function (assert, done) {
  2165   let test = new TestHelper(assert, done);
  2166   test.withTestDoc(function (window, doc) {
  2167     let loader = test.newLoader();
  2168     let item = new loader.cm.Item({
  2169       label: "item",
  2170       contentScript:
  2171         'self.on("click", function () self.postMessage("click"));',
  2172       onMessage: function (msg) {
  2173         if (msg === "click")
  2174           test.done();
  2176     });
  2177     test.showMenu(null, function (popup) {
  2178       test.checkMenu([item], [], []);
  2179       test.getItemElt(popup, item).click();
  2180     });
  2181   });
  2182 };
  2184 // Bug 732716: Ensure that the node given in `click` event works fine
  2185 // (i.e. is correctly wrapped)
  2186 exports.testDrawImageOnClickNode = function (assert, done) {
  2187   let test = new TestHelper(assert, done);
  2188   test.withTestDoc(function (window, doc) {
  2189     let loader = test.newLoader();
  2190     let item = new loader.cm.Item({
  2191       label: "item",
  2192       context: loader.cm.SelectorContext("img"),
  2193       contentScript: "new " + function() {
  2194         self.on("click", function (img, data) {
  2195           let ctx = document.createElement("canvas").getContext("2d");
  2196           ctx.drawImage(img, 1, 1, 1, 1);
  2197           self.postMessage("done");
  2198         });
  2199       },
  2200       onMessage: function (msg) {
  2201         if (msg === "done")
  2202           test.done();
  2204     });
  2205     test.showMenu(doc.getElementById("image"), function (popup) {
  2206       test.checkMenu([item], [], []);
  2207       test.getItemElt(popup, item).click();
  2208     });
  2209   });
  2210 };
  2213 // Setting an item's label before the menu is ever shown should correctly change
  2214 // its label.
  2215 exports.testSetLabelBeforeShow = function (assert, done) {
  2216   let test = new TestHelper(assert, done);
  2217   let loader = test.newLoader();
  2219   let items = [
  2220     new loader.cm.Item({ label: "a" }),
  2221     new loader.cm.Item({ label: "b" })
  2223   items[0].label = "z";
  2224   assert.equal(items[0].label, "z");
  2226   test.showMenu(null, function (popup) {
  2227     test.checkMenu(items, [], []);
  2228     test.done();
  2229   });
  2230 };
  2233 // Setting an item's label after the menu is shown should correctly change its
  2234 // label.
  2235 exports.testSetLabelAfterShow = function (assert, done) {
  2236   let test = new TestHelper(assert, done);
  2237   let loader = test.newLoader();
  2239   let items = [
  2240     new loader.cm.Item({ label: "a" }),
  2241     new loader.cm.Item({ label: "b" })
  2242   ];
  2244   test.showMenu(null, function (popup) {
  2245     test.checkMenu(items, [], []);
  2246     popup.hidePopup();
  2248     items[0].label = "z";
  2249     assert.equal(items[0].label, "z");
  2250     test.showMenu(null, function (popup) {
  2251       test.checkMenu(items, [], []);
  2252       test.done();
  2253     });
  2254   });
  2255 };
  2258 // Setting an item's label before the menu is ever shown should correctly change
  2259 // its label.
  2260 exports.testSetLabelBeforeShowOverflow = function (assert, done) {
  2261   let test = new TestHelper(assert, done);
  2262   let loader = test.newLoader();
  2264   let prefs = loader.loader.require("sdk/preferences/service");
  2265   prefs.set(OVERFLOW_THRESH_PREF, 0);
  2267   let items = [
  2268     new loader.cm.Item({ label: "a" }),
  2269     new loader.cm.Item({ label: "b" })
  2271   items[0].label = "z";
  2272   assert.equal(items[0].label, "z");
  2274   test.showMenu(null, function (popup) {
  2275     test.checkMenu(items, [], []);
  2276     test.done();
  2277   });
  2278 };
  2281 // Setting an item's label after the menu is shown should correctly change its
  2282 // label.
  2283 exports.testSetLabelAfterShowOverflow = function (assert, done) {
  2284   let test = new TestHelper(assert, done);
  2285   let loader = test.newLoader();
  2287   let prefs = loader.loader.require("sdk/preferences/service");
  2288   prefs.set(OVERFLOW_THRESH_PREF, 0);
  2290   let items = [
  2291     new loader.cm.Item({ label: "a" }),
  2292     new loader.cm.Item({ label: "b" })
  2293   ];
  2295   test.showMenu(null, function (popup) {
  2296     test.checkMenu(items, [], []);
  2297     popup.hidePopup();
  2299     items[0].label = "z";
  2300     assert.equal(items[0].label, "z");
  2301     test.showMenu(null, function (popup) {
  2302       test.checkMenu(items, [], []);
  2303       test.done();
  2304     });
  2305   });
  2306 };
  2309 // Setting the label of an item in a Menu should work.
  2310 exports.testSetLabelMenuItem = function (assert, done) {
  2311   let test = new TestHelper(assert, done);
  2312   let loader = test.newLoader();
  2314   let menu = loader.cm.Menu({
  2315     label: "menu",
  2316     items: [loader.cm.Item({ label: "a" })]
  2317   });
  2318   menu.items[0].label = "z";
  2320   assert.equal(menu.items[0].label, "z");
  2322   test.showMenu(null, function (popup) {
  2323     test.checkMenu([menu], [], []);
  2324     test.done();
  2325   });
  2326 };
  2329 // Menu.addItem() should work.
  2330 exports.testMenuAddItem = function (assert, done) {
  2331   let test = new TestHelper(assert, done);
  2332   let loader = test.newLoader();
  2334   let menu = loader.cm.Menu({
  2335     label: "menu",
  2336     items: [
  2337       loader.cm.Item({ label: "item 0" })
  2339   });
  2340   menu.addItem(loader.cm.Item({ label: "item 1" }));
  2341   menu.addItem(loader.cm.Item({ label: "item 2" }));
  2343   assert.equal(menu.items.length, 3,
  2344                    "menu should have correct number of items");
  2345   for (let i = 0; i < 3; i++) {
  2346     assert.equal(menu.items[i].label, "item " + i,
  2347                      "item label should be correct");
  2348     assert.equal(menu.items[i].parentMenu, menu,
  2349                      "item's parent menu should be correct");
  2352   test.showMenu(null, function (popup) {
  2353     test.checkMenu([menu], [], []);
  2354     test.done();
  2355   });
  2356 };
  2359 // Adding the same item twice to a menu should work as expected.
  2360 exports.testMenuAddItemTwice = function (assert, done) {
  2361   let test = new TestHelper(assert, done);
  2362   let loader = test.newLoader();
  2364   let menu = loader.cm.Menu({
  2365     label: "menu",
  2366     items: []
  2367   });
  2368   let subitem = loader.cm.Item({ label: "item 1" })
  2369   menu.addItem(subitem);
  2370   menu.addItem(loader.cm.Item({ label: "item 0" }));
  2371   menu.addItem(subitem);
  2373   assert.equal(menu.items.length, 2,
  2374                    "menu should have correct number of items");
  2375   for (let i = 0; i < 2; i++) {
  2376     assert.equal(menu.items[i].label, "item " + i,
  2377                      "item label should be correct");
  2380   test.showMenu(null, function (popup) {
  2381     test.checkMenu([menu], [], []);
  2382     test.done();
  2383   });
  2384 };
  2387 // Menu.removeItem() should work.
  2388 exports.testMenuRemoveItem = function (assert, done) {
  2389   let test = new TestHelper(assert, done);
  2390   let loader = test.newLoader();
  2392   let subitem = loader.cm.Item({ label: "item 1" });
  2393   let menu = loader.cm.Menu({
  2394     label: "menu",
  2395     items: [
  2396       loader.cm.Item({ label: "item 0" }),
  2397       subitem,
  2398       loader.cm.Item({ label: "item 2" })
  2400   });
  2402   // Removing twice should be harmless.
  2403   menu.removeItem(subitem);
  2404   menu.removeItem(subitem);
  2406   assert.equal(subitem.parentMenu, null,
  2407                    "item's parent menu should be correct");
  2409   assert.equal(menu.items.length, 2,
  2410                    "menu should have correct number of items");
  2411   assert.equal(menu.items[0].label, "item 0",
  2412                    "item label should be correct");
  2413   assert.equal(menu.items[1].label, "item 2",
  2414                    "item label should be correct");
  2416   test.showMenu(null, function (popup) {
  2417     test.checkMenu([menu], [], []);
  2418     test.done();
  2419   });
  2420 };
  2423 // Adding an item currently contained in one menu to another menu should work.
  2424 exports.testMenuItemSwap = function (assert, done) {
  2425   let test = new TestHelper(assert, done);
  2426   let loader = test.newLoader();
  2428   let subitem = loader.cm.Item({ label: "item" });
  2429   let menu0 = loader.cm.Menu({
  2430     label: "menu 0",
  2431     items: [subitem]
  2432   });
  2433   let menu1 = loader.cm.Menu({
  2434     label: "menu 1",
  2435     items: []
  2436   });
  2437   menu1.addItem(subitem);
  2439   assert.equal(menu0.items.length, 0,
  2440                    "menu should have correct number of items");
  2442   assert.equal(menu1.items.length, 1,
  2443                    "menu should have correct number of items");
  2444   assert.equal(menu1.items[0].label, "item",
  2445                    "item label should be correct");
  2447   assert.equal(subitem.parentMenu, menu1,
  2448                    "item's parent menu should be correct");
  2450   test.showMenu(null, function (popup) {
  2451     test.checkMenu([menu0, menu1], [menu0], []);
  2452     test.done();
  2453   });
  2454 };
  2457 // Destroying an item should remove it from its parent menu.
  2458 exports.testMenuItemDestroy = function (assert, done) {
  2459   let test = new TestHelper(assert, done);
  2460   let loader = test.newLoader();
  2462   let subitem = loader.cm.Item({ label: "item" });
  2463   let menu = loader.cm.Menu({
  2464     label: "menu",
  2465     items: [subitem]
  2466   });
  2467   subitem.destroy();
  2469   assert.equal(menu.items.length, 0,
  2470                    "menu should have correct number of items");
  2471   assert.equal(subitem.parentMenu, null,
  2472                    "item's parent menu should be correct");
  2474   test.showMenu(null, function (popup) {
  2475     test.checkMenu([menu], [menu], []);
  2476     test.done();
  2477   });
  2478 };
  2481 // Setting Menu.items should work.
  2482 exports.testMenuItemsSetter = function (assert, done) {
  2483   let test = new TestHelper(assert, done);
  2484   let loader = test.newLoader();
  2486   let menu = loader.cm.Menu({
  2487     label: "menu",
  2488     items: [
  2489       loader.cm.Item({ label: "old item 0" }),
  2490       loader.cm.Item({ label: "old item 1" })
  2492   });
  2493   menu.items = [
  2494     loader.cm.Item({ label: "new item 0" }),
  2495     loader.cm.Item({ label: "new item 1" }),
  2496     loader.cm.Item({ label: "new item 2" })
  2497   ];
  2499   assert.equal(menu.items.length, 3,
  2500                    "menu should have correct number of items");
  2501   for (let i = 0; i < 3; i++) {
  2502     assert.equal(menu.items[i].label, "new item " + i,
  2503                      "item label should be correct");
  2504     assert.equal(menu.items[i].parentMenu, menu,
  2505                      "item's parent menu should be correct");
  2508   test.showMenu(null, function (popup) {
  2509     test.checkMenu([menu], [], []);
  2510     test.done();
  2511   });
  2512 };
  2515 // Setting Item.data should work.
  2516 exports.testItemDataSetter = function (assert, done) {
  2517   let test = new TestHelper(assert, done);
  2518   let loader = test.newLoader();
  2520   let item = loader.cm.Item({ label: "old item 0", data: "old" });
  2521   item.data = "new";
  2523   assert.equal(item.data, "new", "item should have correct data");
  2525   test.showMenu(null, function (popup) {
  2526     test.checkMenu([item], [], []);
  2527     test.done();
  2528   });
  2529 };
  2532 // Open the test doc, load the module, make sure items appear when context-
  2533 // clicking the iframe.
  2534 exports.testAlreadyOpenIframe = function (assert, done) {
  2535   let test = new TestHelper(assert, done);
  2536   test.withTestDoc(function (window, doc) {
  2537     let loader = test.newLoader();
  2538     let item = new loader.cm.Item({
  2539       label: "item"
  2540     });
  2541     test.showMenu(doc.getElementById("iframe"), function (popup) {
  2542       test.checkMenu([item], [], []);
  2543       test.done();
  2544     });
  2545   });
  2546 };
  2549 // Tests that a missing label throws an exception
  2550 exports.testItemNoLabel = function (assert, done) {
  2551   let test = new TestHelper(assert, done);
  2552   let loader = test.newLoader();
  2554   try {
  2555     new loader.cm.Item({});
  2556     assert.ok(false, "Should have seen exception");
  2558   catch (e) {
  2559     assert.ok(true, "Should have seen exception");
  2562   try {
  2563     new loader.cm.Item({ label: null });
  2564     assert.ok(false, "Should have seen exception");
  2566   catch (e) {
  2567     assert.ok(true, "Should have seen exception");
  2570   try {
  2571     new loader.cm.Item({ label: undefined });
  2572     assert.ok(false, "Should have seen exception");
  2574   catch (e) {
  2575     assert.ok(true, "Should have seen exception");
  2578   try {
  2579     new loader.cm.Item({ label: "" });
  2580     assert.ok(false, "Should have seen exception");
  2582   catch (e) {
  2583     assert.ok(true, "Should have seen exception");
  2586   test.done();
  2590 // Tests that items can have an empty data property
  2591 exports.testItemNoData = function (assert, done) {
  2592   let test = new TestHelper(assert, done);
  2593   let loader = test.newLoader();
  2595   function checkData(data) {
  2596     assert.equal(data, undefined, "Data should be undefined");
  2599   let item1 = new loader.cm.Item({
  2600     label: "item 1",
  2601     contentScript: 'self.on("click", function(node, data) self.postMessage(data))',
  2602     onMessage: checkData
  2603   });
  2604   let item2 = new loader.cm.Item({
  2605     label: "item 2",
  2606     data: null,
  2607     contentScript: 'self.on("click", function(node, data) self.postMessage(data))',
  2608     onMessage: checkData
  2609   });
  2610   let item3 = new loader.cm.Item({
  2611     label: "item 3",
  2612     data: undefined,
  2613     contentScript: 'self.on("click", function(node, data) self.postMessage(data))',
  2614     onMessage: checkData
  2615   });
  2617   assert.equal(item1.data, undefined, "Should be no defined data");
  2618   assert.equal(item2.data, null, "Should be no defined data");
  2619   assert.equal(item3.data, undefined, "Should be no defined data");
  2621   test.showMenu(null, function (popup) {
  2622     test.checkMenu([item1, item2, item3], [], []);
  2624     let itemElt = test.getItemElt(popup, item1);
  2625     itemElt.click();
  2627     test.hideMenu(function() {
  2628       test.showMenu(null, function (popup) {
  2629         let itemElt = test.getItemElt(popup, item2);
  2630         itemElt.click();
  2632         test.hideMenu(function() {
  2633           test.showMenu(null, function (popup) {
  2634             let itemElt = test.getItemElt(popup, item3);
  2635             itemElt.click();
  2637             test.done();
  2638           });
  2639         });
  2640       });
  2641     });
  2642   });
  2646 // Tests that items without an image don't attempt to show one
  2647 exports.testItemNoImage = function (assert, done) {
  2648   let test = new TestHelper(assert, done);
  2649   let loader = test.newLoader();
  2651   let item1 = new loader.cm.Item({ label: "item 1" });
  2652   let item2 = new loader.cm.Item({ label: "item 2", image: null });
  2653   let item3 = new loader.cm.Item({ label: "item 3", image: undefined });
  2655   assert.equal(item1.image, undefined, "Should be no defined image");
  2656   assert.equal(item2.image, null, "Should be no defined image");
  2657   assert.equal(item3.image, undefined, "Should be no defined image");
  2659   test.showMenu(null, function (popup) {
  2660     test.checkMenu([item1, item2, item3], [], []);
  2662     test.done();
  2663   });
  2667 // Test image support.
  2668 exports.testItemImage = function (assert, done) {
  2669   let test = new TestHelper(assert, done);
  2670   let loader = test.newLoader();
  2672   let imageURL = data.url("moz_favicon.ico");
  2673   let item = new loader.cm.Item({ label: "item", image: imageURL });
  2674   let menu = new loader.cm.Menu({ label: "menu", image: imageURL, items: [
  2675     loader.cm.Item({ label: "subitem" })
  2676   ]});
  2677   assert.equal(item.image, imageURL, "Should have set the image correctly");
  2678   assert.equal(menu.image, imageURL, "Should have set the image correctly");
  2680   test.showMenu(null, function (popup) {
  2681     test.checkMenu([item, menu], [], []);
  2683     let imageURL2 = data.url("dummy.ico");
  2684     item.image = imageURL2;
  2685     menu.image = imageURL2;
  2686     assert.equal(item.image, imageURL2, "Should have set the image correctly");
  2687     assert.equal(menu.image, imageURL2, "Should have set the image correctly");
  2688     test.checkMenu([item, menu], [], []);
  2690     item.image = null;
  2691     menu.image = null;
  2692     assert.equal(item.image, null, "Should have set the image correctly");
  2693     assert.equal(menu.image, null, "Should have set the image correctly");
  2694     test.checkMenu([item, menu], [], []);
  2696     test.done();
  2697   });
  2698 };
  2700 // Test image URL validation.
  2701 exports.testItemImageValidURL = function (assert, done) {
  2702   let test = new TestHelper(assert, done);
  2703   let loader = test.newLoader();
  2705   assert.throws(function(){
  2706       new loader.cm.Item({
  2707         label: "item 1",
  2708         image: "foo"
  2709       })
  2710     }, /Image URL validation failed/
  2711   );
  2713   assert.throws(function(){
  2714       new loader.cm.Item({
  2715         label: "item 2",
  2716         image: false
  2717       })
  2718     }, /Image URL validation failed/
  2719   );
  2721   assert.throws(function(){
  2722       new loader.cm.Item({
  2723         label: "item 3",
  2724         image: 0
  2725       })
  2726     }, /Image URL validation failed/
  2727   );
  2729   let imageURL = data.url("moz_favicon.ico");
  2730   let item4 = new loader.cm.Item({ label: "item 4", image: imageURL });
  2731   let item5 = new loader.cm.Item({ label: "item 5", image: null });
  2732   let item6 = new loader.cm.Item({ label: "item 6", image: undefined });
  2734   assert.equal(item4.image, imageURL, "Should be proper image URL");
  2735   assert.equal(item5.image, null, "Should be null image");
  2736   assert.equal(item6.image, undefined, "Should be undefined image");
  2738   test.done();
  2739 };
  2742 // Menu.destroy should destroy the item tree rooted at that menu.
  2743 exports.testMenuDestroy = function (assert, done) {
  2744   let test = new TestHelper(assert, done);
  2745   let loader = test.newLoader();
  2747   let menu = loader.cm.Menu({
  2748     label: "menu",
  2749     items: [
  2750       loader.cm.Item({ label: "item 0" }),
  2751       loader.cm.Menu({
  2752         label: "item 1",
  2753         items: [
  2754           loader.cm.Item({ label: "subitem 0" }),
  2755           loader.cm.Item({ label: "subitem 1" }),
  2756           loader.cm.Item({ label: "subitem 2" })
  2758       }),
  2759       loader.cm.Item({ label: "item 2" })
  2761   });
  2762   menu.destroy();
  2764   /*let numRegistryEntries = 0;
  2765   loader.globalScope.browserManager.browserWins.forEach(function (bwin) {
  2766     for (let itemID in bwin.items)
  2767       numRegistryEntries++;
  2768   });
  2769   assert.equal(numRegistryEntries, 0, "All items should be unregistered.");*/
  2771   test.showMenu(null, function (popup) {
  2772     test.checkMenu([menu], [], [menu]);
  2773     test.done();
  2774   });
  2775 };
  2777 // Checks that if a menu contains sub items that are hidden then the menu is
  2778 // hidden too. Also checks that content scripts and contexts work for sub items.
  2779 exports.testSubItemContextNoMatchHideMenu = function (assert, done) {
  2780   let test = new TestHelper(assert, done);
  2781   let loader = test.newLoader();
  2783   let items = [
  2784     loader.cm.Menu({
  2785       label: "menu 1",
  2786       items: [
  2787         loader.cm.Item({
  2788           label: "subitem 1",
  2789           context: loader.cm.SelectorContext(".foo")
  2790         })
  2792     }),
  2793     loader.cm.Menu({
  2794       label: "menu 2",
  2795       items: [
  2796         loader.cm.Item({
  2797           label: "subitem 2",
  2798           contentScript: 'self.on("context", function () false);'
  2799         })
  2801     }),
  2802     loader.cm.Menu({
  2803       label: "menu 3",
  2804       items: [
  2805         loader.cm.Item({
  2806           label: "subitem 3",
  2807           context: loader.cm.SelectorContext(".foo")
  2808         }),
  2809         loader.cm.Item({
  2810           label: "subitem 4",
  2811           contentScript: 'self.on("context", function () false);'
  2812         })
  2814     })
  2815   ];
  2817   test.showMenu(null, function (popup) {
  2818     test.checkMenu(items, items, []);
  2819     test.done();
  2820   });
  2821 };
  2824 // Checks that if a menu contains a combination of hidden and visible sub items
  2825 // then the menu is still visible too.
  2826 exports.testSubItemContextMatch = function (assert, done) {
  2827   let test = new TestHelper(assert, done);
  2828   let loader = test.newLoader();
  2830   let hiddenItems = [
  2831     loader.cm.Item({
  2832       label: "subitem 3",
  2833       context: loader.cm.SelectorContext(".foo")
  2834     }),
  2835     loader.cm.Item({
  2836       label: "subitem 6",
  2837       contentScript: 'self.on("context", function () false);'
  2838     })
  2839   ];
  2841   let items = [
  2842     loader.cm.Menu({
  2843       label: "menu 1",
  2844       items: [
  2845         loader.cm.Item({
  2846           label: "subitem 1",
  2847           context: loader.cm.URLContext(TEST_DOC_URL)
  2848         })
  2850     }),
  2851     loader.cm.Menu({
  2852       label: "menu 2",
  2853       items: [
  2854         loader.cm.Item({
  2855           label: "subitem 2",
  2856           contentScript: 'self.on("context", function () true);'
  2857         })
  2859     }),
  2860     loader.cm.Menu({
  2861       label: "menu 3",
  2862       items: [
  2863         hiddenItems[0],
  2864         loader.cm.Item({
  2865           label: "subitem 4",
  2866           contentScript: 'self.on("context", function () true);'
  2867         })
  2869     }),
  2870     loader.cm.Menu({
  2871       label: "menu 4",
  2872       items: [
  2873         loader.cm.Item({
  2874           label: "subitem 5",
  2875           context: loader.cm.URLContext(TEST_DOC_URL)
  2876         }),
  2877         hiddenItems[1]
  2879     }),
  2880     loader.cm.Menu({
  2881       label: "menu 5",
  2882       items: [
  2883         loader.cm.Item({
  2884           label: "subitem 7",
  2885           context: loader.cm.URLContext(TEST_DOC_URL)
  2886         }),
  2887         loader.cm.Item({
  2888           label: "subitem 8",
  2889           contentScript: 'self.on("context", function () true);'
  2890         })
  2892     })
  2893   ];
  2895   test.withTestDoc(function (window, doc) {
  2896     test.showMenu(null, function (popup) {
  2897       test.checkMenu(items, hiddenItems, []);
  2898       test.done();
  2899     });
  2900   });
  2901 };
  2904 // Child items should default to visible, not to PageContext
  2905 exports.testSubItemDefaultVisible = function (assert, done) {
  2906   let test = new TestHelper(assert, done);
  2907   let loader = test.newLoader();
  2909   let items = [
  2910     loader.cm.Menu({
  2911       label: "menu 1",
  2912       context: loader.cm.SelectorContext("img"),
  2913       items: [
  2914         loader.cm.Item({
  2915           label: "subitem 1"
  2916         }),
  2917         loader.cm.Item({
  2918           label: "subitem 2",
  2919           context: loader.cm.SelectorContext("img")
  2920         }),
  2921         loader.cm.Item({
  2922           label: "subitem 3",
  2923           context: loader.cm.SelectorContext("a")
  2924         })
  2926     })
  2927   ];
  2929   // subitem 3 will be hidden
  2930   let hiddenItems = [items[0].items[2]];
  2932   test.withTestDoc(function (window, doc) {
  2933     test.showMenu(doc.getElementById("image"), function (popup) {
  2934       test.checkMenu(items, hiddenItems, []);
  2935       test.done();
  2936     });
  2937   });
  2938 };
  2940 // Tests that the click event on sub menuitem
  2941 // tiggers the click event for the sub menuitem and the parent menu
  2942 exports.testSubItemClick = function (assert, done) {
  2943   let test = new TestHelper(assert, done);
  2944   let loader = test.newLoader();
  2946   let state = 0;
  2948   let items = [
  2949     loader.cm.Menu({
  2950       label: "menu 1",
  2951       items: [
  2952         loader.cm.Item({
  2953           label: "subitem 1",
  2954           data: "foobar",
  2955           contentScript: 'self.on("click", function (node, data) {' +
  2956                          '  self.postMessage({' +
  2957                          '    tagName: node.tagName,' +
  2958                          '    data: data' +
  2959                          '  });' +
  2960                          '});',
  2961           onMessage: function(msg) {
  2962             assert.equal(msg.tagName, "HTML", "should have seen the right node");
  2963             assert.equal(msg.data, "foobar", "should have seen the right data");
  2964             assert.equal(state, 0, "should have seen the event at the right time");
  2965             state++;
  2967         })
  2968       ],
  2969       contentScript: 'self.on("click", function (node, data) {' +
  2970                      '  self.postMessage({' +
  2971                      '    tagName: node.tagName,' +
  2972                      '    data: data' +
  2973                      '  });' +
  2974                      '});',
  2975       onMessage: function(msg) {
  2976         assert.equal(msg.tagName, "HTML", "should have seen the right node");
  2977         assert.equal(msg.data, "foobar", "should have seen the right data");
  2978         assert.equal(state, 1, "should have seen the event at the right time");
  2980         test.done();
  2982     })
  2983   ];
  2985   test.withTestDoc(function (window, doc) {
  2986     test.showMenu(null, function (popup) {
  2987       test.checkMenu(items, [], []);
  2989       let topMenuElt = test.getItemElt(popup, items[0]);
  2990       let topMenuPopup = topMenuElt.firstChild;
  2991       let itemElt = test.getItemElt(topMenuPopup, items[0].items[0]);
  2992       itemElt.click();
  2993     });
  2994   });
  2995 };
  2997 // Tests that the command event on sub menuitem
  2998 // tiggers the click event for the sub menuitem and the parent menu
  2999 exports.testSubItemCommand = function (assert, done) {
  3000   let test = new TestHelper(assert, done);
  3001   let loader = test.newLoader();
  3003   let state = 0;
  3005   let items = [
  3006     loader.cm.Menu({
  3007       label: "menu 1",
  3008       items: [
  3009         loader.cm.Item({
  3010           label: "subitem 1",
  3011           data: "foobar",
  3012           contentScript: 'self.on("click", function (node, data) {' +
  3013                          '  self.postMessage({' +
  3014                          '    tagName: node.tagName,' +
  3015                          '    data: data' +
  3016                          '  });' +
  3017                          '});',
  3018           onMessage: function(msg) {
  3019             assert.equal(msg.tagName, "HTML", "should have seen the right node");
  3020             assert.equal(msg.data, "foobar", "should have seen the right data");
  3021             assert.equal(state, 0, "should have seen the event at the right time");
  3022             state++;
  3024         })
  3025       ],
  3026       contentScript: 'self.on("click", function (node, data) {' +
  3027                      '  self.postMessage({' +
  3028                      '    tagName: node.tagName,' +
  3029                      '    data: data' +
  3030                      '  });' +
  3031                      '});',
  3032       onMessage: function(msg) {
  3033         assert.equal(msg.tagName, "HTML", "should have seen the right node");
  3034         assert.equal(msg.data, "foobar", "should have seen the right data");
  3035         assert.equal(state, 1, "should have seen the event at the right time");
  3036         state++
  3038         test.done();
  3040     })
  3041   ];
  3043   test.withTestDoc(function (window, doc) {
  3044     test.showMenu(null, function (popup) {
  3045       test.checkMenu(items, [], []);
  3047       let topMenuElt = test.getItemElt(popup, items[0]);
  3048       let topMenuPopup = topMenuElt.firstChild;
  3049       let itemElt = test.getItemElt(topMenuPopup, items[0].items[0]);
  3051       // create a command event
  3052       let evt = itemElt.ownerDocument.createEvent('Event');
  3053       evt.initEvent('command', true, true);
  3054       itemElt.dispatchEvent(evt);
  3055     });
  3056   });
  3057 };
  3059 // Tests that opening a context menu for an outer frame when an inner frame
  3060 // has a selection doesn't activate the SelectionContext
  3061 exports.testSelectionInInnerFrameNoMatch = function (assert, done) {
  3062   let test = new TestHelper(assert, done);
  3063   let loader = test.newLoader();
  3065   let state = 0;
  3067   let items = [
  3068     loader.cm.Item({
  3069       label: "test item",
  3070       context: loader.cm.SelectionContext()
  3071     })
  3072   ];
  3074   test.withTestDoc(function (window, doc) {
  3075     let frame = doc.getElementById("iframe");
  3076     frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body);
  3078     test.showMenu(null, function (popup) {
  3079       test.checkMenu(items, items, []);
  3080       test.done();
  3081     });
  3082   });
  3083 };
  3085 // Tests that opening a context menu for an inner frame when the inner frame
  3086 // has a selection does activate the SelectionContext
  3087 exports.testSelectionInInnerFrameMatch = function (assert, done) {
  3088   let test = new TestHelper(assert, done);
  3089   let loader = test.newLoader();
  3091   let state = 0;
  3093   let items = [
  3094     loader.cm.Item({
  3095       label: "test item",
  3096       context: loader.cm.SelectionContext()
  3097     })
  3098   ];
  3100   test.withTestDoc(function (window, doc) {
  3101     let frame = doc.getElementById("iframe");
  3102     frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body);
  3104     test.showMenu(frame.contentDocument.getElementById("text"), function (popup) {
  3105       test.checkMenu(items, [], []);
  3106       test.done();
  3107     });
  3108   });
  3109 };
  3111 // Tests that opening a context menu for an inner frame when the outer frame
  3112 // has a selection doesn't activate the SelectionContext
  3113 exports.testSelectionInOuterFrameNoMatch = function (assert, done) {
  3114   let test = new TestHelper(assert, done);
  3115   let loader = test.newLoader();
  3117   let state = 0;
  3119   let items = [
  3120     loader.cm.Item({
  3121       label: "test item",
  3122       context: loader.cm.SelectionContext()
  3123     })
  3124   ];
  3126   test.withTestDoc(function (window, doc) {
  3127     let frame = doc.getElementById("iframe");
  3128     window.getSelection().selectAllChildren(doc.body);
  3130     test.showMenu(frame.contentDocument.getElementById("text"), function (popup) {
  3131       test.checkMenu(items, items, []);
  3132       test.done();
  3133     });
  3134   });
  3135 };
  3138 // Test that the return value of the predicate function determines if
  3139 // item is shown
  3140 exports.testPredicateContextControl = function (assert, done) {
  3141   let test = new TestHelper(assert, done);
  3142   let loader = test.newLoader();
  3144   let itemTrue = loader.cm.Item({
  3145     label: "visible",
  3146     context: loader.cm.PredicateContext(function () { return true; })
  3147   });
  3149   let itemFalse = loader.cm.Item({
  3150     label: "hidden",
  3151     context: loader.cm.PredicateContext(function () { return false; })
  3152   });
  3154   test.showMenu(null, function (popup) {
  3155     test.checkMenu([itemTrue, itemFalse], [itemFalse], []);
  3156     test.done();
  3157   });
  3158 };
  3160 // Test that the data object has the correct document type
  3161 exports.testPredicateContextDocumentType = function (assert, done) {
  3162   let test = new TestHelper(assert, done);
  3163   let loader = test.newLoader();
  3165   let items = [loader.cm.Item({
  3166     label: "item",
  3167     context: loader.cm.PredicateContext(function (data) {
  3168       assert.equal(data.documentType, 'text/html');
  3169       return true;
  3170     })
  3171   })];
  3173   test.withTestDoc(function (window, doc) {
  3174     test.showMenu(null, function (popup) {
  3175       test.checkMenu(items, [], []);
  3176       test.done();
  3177     });
  3178   });
  3179 };
  3181 // Test that the data object has the correct document URL
  3182 exports.testPredicateContextDocumentURL = function (assert, done) {
  3183   let test = new TestHelper(assert, done);
  3184   let loader = test.newLoader();
  3186   let items = [loader.cm.Item({
  3187     label: "item",
  3188     context: loader.cm.PredicateContext(function (data) {
  3189       assert.equal(data.documentURL, TEST_DOC_URL);
  3190       return true;
  3191     })
  3192   })];
  3194   test.withTestDoc(function (window, doc) {
  3195     test.showMenu(null, function (popup) {
  3196       test.checkMenu(items, [], []);
  3197       test.done();
  3198     });
  3199   });
  3200 };
  3203 // Test that the data object has the correct element name
  3204 exports.testPredicateContextTargetName = function (assert, done) {
  3205   let test = new TestHelper(assert, done);
  3206   let loader = test.newLoader();
  3208   let items = [loader.cm.Item({
  3209     label: "item",
  3210     context: loader.cm.PredicateContext(function (data) {
  3211       assert.strictEqual(data.targetName, "input");
  3212       return true;
  3213     })
  3214   })];
  3216   test.withTestDoc(function (window, doc) {
  3217     test.showMenu(doc.getElementById("button"), function (popup) {
  3218       test.checkMenu(items, [], []);
  3219       test.done();
  3220     });
  3221   });
  3222 };
  3225 // Test that the data object has the correct ID
  3226 exports.testPredicateContextTargetIDSet = function (assert, done) {
  3227   let test = new TestHelper(assert, done);
  3228   let loader = test.newLoader();
  3230   let items = [loader.cm.Item({
  3231     label: "item",
  3232     context: loader.cm.PredicateContext(function (data) {
  3233       assert.strictEqual(data.targetID, "button");
  3234       return true;
  3235     })
  3236   })];
  3238   test.withTestDoc(function (window, doc) {
  3239     test.showMenu(doc.getElementById("button"), function (popup) {
  3240       test.checkMenu(items, [], []);
  3241       test.done();
  3242     });
  3243   });
  3244 };
  3246 // Test that the data object has the correct ID
  3247 exports.testPredicateContextTargetIDNotSet = function (assert, done) {
  3248   let test = new TestHelper(assert, done);
  3249   let loader = test.newLoader();
  3251   let items = [loader.cm.Item({
  3252     label: "item",
  3253     context: loader.cm.PredicateContext(function (data) {
  3254       assert.strictEqual(data.targetID, null);
  3255       return true;
  3256     })
  3257   })];
  3259   test.withTestDoc(function (window, doc) {
  3260     test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
  3261       test.checkMenu(items, [], []);
  3262       test.done();
  3263     });
  3264   });
  3265 };
  3267 // Test that the data object is showing editable correctly for regular text inputs
  3268 exports.testPredicateContextTextBoxIsEditable = function (assert, done) {
  3269   let test = new TestHelper(assert, done);
  3270   let loader = test.newLoader();
  3272   let items = [loader.cm.Item({
  3273     label: "item",
  3274     context: loader.cm.PredicateContext(function (data) {
  3275       assert.strictEqual(data.isEditable, true);
  3276       return true;
  3277     })
  3278   })];
  3280   test.withTestDoc(function (window, doc) {
  3281     test.showMenu(doc.getElementById("textbox"), function (popup) {
  3282       test.checkMenu(items, [], []);
  3283       test.done();
  3284     });
  3285   });
  3286 };
  3288 // Test that the data object is showing editable correctly for readonly text inputs
  3289 exports.testPredicateContextReadonlyTextBoxIsNotEditable = function (assert, done) {
  3290   let test = new TestHelper(assert, done);
  3291   let loader = test.newLoader();
  3293   let items = [loader.cm.Item({
  3294     label: "item",
  3295     context: loader.cm.PredicateContext(function (data) {
  3296       assert.strictEqual(data.isEditable, false);
  3297       return true;
  3298     })
  3299   })];
  3301   test.withTestDoc(function (window, doc) {
  3302     test.showMenu(doc.getElementById("readonly-textbox"), function (popup) {
  3303       test.checkMenu(items, [], []);
  3304       test.done();
  3305     });
  3306   });
  3307 };
  3309 // Test that the data object is showing editable correctly for disabled text inputs
  3310 exports.testPredicateContextDisabledTextBoxIsNotEditable = function (assert, done) {
  3311   let test = new TestHelper(assert, done);
  3312   let loader = test.newLoader();
  3314   let items = [loader.cm.Item({
  3315     label: "item",
  3316     context: loader.cm.PredicateContext(function (data) {
  3317       assert.strictEqual(data.isEditable, false);
  3318       return true;
  3319     })
  3320   })];
  3322   test.withTestDoc(function (window, doc) {
  3323     test.showMenu(doc.getElementById("disabled-textbox"), function (popup) {
  3324       test.checkMenu(items, [], []);
  3325       test.done();
  3326     });
  3327   });
  3328 };
  3330 // Test that the data object is showing editable correctly for text areas
  3331 exports.testPredicateContextTextAreaIsEditable = function (assert, done) {
  3332   let test = new TestHelper(assert, done);
  3333   let loader = test.newLoader();
  3335   let items = [loader.cm.Item({
  3336     label: "item",
  3337     context: loader.cm.PredicateContext(function (data) {
  3338       assert.strictEqual(data.isEditable, true);
  3339       return true;
  3340     })
  3341   })];
  3343   test.withTestDoc(function (window, doc) {
  3344     test.showMenu(doc.getElementById("textfield"), function (popup) {
  3345       test.checkMenu(items, [], []);
  3346       test.done();
  3347     });
  3348   });
  3349 };
  3351 // Test that non-text inputs are not considered editable
  3352 exports.testPredicateContextButtonIsNotEditable = function (assert, done) {
  3353   let test = new TestHelper(assert, done);
  3354   let loader = test.newLoader();
  3356   let items = [loader.cm.Item({
  3357     label: "item",
  3358     context: loader.cm.PredicateContext(function (data) {
  3359       assert.strictEqual(data.isEditable, false);
  3360       return true;
  3361     })
  3362   })];
  3364   test.withTestDoc(function (window, doc) {
  3365     test.showMenu(doc.getElementById("button"), function (popup) {
  3366       test.checkMenu(items, [], []);
  3367       test.done();
  3368     });
  3369   });
  3370 };
  3373 // Test that the data object is showing editable correctly
  3374 exports.testPredicateContextNonInputIsNotEditable = function (assert, done) {
  3375   let test = new TestHelper(assert, done);
  3376   let loader = test.newLoader();
  3378   let items = [loader.cm.Item({
  3379     label: "item",
  3380     context: loader.cm.PredicateContext(function (data) {
  3381       assert.strictEqual(data.isEditable, false);
  3382       return true;
  3383     })
  3384   })];
  3386   test.withTestDoc(function (window, doc) {
  3387     test.showMenu(doc.getElementById("image"), function (popup) {
  3388       test.checkMenu(items, [], []);
  3389       test.done();
  3390     });
  3391   });
  3392 };
  3395 // Test that the data object is showing editable correctly for HTML contenteditable elements
  3396 exports.testPredicateContextEditableElement = function (assert, done) {
  3397   let test = new TestHelper(assert, done);
  3398   let loader = test.newLoader();
  3400   let items = [loader.cm.Item({
  3401     label: "item",
  3402     context: loader.cm.PredicateContext(function (data) {
  3403       assert.strictEqual(data.isEditable, true);
  3404       return true;
  3405     })
  3406   })];
  3408   test.withTestDoc(function (window, doc) {
  3409     test.showMenu(doc.getElementById("editable"), function (popup) {
  3410       test.checkMenu(items, [], []);
  3411       test.done();
  3412     });
  3413   });
  3414 };
  3417 // Test that the data object does not have a selection when there is none
  3418 exports.testPredicateContextNoSelectionInPage = function (assert, done) {
  3419   let test = new TestHelper(assert, done);
  3420   let loader = test.newLoader();
  3422   let items = [loader.cm.Item({
  3423     label: "item",
  3424     context: loader.cm.PredicateContext(function (data) {
  3425       assert.strictEqual(data.selectionText, null);
  3426       return true;
  3427     })
  3428   })];
  3430   test.withTestDoc(function (window, doc) {
  3431     test.showMenu(null, function (popup) {
  3432       test.checkMenu(items, [], []);
  3433       test.done();
  3434     });
  3435   });
  3436 };
  3438 // Test that the data object includes the selected page text
  3439 exports.testPredicateContextSelectionInPage = function (assert, done) {
  3440   let test = new TestHelper(assert, done);
  3441   let loader = test.newLoader();
  3443   let items = [loader.cm.Item({
  3444     label: "item",
  3445     context: loader.cm.PredicateContext(function (data) {
  3446       // since we might get whitespace
  3447       assert.ok(data.selectionText && data.selectionText.search(/^\s*Some text.\s*$/) != -1,
  3448 		'Expected "Some text.", got "' + data.selectionText + '"');
  3449       return true;
  3450     })
  3451   })];
  3453   test.withTestDoc(function (window, doc) {
  3454     window.getSelection().selectAllChildren(doc.getElementById("text"));
  3455     test.showMenu(null, function (popup) {
  3456       test.checkMenu(items, [], []);
  3457       test.done();
  3458     });
  3459   });
  3460 };
  3462 // Test that the data object includes the selected input text
  3463 exports.testPredicateContextSelectionInTextBox = function (assert, done) {
  3464   let test = new TestHelper(assert, done);
  3465   let loader = test.newLoader();
  3467   let items = [loader.cm.Item({
  3468     label: "item",
  3469     context: loader.cm.PredicateContext(function (data) {
  3470       // since we might get whitespace
  3471       assert.strictEqual(data.selectionText, "t v");
  3472       return true;
  3473     })
  3474   })];
  3476   test.withTestDoc(function (window, doc) {
  3477     let textbox = doc.getElementById("textbox");
  3478     textbox.focus();
  3479     textbox.setSelectionRange(3, 6);
  3480     test.showMenu(textbox, function (popup) {
  3481       test.checkMenu(items, [], []);
  3482       test.done();
  3483     });
  3484   });
  3485 };
  3487 // Test that the data object has the correct src for an image
  3488 exports.testPredicateContextTargetSrcSet = function (assert, done) {
  3489   let test = new TestHelper(assert, done);
  3490   let loader = test.newLoader();
  3491   let image;
  3493   let items = [loader.cm.Item({
  3494     label: "item",
  3495     context: loader.cm.PredicateContext(function (data) {
  3496       assert.strictEqual(data.srcURL, image.src);
  3497       return true;
  3498     })
  3499   })];
  3501   test.withTestDoc(function (window, doc) {
  3502     image = doc.getElementById("image");
  3503     test.showMenu(image, function (popup) {
  3504       test.checkMenu(items, [], []);
  3505       test.done();
  3506     });
  3507   });
  3508 };
  3510 // Test that the data object has no src for a link
  3511 exports.testPredicateContextTargetSrcNotSet = function (assert, done) {
  3512   let test = new TestHelper(assert, done);
  3513   let loader = test.newLoader();
  3515   let items = [loader.cm.Item({
  3516     label: "item",
  3517     context: loader.cm.PredicateContext(function (data) {
  3518       assert.strictEqual(data.srcURL, null);
  3519       return true;
  3520     })
  3521   })];
  3523   test.withTestDoc(function (window, doc) {
  3524     test.showMenu(doc.getElementById("link"), function (popup) {
  3525       test.checkMenu(items, [], []);
  3526       test.done();
  3527     });
  3528   });
  3529 };
  3532 // Test that the data object has the correct link set
  3533 exports.testPredicateContextTargetLinkSet = function (assert, done) {
  3534   let test = new TestHelper(assert, done);
  3535   let loader = test.newLoader();
  3536   let image;
  3538   let items = [loader.cm.Item({
  3539     label: "item",
  3540     context: loader.cm.PredicateContext(function (data) {
  3541       assert.strictEqual(data.linkURL, TEST_DOC_URL + "#test");
  3542       return true;
  3543     })
  3544   })];
  3546   test.withTestDoc(function (window, doc) {
  3547     test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
  3548       test.checkMenu(items, [], []);
  3549       test.done();
  3550     });
  3551   });
  3552 };
  3554 // Test that the data object has no link for an image
  3555 exports.testPredicateContextTargetLinkNotSet = function (assert, done) {
  3556   let test = new TestHelper(assert, done);
  3557   let loader = test.newLoader();
  3559   let items = [loader.cm.Item({
  3560     label: "item",
  3561     context: loader.cm.PredicateContext(function (data) {
  3562       assert.strictEqual(data.linkURL, null);
  3563       return true;
  3564     })
  3565   })];
  3567   test.withTestDoc(function (window, doc) {
  3568     test.showMenu(doc.getElementById("image"), function (popup) {
  3569       test.checkMenu(items, [], []);
  3570       test.done();
  3571     });
  3572   });
  3573 };
  3575 // Test that the data object has the value for an input textbox
  3576 exports.testPredicateContextTargetValueSet = function (assert, done) {
  3577   let test = new TestHelper(assert, done);
  3578   let loader = test.newLoader();
  3579   let image;
  3581   let items = [loader.cm.Item({
  3582     label: "item",
  3583     context: loader.cm.PredicateContext(function (data) {
  3584       assert.strictEqual(data.value, "test value");
  3585       return true;
  3586     })
  3587   })];
  3589   test.withTestDoc(function (window, doc) {
  3590     test.showMenu(doc.getElementById("textbox"), function (popup) {
  3591       test.checkMenu(items, [], []);
  3592       test.done();
  3593     });
  3594   });
  3595 };
  3597 // Test that the data object has no value for an image
  3598 exports.testPredicateContextTargetValueNotSet = function (assert, done) {
  3599   let test = new TestHelper(assert, done);
  3600   let loader = test.newLoader();
  3602   let items = [loader.cm.Item({
  3603     label: "item",
  3604     context: loader.cm.PredicateContext(function (data) {
  3605       assert.strictEqual(data.value, null);
  3606       return true;
  3607     })
  3608   })];
  3610   test.withTestDoc(function (window, doc) {
  3611     test.showMenu(doc.getElementById("image"), function (popup) {
  3612       test.checkMenu(items, [], []);
  3613       test.done();
  3614     });
  3615   });
  3616 };
  3619 // NO TESTS BELOW THIS LINE! ///////////////////////////////////////////////////
  3621 // This makes it easier to run tests by handling things like opening the menu,
  3622 // opening new windows, making assertions, etc.  Methods on |test| can be called
  3623 // on instances of this class.  Don't forget to call done() to end the test!
  3624 // WARNING: This looks up items in popups by comparing labels, so don't give two
  3625 // items the same label.
  3626 function TestHelper(assert, done) {
  3627   this.assert = assert;
  3628   this.end = done;
  3629   this.loaders = [];
  3630   this.browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
  3631                        getService(Ci.nsIWindowMediator).
  3632                        getMostRecentWindow("navigator:browser");
  3633   this.overflowThreshValue = require("sdk/preferences/service").
  3634                              get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
  3637 TestHelper.prototype = {
  3638   get contextMenuPopup() {
  3639     return this.browserWindow.document.getElementById("contentAreaContextMenu");
  3640   },
  3642   get contextMenuSeparator() {
  3643     return this.browserWindow.document.querySelector("." + SEPARATOR_CLASS);
  3644   },
  3646   get overflowPopup() {
  3647     return this.browserWindow.document.querySelector("." + OVERFLOW_POPUP_CLASS);
  3648   },
  3650   get overflowSubmenu() {
  3651     return this.browserWindow.document.querySelector("." + OVERFLOW_MENU_CLASS);
  3652   },
  3654   get tabBrowser() {
  3655     return this.browserWindow.gBrowser;
  3656   },
  3658   // Methods on the wrapped test can be called on this object.
  3659   __noSuchMethod__: function (methodName, args) {
  3660     this.assert[methodName].apply(this.assert, args);
  3661   },
  3663   // Asserts that elt, a DOM element representing item, looks OK.
  3664   checkItemElt: function (elt, item) {
  3665     let itemType = this.getItemType(item);
  3667     switch (itemType) {
  3668     case "Item":
  3669       this.assert.equal(elt.localName, "menuitem",
  3670                             "Item DOM element should be a xul:menuitem");
  3671       if (typeof(item.data) === "string") {
  3672         this.assert.equal(elt.getAttribute("value"), item.data,
  3673                               "Item should have correct data");
  3675       break
  3676     case "Menu":
  3677       this.assert.equal(elt.localName, "menu",
  3678                             "Menu DOM element should be a xul:menu");
  3679       let subPopup = elt.firstChild;
  3680       this.assert.ok(subPopup, "xul:menu should have a child");
  3681       this.assert.equal(subPopup.localName, "menupopup",
  3682                             "xul:menu's first child should be a menupopup");
  3683       break;
  3684     case "Separator":
  3685       this.assert.equal(elt.localName, "menuseparator",
  3686                          "Separator DOM element should be a xul:menuseparator");
  3687       break;
  3690     if (itemType === "Item" || itemType === "Menu") {
  3691       this.assert.equal(elt.getAttribute("label"), item.label,
  3692                             "Item should have correct title");
  3693       if (typeof(item.image) === "string") {
  3694         this.assert.equal(elt.getAttribute("image"), item.image,
  3695                               "Item should have correct image");
  3696         if (itemType === "Menu")
  3697           this.assert.ok(elt.classList.contains("menu-iconic"),
  3698                            "Menus with images should have the correct class")
  3699         else
  3700           this.assert.ok(elt.classList.contains("menuitem-iconic"),
  3701                            "Items with images should have the correct class")
  3703       else {
  3704         this.assert.ok(!elt.getAttribute("image"),
  3705                          "Item should not have image");
  3706         this.assert.ok(!elt.classList.contains("menu-iconic") && !elt.classList.contains("menuitem-iconic"),
  3707                          "The iconic classes should not be present")
  3710   },
  3712   // Asserts that the context menu looks OK given the arguments.  presentItems
  3713   // are items that have been added to the menu.  absentItems are items that
  3714   // shouldn't match the current context.  removedItems are items that have been
  3715   // removed from the menu.
  3716   checkMenu: function (presentItems, absentItems, removedItems) {
  3717     // Count up how many top-level items there are
  3718     let total = 0;
  3719     for (let item of presentItems) {
  3720       if (absentItems.indexOf(item) < 0 && removedItems.indexOf(item) < 0)
  3721         total++;
  3724     let separator = this.contextMenuSeparator;
  3725     if (total == 0) {
  3726       this.assert.ok(!separator || separator.hidden,
  3727                        "separator should not be present");
  3729     else {
  3730       this.assert.ok(separator && !separator.hidden,
  3731                        "separator should be present");
  3734     let mainNodes = this.browserWindow.document.querySelectorAll("#contentAreaContextMenu > ." + ITEM_CLASS);
  3735     let overflowNodes = this.browserWindow.document.querySelectorAll("." + OVERFLOW_POPUP_CLASS + " > ." + ITEM_CLASS);
  3737     this.assert.ok(mainNodes.length == 0 || overflowNodes.length == 0,
  3738                      "Should only see nodes at the top level or in overflow");
  3740     let overflow = this.overflowSubmenu;
  3741     if (this.shouldOverflow(total)) {
  3742       this.assert.ok(overflow && !overflow.hidden,
  3743                        "overflow menu should be present");
  3744       this.assert.equal(mainNodes.length, 0,
  3745                             "should be no items in the main context menu");
  3747     else {
  3748       this.assert.ok(!overflow || overflow.hidden,
  3749                        "overflow menu should not be present");
  3750       // When visible nodes == 0 they could be in overflow or top level
  3751       if (total > 0) {
  3752         this.assert.equal(overflowNodes.length, 0,
  3753                               "should be no items in the overflow context menu");
  3757     // Iterate over wherever the nodes have ended up
  3758     let nodes = mainNodes.length ? mainNodes : overflowNodes;
  3759     this.checkNodes(nodes, presentItems, absentItems, removedItems)
  3760     let pos = 0;
  3761   },
  3763   // Recurses through the item hierarchy of presentItems comparing it to the
  3764   // node hierarchy of nodes. Any items in removedItems will be skipped (so
  3765   // should not exist in the XUL), any items in absentItems must exist and be
  3766   // hidden
  3767   checkNodes: function (nodes, presentItems, absentItems, removedItems) {
  3768     let pos = 0;
  3769     for (let item of presentItems) {
  3770       // Removed items shouldn't be in the list
  3771       if (removedItems.indexOf(item) >= 0)
  3772         continue;
  3774       if (nodes.length <= pos) {
  3775         this.assert.ok(false, "Not enough nodes");
  3776         return;
  3779       let hidden = absentItems.indexOf(item) >= 0;
  3781       this.checkItemElt(nodes[pos], item);
  3782       this.assert.equal(nodes[pos].hidden, hidden,
  3783                             "hidden should be set correctly");
  3785       // The contents of hidden menus doesn't matter so much
  3786       if (!hidden && this.getItemType(item) == "Menu") {
  3787         this.assert.equal(nodes[pos].firstChild.localName, "menupopup",
  3788                               "menu XUL should contain a menupopup");
  3789         this.checkNodes(nodes[pos].firstChild.childNodes, item.items, absentItems, removedItems);
  3792       if (pos > 0)
  3793         this.assert.equal(nodes[pos].previousSibling, nodes[pos - 1],
  3794                               "nodes should all be in the same group");
  3795       pos++;
  3798     this.assert.equal(nodes.length, pos,
  3799                           "should have checked all the XUL nodes");
  3800   },
  3802   // Attaches an event listener to node.  The listener is automatically removed
  3803   // when it's fired (so it's assumed it will fire), and callback is called
  3804   // after a short delay.  Since the module we're testing relies on the same
  3805   // event listeners to do its work, this is to give them a little breathing
  3806   // room before callback runs.  Inside callback |this| is this object.
  3807   // Optionally you can pass a function to test if the event is the event you
  3808   // want.
  3809   delayedEventListener: function (node, event, callback, useCapture, isValid) {
  3810     const self = this;
  3811     node.addEventListener(event, function handler(evt) {
  3812       if (isValid && !isValid(evt))
  3813         return;
  3814       node.removeEventListener(event, handler, useCapture);
  3815       timer.setTimeout(function () {
  3816         try {
  3817           callback.call(self, evt);
  3819         catch (err) {
  3820           self.assert.fail(err);
  3821           self.end();
  3823       }, 20);
  3824     }, useCapture);
  3825   },
  3827   // Call to finish the test.
  3828   done: function () {
  3829     const self = this;
  3830     function commonDone() {
  3831       this.closeTab();
  3833       while (this.loaders.length) {
  3834         this.loaders[0].unload();
  3837       require("sdk/preferences/service").set(OVERFLOW_THRESH_PREF, self.overflowThreshValue);
  3839       this.end();
  3842     function closeBrowserWindow() {
  3843       if (this.oldBrowserWindow) {
  3844         this.delayedEventListener(this.browserWindow, "unload", commonDone,
  3845                                   false);
  3846         this.browserWindow.close();
  3847         this.browserWindow = this.oldBrowserWindow;
  3848         delete this.oldBrowserWindow;
  3850       else {
  3851         commonDone.call(this);
  3853     };
  3855     if (this.contextMenuPopup.state == "closed") {
  3856       closeBrowserWindow.call(this);
  3858     else {
  3859       this.delayedEventListener(this.contextMenuPopup, "popuphidden",
  3860                                 function () closeBrowserWindow.call(this),
  3861                                 false);
  3862       this.contextMenuPopup.hidePopup();
  3864   },
  3866   closeTab: function() {
  3867     if (this.tab) {
  3868       this.tabBrowser.removeTab(this.tab);
  3869       this.tabBrowser.selectedTab = this.oldSelectedTab;
  3870       this.tab = null;
  3872   },
  3874   // Returns the DOM element in popup corresponding to item.
  3875   // WARNING: The element is found by comparing labels, so don't give two items
  3876   // the same label.
  3877   getItemElt: function (popup, item) {
  3878     let nodes = popup.childNodes;
  3879     for (let i = nodes.length - 1; i >= 0; i--) {
  3880       if (this.getItemType(item) === "Separator") {
  3881         if (nodes[i].localName === "menuseparator")
  3882           return nodes[i];
  3884       else if (nodes[i].getAttribute("label") === item.label)
  3885         return nodes[i];
  3887     return null;
  3888   },
  3890   // Returns "Item", "Menu", or "Separator".
  3891   getItemType: function (item) {
  3892     // Could use instanceof here, but that would require accessing the loader
  3893     // that created the item, and I don't want to A) somehow search through the
  3894     // this.loaders list to find it, and B) assume there are any live loaders at
  3895     // all.
  3896     return /^\[object (Item|Menu|Separator)/.exec(item.toString())[1];
  3897   },
  3899   // Returns a wrapper around a new loader: { loader, cm, unload, globalScope }.
  3900   // loader is a Cuddlefish sandboxed loader, cm is the context menu module,
  3901   // globalScope is the context menu module's global scope, and unload is a
  3902   // function that unloads the loader and associated resources.
  3903   newLoader: function () {
  3904     const self = this;
  3905     let loader = Loader(module);
  3906     let wrapper = {
  3907       loader: loader,
  3908       cm: loader.require("sdk/context-menu"),
  3909       globalScope: loader.sandbox("sdk/context-menu"),
  3910       unload: function () {
  3911         loader.unload();
  3912         let idx = self.loaders.indexOf(wrapper);
  3913         if (idx < 0)
  3914           throw new Error("Test error: tried to unload nonexistent loader");
  3915         self.loaders.splice(idx, 1);
  3917     };
  3918     this.loaders.push(wrapper);
  3919     return wrapper;
  3920   },
  3922   // As above but the loader has private-browsing support enabled.
  3923   newPrivateLoader: function() {
  3924     let base = require("@loader/options");
  3926     // Clone current loader's options adding the private-browsing permission
  3927     let options = merge({}, base, {
  3928       metadata: merge({}, base.metadata || {}, {
  3929         permissions: merge({}, base.metadata.permissions || {}, {
  3930           'private-browsing': true
  3931         })
  3932       })
  3933     });
  3935     const self = this;
  3936     let loader = Loader(module, null, options);
  3937     let wrapper = {
  3938       loader: loader,
  3939       cm: loader.require("sdk/context-menu"),
  3940       globalScope: loader.sandbox("sdk/context-menu"),
  3941       unload: function () {
  3942         loader.unload();
  3943         let idx = self.loaders.indexOf(wrapper);
  3944         if (idx < 0)
  3945           throw new Error("Test error: tried to unload nonexistent loader");
  3946         self.loaders.splice(idx, 1);
  3948     };
  3949     this.loaders.push(wrapper);
  3950     return wrapper;
  3951   },
  3953   // Returns true if the count crosses the overflow threshold.
  3954   shouldOverflow: function (count) {
  3955     return count >
  3956            (this.loaders.length ?
  3957             this.loaders[0].loader.require("sdk/preferences/service").
  3958               get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) :
  3959             OVERFLOW_THRESH_DEFAULT);
  3960   },
  3962   // Opens the context menu on the current page.  If targetNode is null, the
  3963   // menu is opened in the top-left corner.  onShowncallback is passed the
  3964   // popup.
  3965   showMenu: function(targetNode, onshownCallback) {
  3966     function sendEvent() {
  3967       this.delayedEventListener(this.browserWindow, "popupshowing",
  3968         function (e) {
  3969           let popup = e.target;
  3970           onshownCallback.call(this, popup);
  3971         }, false);
  3973       let rect = targetNode ?
  3974                  targetNode.getBoundingClientRect() :
  3975                  { left: 0, top: 0, width: 0, height: 0 };
  3976       let contentWin = targetNode ? targetNode.ownerDocument.defaultView
  3977                                   : this.browserWindow.content;
  3978       contentWin.
  3979         QueryInterface(Ci.nsIInterfaceRequestor).
  3980         getInterface(Ci.nsIDOMWindowUtils).
  3981         sendMouseEvent("contextmenu",
  3982                        rect.left + (rect.width / 2),
  3983                        rect.top + (rect.height / 2),
  3984                        2, 1, 0);
  3987     // If a new tab or window has not yet been opened, open a new tab now.  For
  3988     // some reason using the tab already opened when the test starts causes
  3989     // leaks.  See bug 566351 for details.
  3990     if (!targetNode && !this.oldSelectedTab && !this.oldBrowserWindow) {
  3991       this.oldSelectedTab = this.tabBrowser.selectedTab;
  3992       this.tab = this.tabBrowser.addTab("about:blank");
  3993       let browser = this.tabBrowser.getBrowserForTab(this.tab);
  3995       this.delayedEventListener(browser, "load", function () {
  3996         this.tabBrowser.selectedTab = this.tab;
  3997         sendEvent.call(this);
  3998       }, true);
  4000     else
  4001       sendEvent.call(this);
  4002   },
  4004   hideMenu: function(onhiddenCallback) {
  4005     this.delayedEventListener(this.browserWindow, "popuphidden", onhiddenCallback);
  4007     this.contextMenuPopup.hidePopup();
  4008   },
  4010   // Opens a new browser window.  The window will be closed automatically when
  4011   // done() is called.
  4012   withNewWindow: function (onloadCallback) {
  4013     let win = this.browserWindow.OpenBrowserWindow();
  4014     this.delayedEventListener(win, "load", onloadCallback, true);
  4015     this.oldBrowserWindow = this.browserWindow;
  4016     this.browserWindow = win;
  4017   },
  4019   // Opens a new private browser window.  The window will be closed
  4020   // automatically when done() is called.
  4021   withNewPrivateWindow: function (onloadCallback) {
  4022     let win = this.browserWindow.OpenBrowserWindow({private: true});
  4023     this.delayedEventListener(win, "load", onloadCallback, true);
  4024     this.oldBrowserWindow = this.browserWindow;
  4025     this.browserWindow = win;
  4026   },
  4028   // Opens a new tab with our test page in the current window.  The tab will
  4029   // be closed automatically when done() is called.
  4030   withTestDoc: function (onloadCallback) {
  4031     this.oldSelectedTab = this.tabBrowser.selectedTab;
  4032     this.tab = this.tabBrowser.addTab(TEST_DOC_URL);
  4033     let browser = this.tabBrowser.getBrowserForTab(this.tab);
  4035     this.delayedEventListener(browser, "load", function () {
  4036       this.tabBrowser.selectedTab = this.tab;
  4037       onloadCallback.call(this, browser.contentWindow, browser.contentDocument);
  4038     }, true, function(evt) {
  4039       return evt.target.location == TEST_DOC_URL;
  4040     });
  4042 };
  4044 require('sdk/test').run(exports);

mercurial