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