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/. */
17 <textarea>noodles</textarea>\
21 const URL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML);
23 const FRAME_HTML = "<iframe src='" + URL + "'><iframe>";
24 const FRAME_URL = "data:text/html;charset=utf-8," + encodeURIComponent(FRAME_HTML);
26 const { defer } = require("sdk/core/promise");
27 const tabs = require("sdk/tabs");
28 const { setTabURL } = require("sdk/tabs/utils");
29 const { getActiveTab, getTabContentWindow, closeTab } = require("sdk/tabs/utils")
30 const { getMostRecentBrowserWindow } = require("sdk/window/utils");
31 const { open: openNewWindow, close: closeWindow } = require("sdk/window/helpers");
32 const { Loader } = require("sdk/test/loader");
33 const { setTimeout } = require("sdk/timers");
34 const { Cu } = require("chrome");
35 const { merge } = require("sdk/util/object");
36 const { isPrivate } = require("sdk/private-browsing");
37 const events = require("sdk/system/events");
38 // General purpose utility functions
41 * Opens the url given and return a promise, that will be resolved with the
42 * content window when the document is ready.
44 * I believe this approach could be useful in most of our unit test, that
45 * requires to open a tab and need to access to its content.
47 function open(url, options) {
48 let { promise, resolve } = defer();
50 if (options && typeof(options) === "object") {
52 features: merge({ toolbar: true }, options)
53 }).then(function(chromeWindow) {
54 if (isPrivate(chromeWindow) !== !!options.private)
55 throw new Error("Window should have Private set to " + !!options.private);
57 let tab = getActiveTab(chromeWindow);
59 tab.linkedBrowser.addEventListener("load", function ready(event) {
60 let { document } = getTabContentWindow(tab);
62 if (document.readyState === "complete" && document.URL === url) {
63 this.removeEventListener(event.type, ready);
65 resolve(document.defaultView);
77 onReady: function(tab) {
78 // Unfortunately there is no way to get a XUL Tab from SDK Tab on Firefox,
79 // only on Fennec. We should implement `tabNS` also on Firefox in order
82 // Here we assuming that the most recent browser window is the one we're
83 // doing the test, and the active tab is the one we just opened.
84 let window = getTabContentWindow(getActiveTab(getMostRecentBrowserWindow()));
94 * Close the Active Tab
96 function close(window) {
97 if (window && window.top && typeof(window.top).close === "function") {
100 // Here we assuming that the most recent browser window is the one we're
101 // doing the test, and the active tab is the one we just opened.
102 let tab = getActiveTab(getMostRecentBrowserWindow());
109 * Reload the window given and return a promise, that will be resolved with the
110 * content window after a small delay.
112 function reload(window) {
113 let { promise, resolve } = defer();
115 // Here we assuming that the most recent browser window is the one we're
116 // doing the test, and the active tab is the one we just opened.
117 let tab = tabs.activeTab;
119 tab.once("ready", function () {
123 window.location.reload(true);
128 // Selection's unit test utility function
131 * Returns the frame's window once the document is loaded
133 function getFrameWindow(window) {
134 let { promise, resolve } = defer();
136 let frame = window.frames[0];
137 let { document } = frame;
141 if (document.readyState === "complete")
144 document.addEventListener("readystatechange", function readystate() {
145 if (this.readyState === "complete") {
146 this.removeEventListener("readystatechange", readystate);
156 * Hide the frame in order to destroy the selection object, and show it again
157 * after ~500 msec, to give time to attach the code on `document-shown`
159 * In the process, call `Cu.forgeGC` to ensure that the `document-shown` code
162 function hideAndShowFrame(window) {
163 let { promise, resolve } = defer();
164 let iframe = window.document.querySelector("iframe");
166 iframe.style.display = "none";
168 Cu.schedulePreciseGC(function() {
169 events.on("document-shown", function shown(event) {
170 if (iframe.contentWindow !== event.subject.defaultView)
173 events.off("document-shown", shown);
174 setTimeout(resolve, 0, window);
177 iframe.style.display = "";
184 * Select the first div in the page, adding the range to the selection.
186 function selectFirstDiv(window) {
187 let div = window.document.querySelector("div");
188 let selection = window.getSelection();
189 let range = window.document.createRange();
191 if (selection.rangeCount > 0)
192 selection.removeAllRanges();
194 range.selectNode(div);
195 selection.addRange(range);
201 * Select all divs in the page, adding the ranges to the selection.
203 function selectAllDivs(window) {
204 let divs = window.document.getElementsByTagName("div");
205 let selection = window.getSelection();
207 if (selection.rangeCount > 0)
208 selection.removeAllRanges();
210 for (let i = 0; i < divs.length; i++) {
211 let range = window.document.createRange();
213 range.selectNode(divs[i]);
214 selection.addRange(range);
221 * Select the textarea content
223 function selectTextarea(window) {
224 let selection = window.getSelection();
225 let textarea = window.document.querySelector("textarea");
227 if (selection.rangeCount > 0)
228 selection.removeAllRanges();
230 textarea.setSelectionRange(0, textarea.value.length);
237 * Select the content of the first div
239 function selectContentFirstDiv(window) {
240 let div = window.document.querySelector("div");
241 let selection = window.getSelection();
242 let range = window.document.createRange();
244 if (selection.rangeCount > 0)
245 selection.removeAllRanges();
247 range.selectNodeContents(div);
248 selection.addRange(range);
254 * Dispatch the selection event for the selection listener added by
255 * `nsISelectionPrivate.addSelectionListener`
257 function dispatchSelectionEvent(window) {
258 // We modify the selection in order to dispatch the selection's event, by
259 // contract the selection by one character. So if the text selected is "foo"
261 window.getSelection().modify("extend", "backward", "character");
267 * Dispatch the selection event for the selection listener added by
268 * `window.onselect` / `window.addEventListener`
270 function dispatchOnSelectEvent(window) {
271 let { document } = window;
272 let textarea = document.querySelector("textarea");
273 let event = document.createEvent("UIEvents");
275 event.initUIEvent("select", true, true, window, 1);
277 textarea.dispatchEvent(event);
283 * Creates empty ranges and add them to selections
285 function createEmptySelections(window) {
286 selectAllDivs(window);
288 let selection = window.getSelection();
290 for (let i = 0; i < selection.rangeCount; i++)
291 selection.getRangeAt(i).collapse(true);
296 exports["test No Selection"] = function(assert, done) {
297 let loader = Loader(module);
298 let selection = loader.require("sdk/selection");
300 open(URL).then(function() {
302 assert.equal(selection.isContiguous, false,
303 "selection.isContiguous without selection works.");
305 assert.strictEqual(selection.text, null,
306 "selection.text without selection works.");
308 assert.strictEqual(selection.html, null,
309 "selection.html without selection works.");
311 let selectionCount = 0;
312 for each (let sel in selection)
315 assert.equal(selectionCount, 0,
316 "No iterable selections");
318 }).then(close).then(loader.unload).then(done, assert.fail);
321 exports["test Single DOM Selection"] = function(assert, done) {
322 let loader = Loader(module);
323 let selection = loader.require("sdk/selection");
325 open(URL).then(selectFirstDiv).then(function() {
327 assert.equal(selection.isContiguous, true,
328 "selection.isContiguous with single DOM Selection works.");
330 assert.equal(selection.text, "foo",
331 "selection.text with single DOM Selection works.");
333 assert.equal(selection.html, "<div>foo</div>",
334 "selection.html with single DOM Selection works.");
336 let selectionCount = 0;
337 for each (let sel in selection) {
340 assert.equal(sel.text, "foo",
341 "iterable selection.text with single DOM Selection works.");
343 assert.equal(sel.html, "<div>foo</div>",
344 "iterable selection.html with single DOM Selection works.");
347 assert.equal(selectionCount, 1,
348 "One iterable selection");
350 }).then(close).then(loader.unload).then(done, assert.fail);
353 exports["test Multiple DOM Selection"] = function(assert, done) {
354 let loader = Loader(module);
355 let selection = loader.require("sdk/selection");
357 open(URL).then(selectAllDivs).then(function() {
358 let expectedText = ["foo", "and"];
359 let expectedHTML = ["<div>foo</div>", "<div>and</div>"];
361 assert.equal(selection.isContiguous, false,
362 "selection.isContiguous with multiple DOM Selection works.");
364 assert.equal(selection.text, expectedText[0],
365 "selection.text with multiple DOM Selection works.");
367 assert.equal(selection.html, expectedHTML[0],
368 "selection.html with multiple DOM Selection works.");
370 let selectionCount = 0;
371 for each (let sel in selection) {
372 assert.equal(sel.text, expectedText[selectionCount],
373 "iterable selection.text with multiple DOM Selection works.");
375 assert.equal(sel.html, expectedHTML[selectionCount],
376 "iterable selection.text with multiple DOM Selection works.");
381 assert.equal(selectionCount, 2,
382 "Two iterable selections");
384 }).then(close).then(loader.unload).then(done, assert.fail);
387 exports["test Textarea Selection"] = function(assert, done) {
388 let loader = Loader(module);
389 let selection = loader.require("sdk/selection");
391 open(URL).then(selectTextarea).then(function() {
393 assert.equal(selection.isContiguous, true,
394 "selection.isContiguous with Textarea Selection works.");
396 assert.equal(selection.text, "noodles",
397 "selection.text with Textarea Selection works.");
399 assert.strictEqual(selection.html, null,
400 "selection.html with Textarea Selection works.");
402 let selectionCount = 0;
403 for each (let sel in selection) {
406 assert.equal(sel.text, "noodles",
407 "iterable selection.text with Textarea Selection works.");
409 assert.strictEqual(sel.html, null,
410 "iterable selection.html with Textarea Selection works.");
413 assert.equal(selectionCount, 1,
414 "One iterable selection");
416 }).then(close).then(loader.unload).then(done, assert.fail);
419 exports["test Set Text in Multiple DOM Selection"] = function(assert, done) {
420 let loader = Loader(module);
421 let selection = loader.require("sdk/selection");
423 open(URL).then(selectAllDivs).then(function() {
424 let expectedText = ["bar", "and"];
425 let expectedHTML = ["bar", "<div>and</div>"];
427 selection.text = "bar";
429 assert.equal(selection.text, expectedText[0],
430 "set selection.text with single DOM Selection works.");
432 assert.equal(selection.html, expectedHTML[0],
433 "selection.html with single DOM Selection works.");
435 let selectionCount = 0;
436 for each (let sel in selection) {
438 assert.equal(sel.text, expectedText[selectionCount],
439 "iterable selection.text with multiple DOM Selection works.");
441 assert.equal(sel.html, expectedHTML[selectionCount],
442 "iterable selection.html with multiple DOM Selection works.");
447 assert.equal(selectionCount, 2,
448 "Two iterable selections");
450 }).then(close).then(loader.unload).then(done, assert.fail);
453 exports["test Set HTML in Multiple DOM Selection"] = function(assert, done) {
454 let loader = Loader(module);
455 let selection = loader.require("sdk/selection");
457 open(URL).then(selectAllDivs).then(function() {
458 let html = "<span>b<b>a</b>r</span>";
460 let expectedText = ["bar", "and"];
461 let expectedHTML = [html, "<div>and</div>"];
463 selection.html = html;
465 assert.equal(selection.text, expectedText[0],
466 "set selection.text with DOM Selection works.");
468 assert.equal(selection.html, expectedHTML[0],
469 "selection.html with DOM Selection works.");
471 let selectionCount = 0;
472 for each (let sel in selection) {
474 assert.equal(sel.text, expectedText[selectionCount],
475 "iterable selection.text with multiple DOM Selection works.");
477 assert.equal(sel.html, expectedHTML[selectionCount],
478 "iterable selection.html with multiple DOM Selection works.");
483 assert.equal(selectionCount, 2,
484 "Two iterable selections");
486 }).then(close).then(loader.unload).then(done, assert.fail);
489 exports["test Set HTML as text in Multiple DOM Selection"] = function(assert, done) {
490 let loader = Loader(module);
491 let selection = loader.require("sdk/selection");
493 open(URL).then(selectAllDivs).then(function() {
494 let text = "<span>b<b>a</b>r</span>";
495 let html = "<span>b<b>a</b>r</span>";
497 let expectedText = [text, "and"];
498 let expectedHTML = [html, "<div>and</div>"];
500 selection.text = text;
502 assert.equal(selection.text, expectedText[0],
503 "set selection.text with DOM Selection works.");
505 assert.equal(selection.html, expectedHTML[0],
506 "selection.html with DOM Selection works.");
508 let selectionCount = 0;
509 for each (let sel in selection) {
511 assert.equal(sel.text, expectedText[selectionCount],
512 "iterable selection.text with multiple DOM Selection works.");
514 assert.equal(sel.html, expectedHTML[selectionCount],
515 "iterable selection.html with multiple DOM Selection works.");
520 assert.equal(selectionCount, 2,
521 "Two iterable selections");
523 }).then(close).then(loader.unload).then(done, assert.fail);
526 exports["test Set Text in Textarea Selection"] = function(assert, done) {
527 let loader = Loader(module);
528 let selection = loader.require("sdk/selection");
530 open(URL).then(selectTextarea).then(function() {
534 selection.text = text;
536 assert.equal(selection.text, text,
537 "set selection.text with Textarea Selection works.");
539 assert.strictEqual(selection.html, null,
540 "selection.html with Textarea Selection works.");
542 let selectionCount = 0;
543 for each (let sel in selection) {
546 assert.equal(sel.text, text,
547 "iterable selection.text with Textarea Selection works.");
549 assert.strictEqual(sel.html, null,
550 "iterable selection.html with Textarea Selection works.");
553 assert.equal(selectionCount, 1,
554 "One iterable selection");
556 }).then(close).then(loader.unload).then(done, assert.fail);
559 exports["test Set HTML in Textarea Selection"] = function(assert, done) {
560 let loader = Loader(module);
561 let selection = loader.require("sdk/selection");
563 open(URL).then(selectTextarea).then(function() {
565 let html = "<span>b<b>a</b>r</span>";
567 // Textarea can't have HTML so set `html` property is equals to set `text`
569 selection.html = html;
571 assert.equal(selection.text, html,
572 "set selection.text with Textarea Selection works.");
574 assert.strictEqual(selection.html, null,
575 "selection.html with Textarea Selection works.");
577 let selectionCount = 0;
578 for each (let sel in selection) {
581 assert.equal(sel.text, html,
582 "iterable selection.text with Textarea Selection works.");
584 assert.strictEqual(sel.html, null,
585 "iterable selection.html with Textarea Selection works.");
588 assert.equal(selectionCount, 1,
589 "One iterable selection");
591 }).then(close).then(loader.unload).then(done, assert.fail);
594 exports["test Empty Selections"] = function(assert, done) {
595 let loader = Loader(module);
596 let selection = loader.require("sdk/selection");
598 open(URL).then(createEmptySelections).then(function(){
599 assert.equal(selection.isContiguous, false,
600 "selection.isContiguous with empty selections works.");
602 assert.strictEqual(selection.text, null,
603 "selection.text with empty selections works.");
605 assert.strictEqual(selection.html, null,
606 "selection.html with empty selections works.");
608 let selectionCount = 0;
609 for each (let sel in selection)
612 assert.equal(selectionCount, 0,
613 "No iterable selections");
615 }).then(close).then(loader.unload).then(done, assert.fail);
619 exports["test No Selection Exception"] = function(assert, done) {
620 const NO_SELECTION = /It isn't possible to change the selection/;
622 let loader = Loader(module);
623 let selection = loader.require("sdk/selection");
625 open(URL).then(function() {
627 // We're trying to change a selection when there is no selection
628 assert.throws(function() {
629 selection.text = "bar";
632 assert.throws(function() {
633 selection.html = "bar";
636 }).then(close).then(loader.unload).then(done, assert.fail);
639 exports["test for...of without selections"] = function(assert, done) {
640 let loader = Loader(module);
641 let selection = loader.require("sdk/selection");
643 open(URL).then(function() {
644 let selectionCount = 0;
646 for (let sel of selection)
649 assert.equal(selectionCount, 0,
650 "No iterable selections");
652 }).then(close).then(loader.unload).then(null, function(error) {
653 // iterable are not supported yet in Firefox 16, for example, but
654 // they are in Firefox 17.
655 if (error.message.indexOf("is not iterable") > -1)
656 assert.pass("`iterable` method not supported in this application");
659 }).then(done, assert.fail);
662 exports["test for...of with selections"] = function(assert, done) {
663 let loader = Loader(module);
664 let selection = loader.require("sdk/selection");
666 open(URL).then(selectAllDivs).then(function(){
667 let expectedText = ["foo", "and"];
668 let expectedHTML = ["<div>foo</div>", "<div>and</div>"];
670 let selectionCount = 0;
672 for (let sel of selection) {
673 assert.equal(sel.text, expectedText[selectionCount],
674 "iterable selection.text with for...of works.");
676 assert.equal(sel.html, expectedHTML[selectionCount],
677 "iterable selection.text with for...of works.");
682 assert.equal(selectionCount, 2,
683 "Two iterable selections");
685 }).then(close).then(loader.unload).then(null, function(error) {
686 // iterable are not supported yet in Firefox 16, for example, but
687 // they are in Firefox 17.
688 if (error.message.indexOf("is not iterable") > -1)
689 assert.pass("`iterable` method not supported in this application");
692 }).then(done, assert.fail)
695 exports["test Selection Listener"] = function(assert, done) {
696 let loader = Loader(module);
697 let selection = loader.require("sdk/selection");
699 selection.once("select", function() {
700 assert.equal(selection.text, "fo");
706 open(URL).then(selectContentFirstDiv).
707 then(dispatchSelectionEvent, assert.fail);
710 exports["test Textarea OnSelect Listener"] = function(assert, done) {
711 let loader = Loader(module);
712 let selection = loader.require("sdk/selection");
714 selection.once("select", function() {
715 assert.equal(selection.text, "noodles");
721 open(URL).then(selectTextarea).
722 then(dispatchOnSelectEvent, assert.fail);
725 exports["test Selection listener removed on unload"] = function(assert, done) {
726 let loader = Loader(module);
727 let selection = loader.require("sdk/selection");
729 selection.once("select", function() {
730 assert.fail("Shouldn't be never called");
738 then(selectContentFirstDiv).
739 then(dispatchSelectionEvent).
741 then(done, assert.fail);
744 exports["test Textarea onSelect Listener removed on unload"] = function(assert, done) {
745 let loader = Loader(module);
746 let selection = loader.require("sdk/selection");
748 selection.once("select", function() {
749 assert.fail("Shouldn't be never called");
757 then(selectTextarea).
758 then(dispatchOnSelectEvent).
760 then(done, assert.fail);
764 exports["test Selection Listener on existing document"] = function(assert, done) {
765 let loader = Loader(module);
767 open(URL).then(function(window){
768 let selection = loader.require("sdk/selection");
770 selection.once("select", function() {
771 assert.equal(selection.text, "fo");
778 }).then(selectContentFirstDiv).
779 then(dispatchSelectionEvent, assert.fail);
783 exports["test Textarea OnSelect Listener on existing document"] = function(assert, done) {
784 let loader = Loader(module);
786 open(URL).then(function(window){
787 let selection = loader.require("sdk/selection");
789 selection.once("select", function() {
790 assert.equal(selection.text, "noodles");
797 }).then(selectTextarea).
798 then(dispatchOnSelectEvent, assert.fail);
801 exports["test Selection Listener on document reload"] = function(assert, done) {
802 let loader = Loader(module);
803 let selection = loader.require("sdk/selection");
805 selection.once("select", function() {
806 assert.equal(selection.text, "fo");
814 then(selectContentFirstDiv).
815 then(dispatchSelectionEvent, assert.fail);
818 exports["test Textarea OnSelect Listener on document reload"] = function(assert, done) {
819 let loader = Loader(module);
820 let selection = loader.require("sdk/selection");
822 selection.once("select", function() {
823 assert.equal(selection.text, "noodles");
831 then(selectTextarea).
832 then(dispatchOnSelectEvent, assert.fail);
835 exports["test Selection Listener on frame"] = function(assert, done) {
836 let loader = Loader(module);
837 let selection = loader.require("sdk/selection");
839 selection.once("select", function() {
840 assert.equal(selection.text, "fo");
847 then(hideAndShowFrame).
848 then(getFrameWindow).
849 then(selectContentFirstDiv).
850 then(dispatchSelectionEvent).
851 then(null, assert.fail);
854 exports["test Textarea onSelect Listener on frame"] = function(assert, done) {
855 let loader = Loader(module);
856 let selection = loader.require("sdk/selection");
858 selection.once("select", function() {
859 assert.equal(selection.text, "noodles");
866 then(hideAndShowFrame).
867 then(getFrameWindow).
868 then(selectTextarea).
869 then(dispatchOnSelectEvent).
870 then(null, assert.fail);
874 exports["test PBPW Selection Listener"] = function(assert, done) {
875 let loader = Loader(module);
876 let selection = loader.require("sdk/selection");
878 selection.once("select", function() {
879 assert.fail("Shouldn't be never called");
884 open(URL, {private: true}).
885 then(selectContentFirstDiv).
886 then(dispatchSelectionEvent).
889 then(done, assert.fail);
892 exports["test PBPW Textarea OnSelect Listener"] = function(assert, done) {
893 let loader = Loader(module);
894 let selection = loader.require("sdk/selection");
896 selection.once("select", function() {
897 assert.fail("Shouldn't be never called");
902 open(URL, {private: true}).
903 then(selectTextarea).
904 then(dispatchOnSelectEvent).
907 then(done, assert.fail);
911 exports["test PBPW Single DOM Selection"] = function(assert, done) {
912 let loader = Loader(module);
913 let selection = loader.require("sdk/selection");
915 open(URL, {private: true}).then(selectFirstDiv).then(function(window) {
917 assert.equal(selection.isContiguous, false,
918 "selection.isContiguous with single DOM Selection in PBPW works.");
920 assert.equal(selection.text, null,
921 "selection.text with single DOM Selection in PBPW works.");
923 assert.equal(selection.html, null,
924 "selection.html with single DOM Selection in PBPW works.");
926 let selectionCount = 0;
927 for each (let sel in selection)
930 assert.equal(selectionCount, 0,
931 "No iterable selection in PBPW");
934 }).then(closeWindow).then(loader.unload).then(done, assert.fail);
937 exports["test PBPW Textarea Selection"] = function(assert, done) {
938 let loader = Loader(module);
939 let selection = loader.require("sdk/selection");
941 open(URL, {private: true}).then(selectTextarea).then(function(window) {
943 assert.equal(selection.isContiguous, false,
944 "selection.isContiguous with Textarea Selection in PBPW works.");
946 assert.equal(selection.text, null,
947 "selection.text with Textarea Selection in PBPW works.");
949 assert.strictEqual(selection.html, null,
950 "selection.html with Textarea Selection in PBPW works.");
952 let selectionCount = 0;
953 for each (let sel in selection) {
956 assert.equal(sel.text, null,
957 "iterable selection.text with Textarea Selection in PBPW works.");
959 assert.strictEqual(sel.html, null,
960 "iterable selection.html with Textarea Selection in PBPW works.");
963 assert.equal(selectionCount, 0,
964 "No iterable selection in PBPW");
967 }).then(closeWindow).then(loader.unload).then(done, assert.fail);
970 // TODO: test Selection Listener on long-held connection (Bug 661884)
972 // I didn't find a way to do so with httpd, using `processAsync` I'm able to
973 // Keep the connection but not to flush the buffer to the client in two steps,
974 // that is what I need for this test (e.g. flush "Hello" to the client, makes
975 // selection when the connection is still hold, and check that the listener
976 // is executed before the server send "World" and close the connection).
978 // Because this test is needed to the refactoring of context-menu as well, I
979 // believe we will find a proper solution quickly.
981 exports["test Selection Listener on long-held connection"] = function(assert, done) {
986 // If the platform doesn't support the PBPW, we're replacing PBPW tests
987 if (!require("sdk/private-browsing/utils").isWindowPBSupported) {
988 Object.keys(module.exports).forEach(function(key) {
989 if (key.indexOf("test PBPW") === 0) {
990 module.exports[key] = function Unsupported (assert) {
991 assert.pass("Private Window Per Browsing is not supported on this platform.");
997 require("test").run(exports)