test-selection.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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. const HTML = "<html>\
  6. <body>\
  7. <div>foo</div>\
  8. <div>and</div>\
  9. <textarea>noodles</textarea>\
  10. </body>\
  11. </html>";
  12. const URL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML);
  13. const FRAME_HTML = "<iframe src='" + URL + "'><iframe>";
  14. const FRAME_URL = "data:text/html;charset=utf-8," + encodeURIComponent(FRAME_HTML);
  15. const { defer } = require("sdk/core/promise");
  16. const { browserWindows } = require("sdk/windows");
  17. const tabs = require("sdk/tabs");
  18. const { setTabURL, getActiveTab, getTabContentWindow, closeTab, getTabs,
  19. getTabTitle } = require("sdk/tabs/utils");
  20. const { getMostRecentBrowserWindow, isFocused } = require("sdk/window/utils");
  21. const { open: openNewWindow, close: closeWindow, focus } = require("sdk/window/helpers");
  22. const { Loader } = require("sdk/test/loader");
  23. const { merge } = require("sdk/util/object");
  24. const { isPrivate } = require("sdk/private-browsing");
  25. // General purpose utility functions
  26. /**
  27. * Opens the url given and return a promise, that will be resolved with the
  28. * content window when the document is ready.
  29. *
  30. * I believe this approach could be useful in most of our unit test, that
  31. * requires to open a tab and need to access to its content.
  32. */
  33. function open(url, options) {
  34. let { promise, resolve } = defer();
  35. if (options && typeof(options) === "object") {
  36. openNewWindow("", {
  37. features: merge({ toolbar: true }, options)
  38. }).then(function(chromeWindow) {
  39. if (isPrivate(chromeWindow) !== !!options.private)
  40. throw new Error("Window should have Private set to " + !!options.private);
  41. let tab = getActiveTab(chromeWindow);
  42. tab.linkedBrowser.addEventListener("load", function ready(event) {
  43. let { document } = getTabContentWindow(tab);
  44. if (document.readyState === "complete" && document.URL === url) {
  45. this.removeEventListener(event.type, ready);
  46. if (options.title)
  47. document.title = options.title;
  48. resolve(document.defaultView);
  49. }
  50. }, true);
  51. setTabURL(tab, url);
  52. });
  53. return promise;
  54. };
  55. tabs.open({
  56. url: url,
  57. onReady: function(tab) {
  58. // Unfortunately there is no way to get a XUL Tab from SDK Tab on Firefox,
  59. // only on Fennec. We should implement `tabNS` also on Firefox in order
  60. // to have that.
  61. // Here we assuming that the most recent browser window is the one we're
  62. // doing the test, and the active tab is the one we just opened.
  63. let window = getTabContentWindow(getActiveTab(getMostRecentBrowserWindow()));
  64. resolve(window);
  65. }
  66. });
  67. return promise;
  68. };
  69. /**
  70. * Close the Active Tab
  71. */
  72. function close(window) {
  73. let { promise, resolve } = defer();
  74. if (window && typeof(window.close) === "function") {
  75. closeWindow(window).then(function() resolve());
  76. }
  77. else {
  78. // Here we assuming that the most recent browser window is the one we're
  79. // doing the test, and the active tab is the one we just opened.
  80. closeTab(getActiveTab(getMostRecentBrowserWindow()));
  81. resolve();
  82. }
  83. return promise;
  84. }
  85. /**
  86. * Reload the window given and return a promise, that will be resolved with the
  87. * content window after a small delay.
  88. */
  89. function reload(window) {
  90. let { promise, resolve } = defer();
  91. // Here we assuming that the most recent browser window is the one we're
  92. // doing the test, and the active tab is the one we just opened.
  93. let tab = tabs.activeTab;
  94. tab.once("ready", function () {
  95. resolve(window);
  96. });
  97. window.location.reload(true);
  98. return promise;
  99. }
  100. // Selection's unit test utility function
  101. /**
  102. * Select the first div in the page, adding the range to the selection.
  103. */
  104. function selectFirstDiv(window) {
  105. let div = window.document.querySelector("div");
  106. let selection = window.getSelection();
  107. let range = window.document.createRange();
  108. if (selection.rangeCount > 0)
  109. selection.removeAllRanges();
  110. range.selectNode(div);
  111. selection.addRange(range);
  112. return window;
  113. }
  114. /**
  115. * Select all divs in the page, adding the ranges to the selection.
  116. */
  117. function selectAllDivs(window) {
  118. let divs = window.document.getElementsByTagName("div");
  119. let selection = window.getSelection();
  120. if (selection.rangeCount > 0)
  121. selection.removeAllRanges();
  122. for (let i = 0; i < divs.length; i++) {
  123. let range = window.document.createRange();
  124. range.selectNode(divs[i]);
  125. selection.addRange(range);
  126. }
  127. return window;
  128. }
  129. /**
  130. * Select the textarea content
  131. */
  132. function selectTextarea(window) {
  133. let selection = window.getSelection();
  134. let textarea = window.document.querySelector("textarea");
  135. if (selection.rangeCount > 0)
  136. selection.removeAllRanges();
  137. textarea.setSelectionRange(0, textarea.value.length);
  138. textarea.focus();
  139. return window;
  140. }
  141. /**
  142. * Select the content of the first div
  143. */
  144. function selectContentFirstDiv(window) {
  145. let div = window.document.querySelector("div");
  146. let selection = window.getSelection();
  147. let range = window.document.createRange();
  148. if (selection.rangeCount > 0)
  149. selection.removeAllRanges();
  150. range.selectNodeContents(div);
  151. selection.addRange(range);
  152. return window;
  153. }
  154. /**
  155. * Dispatch the selection event for the selection listener added by
  156. * `nsISelectionPrivate.addSelectionListener`
  157. */
  158. function dispatchSelectionEvent(window) {
  159. // We modify the selection in order to dispatch the selection's event, by
  160. // contract the selection by one character. So if the text selected is "foo"
  161. // will be "fo".
  162. window.getSelection().modify("extend", "backward", "character");
  163. return window;
  164. }
  165. /**
  166. * Dispatch the selection event for the selection listener added by
  167. * `window.onselect` / `window.addEventListener`
  168. */
  169. function dispatchOnSelectEvent(window) {
  170. let { document } = window;
  171. let textarea = document.querySelector("textarea");
  172. let event = document.createEvent("UIEvents");
  173. event.initUIEvent("select", true, true, window, 1);
  174. textarea.dispatchEvent(event);
  175. return window;
  176. }
  177. // Test cases
  178. exports["test PWPB Selection Listener"] = function(assert, done) {
  179. let loader = Loader(module);
  180. let selection = loader.require("sdk/selection");
  181. open(URL, {private: true, title: "PWPB Selection Listener"}).
  182. then(function(window) {
  183. selection.once("select", function() {
  184. assert.equal(browserWindows.length, 2, "there should be only two windows open.");
  185. assert.equal(getTabs().length, 2, "there should be only two tabs open: '" +
  186. getTabs().map(function(tab) getTabTitle(tab)).join("', '") +
  187. "'."
  188. );
  189. // window should be focused, but force the focus anyhow.. see bug 841823
  190. focus(window).then(function() {
  191. // check state of window
  192. assert.ok(isFocused(window), "the window is focused");
  193. assert.ok(isPrivate(window), "the window should be a private window");
  194. assert.equal(selection.text, "fo");
  195. close(window).
  196. then(loader.unload).
  197. then(done, assert.fail);
  198. });
  199. });
  200. return window;
  201. }).
  202. then(selectContentFirstDiv).
  203. then(dispatchSelectionEvent).
  204. then(null, assert.fail);
  205. };
  206. exports["test PWPB Textarea OnSelect Listener"] = function(assert, done) {
  207. let loader = Loader(module);
  208. let selection = loader.require("sdk/selection");
  209. open(URL, {private: true, title: "PWPB OnSelect Listener"}).
  210. then(function(window) {
  211. selection.once("select", function() {
  212. assert.equal(browserWindows.length, 2, "there should be only two windows open.");
  213. assert.equal(getTabs().length, 2, "there should be only two tabs open: '" +
  214. getTabs().map(function(tab) getTabTitle(tab)).join("', '") +
  215. "'."
  216. );
  217. // window should be focused, but force the focus anyhow.. see bug 841823
  218. focus(window).then(function() {
  219. assert.equal(selection.text, "noodles");
  220. close(window).
  221. then(loader.unload).
  222. then(done, assert.fail);
  223. });
  224. });
  225. return window;
  226. }).
  227. then(selectTextarea).
  228. then(dispatchOnSelectEvent).
  229. then(null, assert.fail);
  230. };
  231. exports["test PWPB Single DOM Selection"] = function(assert, done) {
  232. let loader = Loader(module);
  233. let selection = loader.require("sdk/selection");
  234. open(URL, {private: true, title: "PWPB Single DOM Selection"}).
  235. then(selectFirstDiv).
  236. then(focus).then(function() {
  237. assert.equal(selection.isContiguous, true,
  238. "selection.isContiguous with single DOM Selection works.");
  239. assert.equal(selection.text, "foo",
  240. "selection.text with single DOM Selection works.");
  241. assert.equal(selection.html, "<div>foo</div>",
  242. "selection.html with single DOM Selection works.");
  243. let selectionCount = 0;
  244. for each (let sel in selection) {
  245. selectionCount++;
  246. assert.equal(sel.text, "foo",
  247. "iterable selection.text with single DOM Selection works.");
  248. assert.equal(sel.html, "<div>foo</div>",
  249. "iterable selection.html with single DOM Selection works.");
  250. }
  251. assert.equal(selectionCount, 1,
  252. "One iterable selection");
  253. }).then(close).then(loader.unload).then(done, assert.fail);
  254. }
  255. exports["test PWPB Textarea Selection"] = function(assert, done) {
  256. let loader = Loader(module);
  257. let selection = loader.require("sdk/selection");
  258. open(URL, {private: true, title: "PWPB Textarea Listener"}).
  259. then(selectTextarea).
  260. then(focus).
  261. then(function() {
  262. assert.equal(selection.isContiguous, true,
  263. "selection.isContiguous with Textarea Selection works.");
  264. assert.equal(selection.text, "noodles",
  265. "selection.text with Textarea Selection works.");
  266. assert.strictEqual(selection.html, null,
  267. "selection.html with Textarea Selection works.");
  268. let selectionCount = 0;
  269. for each (let sel in selection) {
  270. selectionCount++;
  271. assert.equal(sel.text, "noodles",
  272. "iterable selection.text with Textarea Selection works.");
  273. assert.strictEqual(sel.html, null,
  274. "iterable selection.html with Textarea Selection works.");
  275. }
  276. assert.equal(selectionCount, 1,
  277. "One iterable selection");
  278. }).then(close).then(loader.unload).then(done, assert.fail);
  279. };
  280. exports["test PWPB Set HTML in Multiple DOM Selection"] = function(assert, done) {
  281. let loader = Loader(module);
  282. let selection = loader.require("sdk/selection");
  283. open(URL, {private: true, title: "PWPB Set HTML in Multiple DOM Selection"}).
  284. then(selectAllDivs).
  285. then(focus).
  286. then(function() {
  287. let html = "<span>b<b>a</b>r</span>";
  288. let expectedText = ["bar", "and"];
  289. let expectedHTML = [html, "<div>and</div>"];
  290. selection.html = html;
  291. assert.equal(selection.text, expectedText[0],
  292. "set selection.text with DOM Selection works.");
  293. assert.equal(selection.html, expectedHTML[0],
  294. "selection.html with DOM Selection works.");
  295. let selectionCount = 0;
  296. for each (let sel in selection) {
  297. assert.equal(sel.text, expectedText[selectionCount],
  298. "iterable selection.text with multiple DOM Selection works.");
  299. assert.equal(sel.html, expectedHTML[selectionCount],
  300. "iterable selection.html with multiple DOM Selection works.");
  301. selectionCount++;
  302. }
  303. assert.equal(selectionCount, 2,
  304. "Two iterable selections");
  305. }).then(close).then(loader.unload).then(done, assert.fail);
  306. };
  307. exports["test PWPB Set Text in Textarea Selection"] = function(assert, done) {
  308. let loader = Loader(module);
  309. let selection = loader.require("sdk/selection");
  310. open(URL, {private: true, title: "test PWPB Set Text in Textarea Selection"}).
  311. then(selectTextarea).
  312. then(focus).
  313. then(function() {
  314. let text = "bar";
  315. selection.text = text;
  316. assert.equal(selection.text, text,
  317. "set selection.text with Textarea Selection works.");
  318. assert.strictEqual(selection.html, null,
  319. "selection.html with Textarea Selection works.");
  320. let selectionCount = 0;
  321. for each (let sel in selection) {
  322. selectionCount++;
  323. assert.equal(sel.text, text,
  324. "iterable selection.text with Textarea Selection works.");
  325. assert.strictEqual(sel.html, null,
  326. "iterable selection.html with Textarea Selection works.");
  327. }
  328. assert.equal(selectionCount, 1,
  329. "One iterable selection");
  330. }).then(close).then(loader.unload).then(done, assert.fail);
  331. };
  332. // If the platform doesn't support the PBPW, we're replacing PBPW tests
  333. if (!require("sdk/private-browsing/utils").isWindowPBSupported) {
  334. module.exports = {
  335. "test PBPW Unsupported": function Unsupported (assert) {
  336. assert.pass("Private Window Per Browsing is not supported on this platform.");
  337. }
  338. }
  339. }
  340. // If the module doesn't support the app we're being run in, require() will
  341. // throw. In that case, remove all tests above from exports, and add one dummy
  342. // test that passes.
  343. try {
  344. require("sdk/selection");
  345. }
  346. catch (err) {
  347. if (!/^Unsupported Application/.test(err.message))
  348. throw err;
  349. module.exports = {
  350. "test Unsupported Application": function Unsupported (assert) {
  351. assert.pass(err.message);
  352. }
  353. }
  354. }