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

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:35dda174e765
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';
5
6 let { Cc, Ci } = require("chrome");
7
8 require("sdk/context-menu");
9
10 const { Loader } = require('sdk/test/loader');
11 const timer = require("sdk/timers");
12 const { merge } = require("sdk/util/object");
13
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";
22
23 const TEST_DOC_URL = module.uri.replace(/\.js$/, ".html");
24 const data = require("./fixtures");
25
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();
31
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);
36
37 // Create an item.
38 let item = new loader.cm.Item({ label: "item" });
39
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 };
47
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();
53
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");
58
59 test.showMenu(null, function (popup) {
60
61 // It should be present when the menu is shown.
62 test.checkMenu([item], [], []);
63 popup.hidePopup();
64
65 // Destroy the item. Multiple destroys should be harmless.
66 item.destroy();
67 item.destroy();
68 test.showMenu(null, function (popup) {
69
70 // It should be removed from the menu.
71 test.checkMenu([item], [], [item]);
72 test.done();
73 });
74 });
75 };
76
77
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();
82
83 let item = new loader.cm.Item({ label: "item" });
84 item.destroy();
85 item.destroy();
86
87 test.pass("Destroying an item twice should not cause an error.");
88 test.done();
89 };
90
91
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();
97
98 let item = new loader.cm.Item({
99 label: "item",
100 data: "item",
101 context: loader.cm.SelectorContext("img")
102 });
103
104 test.withTestDoc(function (window, doc) {
105 test.showMenu(doc.getElementById("image"), function (popup) {
106 test.checkMenu([item], [], []);
107 test.done();
108 });
109 });
110 };
111
112
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();
119
120 let item = new loader.cm.Item({
121 label: "item",
122 data: "item",
123 context: loader.cm.SelectorContext("a[href]")
124 });
125
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 };
133
134
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();
141
142 let item = new loader.cm.Item({
143 label: "item",
144 data: "item",
145 context: loader.cm.SelectorContext("img")
146 });
147
148 test.showMenu(null, function (popup) {
149 test.checkMenu([item], [item], []);
150 test.done();
151 });
152 };
153
154
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();
160
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 ];
178
179 test.showMenu(null, function (popup) {
180 test.checkMenu(items, [], []);
181 test.done();
182 });
183 };
184
185
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();
191
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 ];
209
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 };
217
218
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();
223
224 let item = loader.cm.Item({
225 label: "item",
226 context: loader.cm.SelectionContext()
227 });
228
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 };
237
238
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();
244
245 let item = loader.cm.Item({
246 label: "item",
247 context: loader.cm.SelectionContext()
248 });
249
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 };
259
260
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();
266
267 let item = loader.cm.Item({
268 label: "item",
269 context: loader.cm.SelectionContext()
270 });
271
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 };
281
282
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();
288
289 let item = loader.cm.Item({
290 label: "item",
291 context: loader.cm.SelectionContext()
292 });
293
294 test.showMenu(null, function (popup) {
295 test.checkMenu([item], [item], []);
296 test.done();
297 });
298 };
299
300
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();
306
307 let item = loader.cm.Item({
308 label: "item",
309 context: loader.cm.SelectionContext()
310 });
311
312 test.withTestDoc(function (window, doc) {
313 let link = doc.getElementById("targetlink");
314 link.click();
315
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);
321
322 test.showMenu(null, function (popup) {
323 test.checkMenu([item], [], []);
324 popup.hidePopup();
325
326 test.tabBrowser.removeTab(test.tabBrowser.selectedTab);
327 test.tabBrowser.selectedTab = test.tab;
328
329 test.showMenu(null, function (popup) {
330 test.checkMenu([item], [item], []);
331 test.done();
332 });
333 });
334 }, true);
335 });
336 };
337
338
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();
343
344 let item = loader.cm.Item({
345 label: "item",
346 context: loader.cm.SelectionContext()
347 });
348
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 };
358
359
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();
364
365 let item = loader.cm.Item({
366 label: "item",
367 context: loader.cm.SelectionContext()
368 });
369
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 };
378
379
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();
384
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 ];
399
400 test.withTestDoc(function (window, doc) {
401 test.showMenu(null, function (popup) {
402 test.checkMenu(items, [], []);
403 test.done();
404 });
405 });
406 };
407
408
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();
413
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 ];
428
429 test.withTestDoc(function (window, doc) {
430 test.showMenu(null, function (popup) {
431 test.checkMenu(items, items, []);
432 test.done();
433 });
434 });
435 };
436
437
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();
444
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 });
458
459 test.withTestDoc(function (window, doc) {
460 test.showMenu(null, function (popup) {
461 test.checkMenu([item], [item], []);
462
463 item.context.remove(context);
464
465 shouldBeEvaled = true;
466
467 test.hideMenu(function () {
468 test.showMenu(null, function (popup) {
469 test.checkMenu([item], [], []);
470
471 assert.ok(!shouldBeEvaled,
472 "content script should have been evaluated");
473
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 };
486
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();
492
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 });
497
498 test.withTestDoc(function (window, doc) {
499 // Set a flag on the document that the item uses
500 doc.body.setAttribute("showItem", "true");
501
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;
511
512 // Set a flag on the document that the item uses
513 doc.body.setAttribute("showItem", "false");
514
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], []);
520
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 };
533
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();
539
540 let loadExpected = false;
541
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 });
557
558 test.withTestDoc(function (window, doc) {
559 loadExpected = true;
560 test.showMenu(null, function (popup) {
561 assert.ok(!loadExpected, "Should have seen a message");
562
563 test.checkMenu([item], [], []);
564
565 test.closeTab();
566 });
567 });
568 };*/
569
570
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();
576
577 let item = new loader.cm.Item({
578 label: "item",
579 contentScript: 'self.on("context", function () true);'
580 });
581
582 test.showMenu(null, function (popup) {
583 test.checkMenu([item], [], []);
584 test.done();
585 });
586 };
587
588
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();
594
595 let item = new loader.cm.Item({
596 label: "item",
597 contentScript: 'self.on("context", function () false);'
598 });
599
600 test.showMenu(null, function (popup) {
601 test.checkMenu([item], [item], []);
602 test.done();
603 });
604 };
605
606
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();
612
613 let item = new loader.cm.Item({
614 label: "item",
615 contentScript: 'self.on("context", function () {});'
616 });
617
618 test.showMenu(null, function (popup) {
619 test.checkMenu([item], [item], []);
620 test.done();
621 });
622 };
623
624
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();
630
631 let item = new loader.cm.Item({
632 label: "item",
633 contentScript: 'self.on("context", function () "");'
634 });
635
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 };
642
643
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();
649
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 });
658
659 test.showMenu(null, function (popup) {
660 test.checkMenu([item], [], []);
661 test.done();
662 });
663 };
664
665
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();
671
672 let item = new loader.cm.Item({
673 label: "item",
674 contentScript: 'self.on("context", function () false); ' +
675 'self.on("context", function () true);'
676 });
677
678 test.showMenu(null, function (popup) {
679 test.checkMenu([item], [], []);
680 test.done();
681 });
682 };
683
684
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();
690
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 });
696
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 };
703
704
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();
710
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 });
716
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 };
723
724
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();
729
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 });
735
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 };
742
743
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();
749
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 ];
772
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 };
780
781
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();
787
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 ];
810
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 };
818
819
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();
825
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 ];
848
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 };
856
857
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();
863
864 let item = new loader.cm.Item({
865 label: "first label",
866 contentScript: 'self.on("context", function () "second label");'
867 });
868
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 };
876
877
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();
882
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");
893
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 });
899
900 test.showMenu(null, function (popup) {
901 test.checkMenu([item], [], []);
902 test.done();
903 });
904 };
905
906
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;
912
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 });
924
925 test.showMenu(null, function () {
926 if (++callbacks == 2) test.done();
927 });
928 };
929
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();
935
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 });
944
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 };
952
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();
957
958 let ctxt = loader.cm.SelectorContext("img");
959 let item = new loader.cm.Item({
960 label: "item",
961 context: ctxt
962 });
963
964 test.withTestDoc(function (window, doc) {
965 test.showMenu(doc.getElementById("image"), function (popup) {
966
967 // The item should be present at first.
968 test.checkMenu([item], [], []);
969 popup.hidePopup();
970
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 };
980
981
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();
986
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 }
992
993 test.showMenu(null, function (popup) {
994 test.checkMenu(items, [], []);
995 test.done();
996 });
997 };
998
999
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();
1004
1005 let item = new loader.cm.Item({ label: "item" });
1006
1007 test.showMenu(null, function (popup) {
1008
1009 // The menu should contain the item.
1010 test.checkMenu([item], [], []);
1011 popup.hidePopup();
1012
1013 // Unload the module.
1014 loader.unload();
1015 test.showMenu(null, function (popup) {
1016
1017 // The item should be removed from the menu.
1018 test.checkMenu([item], [], [item]);
1019 test.done();
1020 });
1021 });
1022 };
1023
1024
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();
1031
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" });
1035
1036 test.showMenu(null, function (popup) {
1037
1038 // The menu should contain both items.
1039 test.checkMenu([item0, item1], [], []);
1040 popup.hidePopup();
1041
1042 // Unload the first module.
1043 loader0.unload();
1044 test.showMenu(null, function (popup) {
1045
1046 // The first item should be removed from the menu.
1047 test.checkMenu([item0, item1], [], [item0]);
1048 popup.hidePopup();
1049
1050 // Unload the second module.
1051 loader1.unload();
1052 test.showMenu(null, function (popup) {
1053
1054 // Both items should be removed from the menu.
1055 test.checkMenu([item0, item1], [], [item0, item1]);
1056 test.done();
1057 });
1058 });
1059 });
1060 };
1061
1062
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();
1068
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);
1074 }
1075
1076 // Use module 1 to add one item.
1077 let item1 = new loader1.cm.Item({ label: "item 1" });
1078
1079 let allItems = items0.concat(item1);
1080
1081 test.showMenu(null, function (popup) {
1082
1083 // The menu should contain all items in overflow.
1084 test.checkMenu(allItems, [], []);
1085 popup.hidePopup();
1086
1087 // Unload the first module.
1088 loader0.unload();
1089 test.showMenu(null, function (popup) {
1090
1091 // The first items should be removed from the menu, which should not
1092 // overflow.
1093 test.checkMenu(allItems, [], items0);
1094 popup.hidePopup();
1095
1096 // Unload the second module.
1097 loader1.unload();
1098 test.showMenu(null, function (popup) {
1099
1100 // All items should be removed from the menu.
1101 test.checkMenu(allItems, [], allItems);
1102 test.done();
1103 });
1104 });
1105 });
1106 };
1107
1108
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();
1117
1118 let item0 = new loader0.cm.Item({
1119 label: "item 0",
1120 context: loader0.cm.SelectorContext("img")
1121 });
1122
1123 let item1 = new loader1.cm.Item({ label: "item 1" });
1124
1125 test.showMenu(null, function (popup) {
1126
1127 // The menu should contain item1.
1128 test.checkMenu([item0, item1], [item0], []);
1129 popup.hidePopup();
1130
1131 // Unload module 0.
1132 loader0.unload();
1133 test.showMenu(null, function (popup) {
1134
1135 // item0 should be removed from the menu.
1136 test.checkMenu([item0, item1], [], [item0]);
1137 popup.hidePopup();
1138
1139 // Unload module 1.
1140 loader1.unload();
1141 test.showMenu(null, function (popup) {
1142
1143 // Both items should be removed from the menu.
1144 test.checkMenu([item0, item1], [], [item0, item1]);
1145 test.done();
1146 });
1147 });
1148 });
1149 };
1150
1151
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();
1160
1161 let item1 = new loader1.cm.Item({ label: "item 1" });
1162
1163 let item0 = new loader0.cm.Item({
1164 label: "item 0",
1165 context: loader0.cm.SelectorContext("img")
1166 });
1167
1168 test.showMenu(null, function (popup) {
1169
1170 // The menu should contain item1.
1171 test.checkMenu([item0, item1], [item0], []);
1172 popup.hidePopup();
1173
1174 // Unload module 0.
1175 loader0.unload();
1176 test.showMenu(null, function (popup) {
1177
1178 // item0 should be removed from the menu.
1179 test.checkMenu([item0, item1], [], [item0]);
1180 popup.hidePopup();
1181
1182 // Unload module 1.
1183 loader1.unload();
1184 test.showMenu(null, function (popup) {
1185
1186 // Both items should be removed from the menu.
1187 test.checkMenu([item0, item1], [], [item0, item1]);
1188 test.done();
1189 });
1190 });
1191 });
1192 };
1193
1194
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();
1203
1204 let item0 = new loader0.cm.Item({
1205 label: "item 0",
1206 context: loader0.cm.SelectorContext("img")
1207 });
1208
1209 let item1 = new loader1.cm.Item({ label: "item 1" });
1210
1211 test.showMenu(null, function (popup) {
1212
1213 // The menu should contain item1.
1214 test.checkMenu([item0, item1], [item0], []);
1215 popup.hidePopup();
1216
1217 // Unload module 1.
1218 loader1.unload();
1219 test.showMenu(null, function (popup) {
1220
1221 // item1 should be removed from the menu.
1222 test.checkMenu([item0, item1], [item0], [item1]);
1223 popup.hidePopup();
1224
1225 // Unload module 0.
1226 loader0.unload();
1227 test.showMenu(null, function (popup) {
1228
1229 // Both items should be removed from the menu.
1230 test.checkMenu([item0, item1], [], [item0, item1]);
1231 test.done();
1232 });
1233 });
1234 });
1235 };
1236
1237
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();
1246
1247 let item1 = new loader1.cm.Item({ label: "item 1" });
1248
1249 let item0 = new loader0.cm.Item({
1250 label: "item 0",
1251 context: loader0.cm.SelectorContext("img")
1252 });
1253
1254 test.showMenu(null, function (popup) {
1255
1256 // The menu should contain item1.
1257 test.checkMenu([item0, item1], [item0], []);
1258 popup.hidePopup();
1259
1260 // Unload module 1.
1261 loader1.unload();
1262 test.showMenu(null, function (popup) {
1263
1264 // item1 should be removed from the menu.
1265 test.checkMenu([item0, item1], [item0], [item1]);
1266 popup.hidePopup();
1267
1268 // Unload module 0.
1269 loader0.unload();
1270 test.showMenu(null, function (popup) {
1271
1272 // Both items should be removed from the menu.
1273 test.checkMenu([item0, item1], [], [item0, item1]);
1274 test.done();
1275 });
1276 });
1277 });
1278 };
1279
1280
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();
1287
1288 let item = new loader0.cm.Item({ label: "item" });
1289
1290 test.showMenu(null, function (popup) {
1291
1292 // The menu should contain the item.
1293 test.checkMenu([item], [], []);
1294 popup.hidePopup();
1295
1296 // Remove the item.
1297 item.destroy();
1298 test.showMenu(null, function (popup) {
1299
1300 // The item should be removed from the menu.
1301 test.checkMenu([item], [], [item]);
1302 popup.hidePopup();
1303
1304 // Unload module 1.
1305 loader1.unload();
1306 test.showMenu(null, function (popup) {
1307
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 };
1316
1317
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();
1324
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" });
1328
1329 test.showMenu(null, function (popup) {
1330
1331 // The menu should contain both items.
1332 test.checkMenu([item0, item1], [], []);
1333 popup.hidePopup();
1334
1335 let item2 = new loader0.cm.Item({ label: "item 2" });
1336
1337 test.showMenu(null, function (popup) {
1338
1339 // The new item should be grouped with the same items from loader0.
1340 test.checkMenu([item0, item2, item1], [], []);
1341 popup.hidePopup();
1342
1343 let item3 = new loader1.cm.Item({ label: "item 3" });
1344
1345 test.showMenu(null, function (popup) {
1346
1347 // Same again
1348 test.checkMenu([item0, item2, item1, item3], [], []);
1349 test.done();
1350 });
1351 });
1352 });
1353 };
1354
1355
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();
1363
1364 let prefs = loader0.loader.require("sdk/preferences/service");
1365 prefs.set(OVERFLOW_THRESH_PREF, 0);
1366
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" });
1370
1371 test.showMenu(null, function (popup) {
1372
1373 // The menu should contain both items.
1374 test.checkMenu([item0, item1], [], []);
1375 popup.hidePopup();
1376
1377 let item2 = new loader0.cm.Item({ label: "item 2" });
1378
1379 test.showMenu(null, function (popup) {
1380
1381 // The new item should be grouped with the same items from loader0.
1382 test.checkMenu([item0, item2, item1], [], []);
1383 popup.hidePopup();
1384
1385 let item3 = new loader1.cm.Item({ label: "item 3" });
1386
1387 test.showMenu(null, function (popup) {
1388
1389 // Same again
1390 test.checkMenu([item0, item2, item1, item3], [], []);
1391 test.done();
1392 });
1393 });
1394 });
1395 };
1396
1397
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();
1404
1405 let prefs = loader0.loader.require("sdk/preferences/service");
1406 prefs.set(OVERFLOW_THRESH_PREF, 0);
1407
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 });
1414
1415 test.showMenu(null, function (popup) {
1416 // One should be hidden
1417 test.checkMenu([item0, item1], [item1], []);
1418 test.done();
1419 });
1420 };
1421
1422
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();
1429
1430 let prefs = loader0.loader.require("sdk/preferences/service");
1431 prefs.set(OVERFLOW_THRESH_PREF, 0);
1432
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" });
1439
1440 test.showMenu(null, function (popup) {
1441 // One should be hidden
1442 test.checkMenu([item0, item1], [item0], []);
1443 test.done();
1444 });
1445 };
1446
1447
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();
1453
1454 let prefs = loader.loader.require("sdk/preferences/service");
1455 prefs.set(OVERFLOW_THRESH_PREF, 2);
1456
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 ];
1469
1470 test.showMenu(null, function (popup) {
1471 // One should be hidden
1472 test.checkMenu(allItems, [allItems[2]], []);
1473 test.done();
1474 });
1475 };
1476
1477
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();
1484
1485 let prefs = loader0.loader.require("sdk/preferences/service");
1486 prefs.set(OVERFLOW_THRESH_PREF, 2);
1487
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 ];
1504
1505 test.showMenu(null, function (popup) {
1506 // One should be hidden
1507 test.checkMenu(allItems, [allItems[2], allItems[3]], []);
1508 test.done();
1509 });
1510 };
1511
1512
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();
1519
1520 let prefs = loader0.loader.require("sdk/preferences/service");
1521 prefs.set(OVERFLOW_THRESH_PREF, 2);
1522
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 ];
1539
1540 test.showMenu(null, function (popup) {
1541 // One should be hidden
1542 test.checkMenu(allItems, [allItems[1], allItems[3]], []);
1543 test.done();
1544 });
1545 };
1546
1547
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();
1554
1555 let prefs = loader0.loader.require("sdk/preferences/service");
1556 prefs.set(OVERFLOW_THRESH_PREF, 2);
1557
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 ];
1574
1575 test.showMenu(null, function (popup) {
1576 // One should be hidden
1577 test.checkMenu(allItems, [allItems[0], allItems[1]], []);
1578 test.done();
1579 });
1580 };
1581
1582
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();
1588
1589 let prefs = loader.loader.require("sdk/preferences/service");
1590 prefs.set(OVERFLOW_THRESH_PREF, 2);
1591
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 ];
1602
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 ];
1613
1614 let allItems = pItems.concat(aItems);
1615
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();
1621
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();
1626
1627 test.showMenu(null, function (popup) {
1628 // None of the items will be visible
1629 test.checkMenu(allItems, allItems, []);
1630 popup.hidePopup();
1631
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();
1636
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();
1641
1642 test.showMenu(null, function (popup) {
1643 // None of the items will be visible
1644 test.checkMenu(allItems, allItems, []);
1645 popup.hidePopup();
1646
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 };
1660
1661
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();
1666
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();
1681 }
1682 });
1683
1684 test.showMenu(null, function (popup) {
1685 test.checkMenu([item], [], []);
1686 let elt = test.getItemElt(popup, item);
1687
1688 // create a command event
1689 let evt = elt.ownerDocument.createEvent('Event');
1690 evt.initEvent('command', true, true);
1691 elt.dispatchEvent(evt);
1692 });
1693 };
1694
1695
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();
1706
1707 let item = new loader.cm.Item({
1708 label: "submenu item",
1709 data: "submenu item data",
1710 context: loader.cm.SelectorContext("a"),
1711 });
1712
1713 let submenu = new loader.cm.Menu({
1714 label: "submenu",
1715 context: loader.cm.SelectorContext("a"),
1716 items: [item]
1717 });
1718
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 });
1737
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);
1746
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 };
1754
1755
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();
1761
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");
1767 }
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();
1775 }
1776 });
1777
1778 test.showMenu(null, function (popup) {
1779 test.checkMenu([item0, item1], [], []);
1780 let item1Elt = test.getItemElt(popup, item1);
1781
1782 // create a command event
1783 let evt = item1Elt.ownerDocument.createEvent('Event');
1784 evt.initEvent('command', true, true);
1785 item1Elt.dispatchEvent(evt);
1786 });
1787 };
1788
1789
1790
1791
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();
1796
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();
1811 }
1812 });
1813
1814 test.showMenu(null, function (popup) {
1815 test.checkMenu([item], [], []);
1816 let elt = test.getItemElt(popup, item);
1817 elt.click();
1818 });
1819 };
1820
1821
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();
1832
1833 let item = new loader.cm.Item({
1834 label: "submenu item",
1835 data: "submenu item data",
1836 context: loader.cm.SelectorContext("a"),
1837 });
1838
1839 let submenu = new loader.cm.Menu({
1840 label: "submenu",
1841 context: loader.cm.SelectorContext("a"),
1842 items: [item]
1843 });
1844
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 });
1863
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 };
1876
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();
1882
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");
1888 }
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();
1896 }
1897 });
1898
1899 test.showMenu(null, function (popup) {
1900 test.checkMenu([item0, item1], [], []);
1901 let item1Elt = test.getItemElt(popup, item1);
1902 item1Elt.click();
1903 });
1904 };
1905
1906
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();
1911
1912 let menu = new loader.cm.Menu({
1913 label: "submenu",
1914 items: [new loader.cm.Separator()]
1915 });
1916
1917 test.showMenu(null, function (popup) {
1918 test.checkMenu([menu], [], []);
1919 test.done();
1920 });
1921 };
1922
1923
1924 // The parentMenu option should work
1925 exports.testParentMenu = function (assert, done) {
1926 let test = new TestHelper(assert, done);
1927 let loader = test.newLoader();
1928
1929 let menu = new loader.cm.Menu({
1930 label: "submenu",
1931 items: [loader.cm.Item({ label: "item 1" })],
1932 parentMenu: loader.cm.contentContextMenu
1933 });
1934
1935 let item = loader.cm.Item({
1936 label: "item 2",
1937 parentMenu: menu,
1938 });
1939
1940 assert.equal(menu.items[1], item, "Item should be in the sub menu");
1941
1942 test.showMenu(null, function (popup) {
1943 test.checkMenu([menu], [], []);
1944 test.done();
1945 });
1946 };
1947
1948
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();
1953
1954 let item = new loader.cm.Item({ label: "item" });
1955
1956 test.withNewWindow(function () {
1957 test.showMenu(null, function (popup) {
1958 test.checkMenu([item], [], []);
1959 test.done();
1960 });
1961 });
1962 };
1963
1964
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" });
1971
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 };
1984
1985
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();
1990
1991 let item = new loader.cm.Item({ label: "item" });
1992
1993 test.showMenu(null, function (popup) {
1994 test.checkMenu([item], [], []);
1995 popup.hidePopup();
1996
1997 test.withNewPrivateWindow(function () {
1998 test.showMenu(null, function (popup) {
1999 test.checkMenu([], [], []);
2000 test.done();
2001 });
2002 });
2003 });
2004 };
2005
2006
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();
2012
2013 let item = new loader.cm.Item({ label: "item" });
2014
2015 test.showMenu(null, function (popup) {
2016 test.checkMenu([item], [], []);
2017 popup.hidePopup();
2018
2019 test.withNewPrivateWindow(function () {
2020 test.showMenu(null, function (popup) {
2021 test.checkMenu([item], [], []);
2022 test.done();
2023 });
2024 });
2025 });
2026 };
2027
2028
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();
2034
2035 let item = new loader.cm.Item({ label: "item" });
2036
2037 test.showMenu(null, function (popup) {
2038 test.checkMenu([item], [], []);
2039 popup.hidePopup();
2040
2041 loader.unload();
2042
2043 test.withNewPrivateWindow(function () {
2044 test.showMenu(null, function (popup) {
2045 test.checkMenu([], [], []);
2046 test.done();
2047 });
2048 });
2049 });
2050 };
2051
2052
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();
2057
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 }));
2064 }
2065
2066 test.showMenu(null, function (popup) {
2067 test.checkMenu(items, [], []);
2068 test.done();
2069 });
2070 };
2071
2072
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();
2077
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 }));
2084 }
2085
2086 test.showMenu(null, function (popup) {
2087 test.checkMenu(items, [], []);
2088 test.done();
2089 });
2090 };
2091
2092
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();
2098
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);
2105 }
2106 else {
2107 let item = new loader1.cm.Item({ label: "item " + i });
2108 items1.push(item);
2109 }
2110 }
2111 let allItems = items0.concat(items1);
2112
2113 test.showMenu(null, function (popup) {
2114
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) {
2121
2122 // All items should be removed.
2123 test.checkMenu(allItems, [], allItems);
2124 test.done();
2125 });
2126 });
2127 };
2128
2129
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();
2135
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 });
2147
2148 item.on("message", function (data) {
2149 assert.equal(data, "potato", "That's a lot of potatoes!");
2150 test.done();
2151 });
2152
2153 test.showMenu(null, function (popup) {
2154 test.checkMenu([item], [], []);
2155 let elt = test.getItemElt(popup, item);
2156 elt.click();
2157 });
2158 };
2159
2160
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();
2175 }
2176 });
2177 test.showMenu(null, function (popup) {
2178 test.checkMenu([item], [], []);
2179 test.getItemElt(popup, item).click();
2180 });
2181 });
2182 };
2183
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();
2203 }
2204 });
2205 test.showMenu(doc.getElementById("image"), function (popup) {
2206 test.checkMenu([item], [], []);
2207 test.getItemElt(popup, item).click();
2208 });
2209 });
2210 };
2211
2212
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();
2218
2219 let items = [
2220 new loader.cm.Item({ label: "a" }),
2221 new loader.cm.Item({ label: "b" })
2222 ]
2223 items[0].label = "z";
2224 assert.equal(items[0].label, "z");
2225
2226 test.showMenu(null, function (popup) {
2227 test.checkMenu(items, [], []);
2228 test.done();
2229 });
2230 };
2231
2232
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();
2238
2239 let items = [
2240 new loader.cm.Item({ label: "a" }),
2241 new loader.cm.Item({ label: "b" })
2242 ];
2243
2244 test.showMenu(null, function (popup) {
2245 test.checkMenu(items, [], []);
2246 popup.hidePopup();
2247
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 };
2256
2257
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();
2263
2264 let prefs = loader.loader.require("sdk/preferences/service");
2265 prefs.set(OVERFLOW_THRESH_PREF, 0);
2266
2267 let items = [
2268 new loader.cm.Item({ label: "a" }),
2269 new loader.cm.Item({ label: "b" })
2270 ]
2271 items[0].label = "z";
2272 assert.equal(items[0].label, "z");
2273
2274 test.showMenu(null, function (popup) {
2275 test.checkMenu(items, [], []);
2276 test.done();
2277 });
2278 };
2279
2280
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();
2286
2287 let prefs = loader.loader.require("sdk/preferences/service");
2288 prefs.set(OVERFLOW_THRESH_PREF, 0);
2289
2290 let items = [
2291 new loader.cm.Item({ label: "a" }),
2292 new loader.cm.Item({ label: "b" })
2293 ];
2294
2295 test.showMenu(null, function (popup) {
2296 test.checkMenu(items, [], []);
2297 popup.hidePopup();
2298
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 };
2307
2308
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();
2313
2314 let menu = loader.cm.Menu({
2315 label: "menu",
2316 items: [loader.cm.Item({ label: "a" })]
2317 });
2318 menu.items[0].label = "z";
2319
2320 assert.equal(menu.items[0].label, "z");
2321
2322 test.showMenu(null, function (popup) {
2323 test.checkMenu([menu], [], []);
2324 test.done();
2325 });
2326 };
2327
2328
2329 // Menu.addItem() should work.
2330 exports.testMenuAddItem = function (assert, done) {
2331 let test = new TestHelper(assert, done);
2332 let loader = test.newLoader();
2333
2334 let menu = loader.cm.Menu({
2335 label: "menu",
2336 items: [
2337 loader.cm.Item({ label: "item 0" })
2338 ]
2339 });
2340 menu.addItem(loader.cm.Item({ label: "item 1" }));
2341 menu.addItem(loader.cm.Item({ label: "item 2" }));
2342
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");
2350 }
2351
2352 test.showMenu(null, function (popup) {
2353 test.checkMenu([menu], [], []);
2354 test.done();
2355 });
2356 };
2357
2358
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();
2363
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);
2372
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");
2378 }
2379
2380 test.showMenu(null, function (popup) {
2381 test.checkMenu([menu], [], []);
2382 test.done();
2383 });
2384 };
2385
2386
2387 // Menu.removeItem() should work.
2388 exports.testMenuRemoveItem = function (assert, done) {
2389 let test = new TestHelper(assert, done);
2390 let loader = test.newLoader();
2391
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" })
2399 ]
2400 });
2401
2402 // Removing twice should be harmless.
2403 menu.removeItem(subitem);
2404 menu.removeItem(subitem);
2405
2406 assert.equal(subitem.parentMenu, null,
2407 "item's parent menu should be correct");
2408
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");
2415
2416 test.showMenu(null, function (popup) {
2417 test.checkMenu([menu], [], []);
2418 test.done();
2419 });
2420 };
2421
2422
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();
2427
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);
2438
2439 assert.equal(menu0.items.length, 0,
2440 "menu should have correct number of items");
2441
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");
2446
2447 assert.equal(subitem.parentMenu, menu1,
2448 "item's parent menu should be correct");
2449
2450 test.showMenu(null, function (popup) {
2451 test.checkMenu([menu0, menu1], [menu0], []);
2452 test.done();
2453 });
2454 };
2455
2456
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();
2461
2462 let subitem = loader.cm.Item({ label: "item" });
2463 let menu = loader.cm.Menu({
2464 label: "menu",
2465 items: [subitem]
2466 });
2467 subitem.destroy();
2468
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");
2473
2474 test.showMenu(null, function (popup) {
2475 test.checkMenu([menu], [menu], []);
2476 test.done();
2477 });
2478 };
2479
2480
2481 // Setting Menu.items should work.
2482 exports.testMenuItemsSetter = function (assert, done) {
2483 let test = new TestHelper(assert, done);
2484 let loader = test.newLoader();
2485
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" })
2491 ]
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 ];
2498
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");
2506 }
2507
2508 test.showMenu(null, function (popup) {
2509 test.checkMenu([menu], [], []);
2510 test.done();
2511 });
2512 };
2513
2514
2515 // Setting Item.data should work.
2516 exports.testItemDataSetter = function (assert, done) {
2517 let test = new TestHelper(assert, done);
2518 let loader = test.newLoader();
2519
2520 let item = loader.cm.Item({ label: "old item 0", data: "old" });
2521 item.data = "new";
2522
2523 assert.equal(item.data, "new", "item should have correct data");
2524
2525 test.showMenu(null, function (popup) {
2526 test.checkMenu([item], [], []);
2527 test.done();
2528 });
2529 };
2530
2531
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 };
2547
2548
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();
2553
2554 try {
2555 new loader.cm.Item({});
2556 assert.ok(false, "Should have seen exception");
2557 }
2558 catch (e) {
2559 assert.ok(true, "Should have seen exception");
2560 }
2561
2562 try {
2563 new loader.cm.Item({ label: null });
2564 assert.ok(false, "Should have seen exception");
2565 }
2566 catch (e) {
2567 assert.ok(true, "Should have seen exception");
2568 }
2569
2570 try {
2571 new loader.cm.Item({ label: undefined });
2572 assert.ok(false, "Should have seen exception");
2573 }
2574 catch (e) {
2575 assert.ok(true, "Should have seen exception");
2576 }
2577
2578 try {
2579 new loader.cm.Item({ label: "" });
2580 assert.ok(false, "Should have seen exception");
2581 }
2582 catch (e) {
2583 assert.ok(true, "Should have seen exception");
2584 }
2585
2586 test.done();
2587 }
2588
2589
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();
2594
2595 function checkData(data) {
2596 assert.equal(data, undefined, "Data should be undefined");
2597 }
2598
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 });
2616
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");
2620
2621 test.showMenu(null, function (popup) {
2622 test.checkMenu([item1, item2, item3], [], []);
2623
2624 let itemElt = test.getItemElt(popup, item1);
2625 itemElt.click();
2626
2627 test.hideMenu(function() {
2628 test.showMenu(null, function (popup) {
2629 let itemElt = test.getItemElt(popup, item2);
2630 itemElt.click();
2631
2632 test.hideMenu(function() {
2633 test.showMenu(null, function (popup) {
2634 let itemElt = test.getItemElt(popup, item3);
2635 itemElt.click();
2636
2637 test.done();
2638 });
2639 });
2640 });
2641 });
2642 });
2643 }
2644
2645
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();
2650
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 });
2654
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");
2658
2659 test.showMenu(null, function (popup) {
2660 test.checkMenu([item1, item2, item3], [], []);
2661
2662 test.done();
2663 });
2664 }
2665
2666
2667 // Test image support.
2668 exports.testItemImage = function (assert, done) {
2669 let test = new TestHelper(assert, done);
2670 let loader = test.newLoader();
2671
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");
2679
2680 test.showMenu(null, function (popup) {
2681 test.checkMenu([item, menu], [], []);
2682
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], [], []);
2689
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], [], []);
2695
2696 test.done();
2697 });
2698 };
2699
2700 // Test image URL validation.
2701 exports.testItemImageValidURL = function (assert, done) {
2702 let test = new TestHelper(assert, done);
2703 let loader = test.newLoader();
2704
2705 assert.throws(function(){
2706 new loader.cm.Item({
2707 label: "item 1",
2708 image: "foo"
2709 })
2710 }, /Image URL validation failed/
2711 );
2712
2713 assert.throws(function(){
2714 new loader.cm.Item({
2715 label: "item 2",
2716 image: false
2717 })
2718 }, /Image URL validation failed/
2719 );
2720
2721 assert.throws(function(){
2722 new loader.cm.Item({
2723 label: "item 3",
2724 image: 0
2725 })
2726 }, /Image URL validation failed/
2727 );
2728
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 });
2733
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");
2737
2738 test.done();
2739 };
2740
2741
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();
2746
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" })
2757 ]
2758 }),
2759 loader.cm.Item({ label: "item 2" })
2760 ]
2761 });
2762 menu.destroy();
2763
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.");*/
2770
2771 test.showMenu(null, function (popup) {
2772 test.checkMenu([menu], [], [menu]);
2773 test.done();
2774 });
2775 };
2776
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();
2782
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 })
2791 ]
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 })
2800 ]
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 })
2813 ]
2814 })
2815 ];
2816
2817 test.showMenu(null, function (popup) {
2818 test.checkMenu(items, items, []);
2819 test.done();
2820 });
2821 };
2822
2823
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();
2829
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 ];
2840
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 })
2849 ]
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 })
2858 ]
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 })
2868 ]
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]
2878 ]
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 })
2891 ]
2892 })
2893 ];
2894
2895 test.withTestDoc(function (window, doc) {
2896 test.showMenu(null, function (popup) {
2897 test.checkMenu(items, hiddenItems, []);
2898 test.done();
2899 });
2900 });
2901 };
2902
2903
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();
2908
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 })
2925 ]
2926 })
2927 ];
2928
2929 // subitem 3 will be hidden
2930 let hiddenItems = [items[0].items[2]];
2931
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 };
2939
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();
2945
2946 let state = 0;
2947
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++;
2966 }
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");
2979
2980 test.done();
2981 }
2982 })
2983 ];
2984
2985 test.withTestDoc(function (window, doc) {
2986 test.showMenu(null, function (popup) {
2987 test.checkMenu(items, [], []);
2988
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 };
2996
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();
3002
3003 let state = 0;
3004
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++;
3023 }
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++
3037
3038 test.done();
3039 }
3040 })
3041 ];
3042
3043 test.withTestDoc(function (window, doc) {
3044 test.showMenu(null, function (popup) {
3045 test.checkMenu(items, [], []);
3046
3047 let topMenuElt = test.getItemElt(popup, items[0]);
3048 let topMenuPopup = topMenuElt.firstChild;
3049 let itemElt = test.getItemElt(topMenuPopup, items[0].items[0]);
3050
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 };
3058
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();
3064
3065 let state = 0;
3066
3067 let items = [
3068 loader.cm.Item({
3069 label: "test item",
3070 context: loader.cm.SelectionContext()
3071 })
3072 ];
3073
3074 test.withTestDoc(function (window, doc) {
3075 let frame = doc.getElementById("iframe");
3076 frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body);
3077
3078 test.showMenu(null, function (popup) {
3079 test.checkMenu(items, items, []);
3080 test.done();
3081 });
3082 });
3083 };
3084
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();
3090
3091 let state = 0;
3092
3093 let items = [
3094 loader.cm.Item({
3095 label: "test item",
3096 context: loader.cm.SelectionContext()
3097 })
3098 ];
3099
3100 test.withTestDoc(function (window, doc) {
3101 let frame = doc.getElementById("iframe");
3102 frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body);
3103
3104 test.showMenu(frame.contentDocument.getElementById("text"), function (popup) {
3105 test.checkMenu(items, [], []);
3106 test.done();
3107 });
3108 });
3109 };
3110
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();
3116
3117 let state = 0;
3118
3119 let items = [
3120 loader.cm.Item({
3121 label: "test item",
3122 context: loader.cm.SelectionContext()
3123 })
3124 ];
3125
3126 test.withTestDoc(function (window, doc) {
3127 let frame = doc.getElementById("iframe");
3128 window.getSelection().selectAllChildren(doc.body);
3129
3130 test.showMenu(frame.contentDocument.getElementById("text"), function (popup) {
3131 test.checkMenu(items, items, []);
3132 test.done();
3133 });
3134 });
3135 };
3136
3137
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();
3143
3144 let itemTrue = loader.cm.Item({
3145 label: "visible",
3146 context: loader.cm.PredicateContext(function () { return true; })
3147 });
3148
3149 let itemFalse = loader.cm.Item({
3150 label: "hidden",
3151 context: loader.cm.PredicateContext(function () { return false; })
3152 });
3153
3154 test.showMenu(null, function (popup) {
3155 test.checkMenu([itemTrue, itemFalse], [itemFalse], []);
3156 test.done();
3157 });
3158 };
3159
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();
3164
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 })];
3172
3173 test.withTestDoc(function (window, doc) {
3174 test.showMenu(null, function (popup) {
3175 test.checkMenu(items, [], []);
3176 test.done();
3177 });
3178 });
3179 };
3180
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();
3185
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 })];
3193
3194 test.withTestDoc(function (window, doc) {
3195 test.showMenu(null, function (popup) {
3196 test.checkMenu(items, [], []);
3197 test.done();
3198 });
3199 });
3200 };
3201
3202
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();
3207
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 })];
3215
3216 test.withTestDoc(function (window, doc) {
3217 test.showMenu(doc.getElementById("button"), function (popup) {
3218 test.checkMenu(items, [], []);
3219 test.done();
3220 });
3221 });
3222 };
3223
3224
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();
3229
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 })];
3237
3238 test.withTestDoc(function (window, doc) {
3239 test.showMenu(doc.getElementById("button"), function (popup) {
3240 test.checkMenu(items, [], []);
3241 test.done();
3242 });
3243 });
3244 };
3245
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();
3250
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 })];
3258
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 };
3266
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();
3271
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 })];
3279
3280 test.withTestDoc(function (window, doc) {
3281 test.showMenu(doc.getElementById("textbox"), function (popup) {
3282 test.checkMenu(items, [], []);
3283 test.done();
3284 });
3285 });
3286 };
3287
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();
3292
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 })];
3300
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 };
3308
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();
3313
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 })];
3321
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 };
3329
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();
3334
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 })];
3342
3343 test.withTestDoc(function (window, doc) {
3344 test.showMenu(doc.getElementById("textfield"), function (popup) {
3345 test.checkMenu(items, [], []);
3346 test.done();
3347 });
3348 });
3349 };
3350
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();
3355
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 })];
3363
3364 test.withTestDoc(function (window, doc) {
3365 test.showMenu(doc.getElementById("button"), function (popup) {
3366 test.checkMenu(items, [], []);
3367 test.done();
3368 });
3369 });
3370 };
3371
3372
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();
3377
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 })];
3385
3386 test.withTestDoc(function (window, doc) {
3387 test.showMenu(doc.getElementById("image"), function (popup) {
3388 test.checkMenu(items, [], []);
3389 test.done();
3390 });
3391 });
3392 };
3393
3394
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();
3399
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 })];
3407
3408 test.withTestDoc(function (window, doc) {
3409 test.showMenu(doc.getElementById("editable"), function (popup) {
3410 test.checkMenu(items, [], []);
3411 test.done();
3412 });
3413 });
3414 };
3415
3416
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();
3421
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 })];
3429
3430 test.withTestDoc(function (window, doc) {
3431 test.showMenu(null, function (popup) {
3432 test.checkMenu(items, [], []);
3433 test.done();
3434 });
3435 });
3436 };
3437
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();
3442
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 })];
3452
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 };
3461
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();
3466
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 })];
3475
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 };
3486
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;
3492
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 })];
3500
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 };
3509
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();
3514
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 })];
3522
3523 test.withTestDoc(function (window, doc) {
3524 test.showMenu(doc.getElementById("link"), function (popup) {
3525 test.checkMenu(items, [], []);
3526 test.done();
3527 });
3528 });
3529 };
3530
3531
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;
3537
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 })];
3545
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 };
3553
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();
3558
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 })];
3566
3567 test.withTestDoc(function (window, doc) {
3568 test.showMenu(doc.getElementById("image"), function (popup) {
3569 test.checkMenu(items, [], []);
3570 test.done();
3571 });
3572 });
3573 };
3574
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;
3580
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 })];
3588
3589 test.withTestDoc(function (window, doc) {
3590 test.showMenu(doc.getElementById("textbox"), function (popup) {
3591 test.checkMenu(items, [], []);
3592 test.done();
3593 });
3594 });
3595 };
3596
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();
3601
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 })];
3609
3610 test.withTestDoc(function (window, doc) {
3611 test.showMenu(doc.getElementById("image"), function (popup) {
3612 test.checkMenu(items, [], []);
3613 test.done();
3614 });
3615 });
3616 };
3617
3618
3619 // NO TESTS BELOW THIS LINE! ///////////////////////////////////////////////////
3620
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);
3635 }
3636
3637 TestHelper.prototype = {
3638 get contextMenuPopup() {
3639 return this.browserWindow.document.getElementById("contentAreaContextMenu");
3640 },
3641
3642 get contextMenuSeparator() {
3643 return this.browserWindow.document.querySelector("." + SEPARATOR_CLASS);
3644 },
3645
3646 get overflowPopup() {
3647 return this.browserWindow.document.querySelector("." + OVERFLOW_POPUP_CLASS);
3648 },
3649
3650 get overflowSubmenu() {
3651 return this.browserWindow.document.querySelector("." + OVERFLOW_MENU_CLASS);
3652 },
3653
3654 get tabBrowser() {
3655 return this.browserWindow.gBrowser;
3656 },
3657
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 },
3662
3663 // Asserts that elt, a DOM element representing item, looks OK.
3664 checkItemElt: function (elt, item) {
3665 let itemType = this.getItemType(item);
3666
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");
3674 }
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;
3688 }
3689
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")
3702 }
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")
3708 }
3709 }
3710 },
3711
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++;
3722 }
3723
3724 let separator = this.contextMenuSeparator;
3725 if (total == 0) {
3726 this.assert.ok(!separator || separator.hidden,
3727 "separator should not be present");
3728 }
3729 else {
3730 this.assert.ok(separator && !separator.hidden,
3731 "separator should be present");
3732 }
3733
3734 let mainNodes = this.browserWindow.document.querySelectorAll("#contentAreaContextMenu > ." + ITEM_CLASS);
3735 let overflowNodes = this.browserWindow.document.querySelectorAll("." + OVERFLOW_POPUP_CLASS + " > ." + ITEM_CLASS);
3736
3737 this.assert.ok(mainNodes.length == 0 || overflowNodes.length == 0,
3738 "Should only see nodes at the top level or in overflow");
3739
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");
3746 }
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");
3754 }
3755 }
3756
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 },
3762
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;
3773
3774 if (nodes.length <= pos) {
3775 this.assert.ok(false, "Not enough nodes");
3776 return;
3777 }
3778
3779 let hidden = absentItems.indexOf(item) >= 0;
3780
3781 this.checkItemElt(nodes[pos], item);
3782 this.assert.equal(nodes[pos].hidden, hidden,
3783 "hidden should be set correctly");
3784
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);
3790 }
3791
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++;
3796 }
3797
3798 this.assert.equal(nodes.length, pos,
3799 "should have checked all the XUL nodes");
3800 },
3801
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);
3818 }
3819 catch (err) {
3820 self.assert.fail(err);
3821 self.end();
3822 }
3823 }, 20);
3824 }, useCapture);
3825 },
3826
3827 // Call to finish the test.
3828 done: function () {
3829 const self = this;
3830 function commonDone() {
3831 this.closeTab();
3832
3833 while (this.loaders.length) {
3834 this.loaders[0].unload();
3835 }
3836
3837 require("sdk/preferences/service").set(OVERFLOW_THRESH_PREF, self.overflowThreshValue);
3838
3839 this.end();
3840 }
3841
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;
3849 }
3850 else {
3851 commonDone.call(this);
3852 }
3853 };
3854
3855 if (this.contextMenuPopup.state == "closed") {
3856 closeBrowserWindow.call(this);
3857 }
3858 else {
3859 this.delayedEventListener(this.contextMenuPopup, "popuphidden",
3860 function () closeBrowserWindow.call(this),
3861 false);
3862 this.contextMenuPopup.hidePopup();
3863 }
3864 },
3865
3866 closeTab: function() {
3867 if (this.tab) {
3868 this.tabBrowser.removeTab(this.tab);
3869 this.tabBrowser.selectedTab = this.oldSelectedTab;
3870 this.tab = null;
3871 }
3872 },
3873
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];
3883 }
3884 else if (nodes[i].getAttribute("label") === item.label)
3885 return nodes[i];
3886 }
3887 return null;
3888 },
3889
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 },
3898
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);
3916 }
3917 };
3918 this.loaders.push(wrapper);
3919 return wrapper;
3920 },
3921
3922 // As above but the loader has private-browsing support enabled.
3923 newPrivateLoader: function() {
3924 let base = require("@loader/options");
3925
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 });
3934
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);
3947 }
3948 };
3949 this.loaders.push(wrapper);
3950 return wrapper;
3951 },
3952
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 },
3961
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);
3972
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);
3985 }
3986
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);
3994
3995 this.delayedEventListener(browser, "load", function () {
3996 this.tabBrowser.selectedTab = this.tab;
3997 sendEvent.call(this);
3998 }, true);
3999 }
4000 else
4001 sendEvent.call(this);
4002 },
4003
4004 hideMenu: function(onhiddenCallback) {
4005 this.delayedEventListener(this.browserWindow, "popuphidden", onhiddenCallback);
4006
4007 this.contextMenuPopup.hidePopup();
4008 },
4009
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 },
4018
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 },
4027
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);
4034
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 });
4041 }
4042 };
4043
4044 require('sdk/test').run(exports);

mercurial