1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/browser-element/mochitest/browserElementTestHelpers.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,574 @@ 1.4 +/* Any copyright is dedicated to the public domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +// Helpers for managing the browser frame preferences. 1.8 +"use strict"; 1.9 + 1.10 +function _getPath() { 1.11 + return window.location.pathname 1.12 + .substring(0, window.location.pathname.lastIndexOf('/')) 1.13 + .replace("/priority", ""); 1.14 +} 1.15 + 1.16 +const browserElementTestHelpers = { 1.17 + _getBoolPref: function(pref) { 1.18 + try { 1.19 + return SpecialPowers.getBoolPref(pref); 1.20 + } 1.21 + catch (e) { 1.22 + return undefined; 1.23 + } 1.24 + }, 1.25 + 1.26 + _setPref: function(pref, value) { 1.27 + this.lockTestReady(); 1.28 + if (value !== undefined && value !== null) { 1.29 + SpecialPowers.pushPrefEnv({'set': [[pref, value]]}, this.unlockTestReady.bind(this)); 1.30 + } else { 1.31 + SpecialPowers.pushPrefEnv({'clear': [[pref]]}, this.unlockTestReady.bind(this)); 1.32 + } 1.33 + }, 1.34 + 1.35 + _setPrefs: function() { 1.36 + this.lockTestReady(); 1.37 + SpecialPowers.pushPrefEnv({'set': Array.slice(arguments)}, this.unlockTestReady.bind(this)); 1.38 + }, 1.39 + 1.40 + _testReadyLockCount: 0, 1.41 + _firedTestReady: false, 1.42 + lockTestReady: function() { 1.43 + this._testReadyLockCount++; 1.44 + }, 1.45 + 1.46 + unlockTestReady: function() { 1.47 + this._testReadyLockCount--; 1.48 + if (this._testReadyLockCount == 0 && !this._firedTestReady) { 1.49 + this._firedTestReady = true; 1.50 + dispatchEvent(new Event("testready")); 1.51 + } 1.52 + }, 1.53 + 1.54 + enableProcessPriorityManager: function() { 1.55 + this._setPrefs( 1.56 + ['dom.ipc.processPriorityManager.testMode', true], 1.57 + ['dom.ipc.processPriorityManager.enabled', true], 1.58 + ['dom.ipc.processPriorityManager.backgroundLRUPoolLevels', 2] 1.59 + ); 1.60 + }, 1.61 + 1.62 + setEnabledPref: function(value) { 1.63 + this._setPref('dom.mozBrowserFramesEnabled', value); 1.64 + }, 1.65 + 1.66 + getOOPByDefaultPref: function() { 1.67 + return this._getBoolPref("dom.ipc.browser_frames.oop_by_default"); 1.68 + }, 1.69 + 1.70 + addPermission: function() { 1.71 + SpecialPowers.addPermission("browser", true, document); 1.72 + this.tempPermissions.push(location.href) 1.73 + }, 1.74 + 1.75 + 'tempPermissions': [], 1.76 + addPermissionForUrl: function(url) { 1.77 + SpecialPowers.addPermission("browser", true, url); 1.78 + this.tempPermissions.push(url); 1.79 + }, 1.80 + 1.81 + _observers: [], 1.82 + 1.83 + // This function is a wrapper which lets you register an observer to one of 1.84 + // the process priority manager's test-only topics. observerFn should be a 1.85 + // function which takes (subject, topic, data). 1.86 + // 1.87 + // We'll clean up any observers you add at the end of the test. 1.88 + addProcessPriorityObserver: function(processPriorityTopic, observerFn) { 1.89 + var topic = "process-priority-manager:TEST-ONLY:" + processPriorityTopic; 1.90 + 1.91 + // SpecialPowers appears to require that the observer be an object, not a 1.92 + // function. 1.93 + var observer = { 1.94 + observe: observerFn 1.95 + }; 1.96 + 1.97 + SpecialPowers.addObserver(observer, topic, /* weak = */ false); 1.98 + this._observers.push([observer, topic]); 1.99 + }, 1.100 + 1.101 + cleanUp: function() { 1.102 + for (var i = 0; i < this.tempPermissions.length; i++) { 1.103 + SpecialPowers.removePermission("browser", this.tempPermissions[i]); 1.104 + } 1.105 + 1.106 + for (var i = 0; i < this._observers.length; i++) { 1.107 + SpecialPowers.removeObserver(this._observers[i][0], 1.108 + this._observers[i][1]); 1.109 + } 1.110 + }, 1.111 + 1.112 + // Some basically-empty pages from different domains you can load. 1.113 + 'emptyPage1': 'http://example.com' + _getPath() + '/file_empty.html', 1.114 + 'emptyPage2': 'http://example.org' + _getPath() + '/file_empty.html', 1.115 + 'emptyPage3': 'http://test1.example.org' + _getPath() + '/file_empty.html', 1.116 + 'focusPage': 'http://example.org' + _getPath() + '/file_focus.html', 1.117 +}; 1.118 + 1.119 +// Returns a promise which is resolved when a subprocess is created. The 1.120 +// argument to resolve() is the childID of the subprocess. 1.121 +function expectProcessCreated() { 1.122 + var deferred = Promise.defer(); 1.123 + 1.124 + var observed = false; 1.125 + browserElementTestHelpers.addProcessPriorityObserver( 1.126 + "process-created", 1.127 + function(subject, topic, data) { 1.128 + // Don't run this observer twice, so we don't ok(true) twice. (It's fine 1.129 + // to resolve a promise twice; the second resolve() call does nothing.) 1.130 + if (observed) { 1.131 + return; 1.132 + } 1.133 + observed = true; 1.134 + 1.135 + var childID = parseInt(data); 1.136 + ok(true, 'Got new process, id=' + childID); 1.137 + deferred.resolve(childID); 1.138 + } 1.139 + ); 1.140 + 1.141 + return deferred.promise; 1.142 +} 1.143 + 1.144 +// Just like expectProcessCreated(), except we'll call ok(false) if a second 1.145 +// process is created. 1.146 +function expectOnlyOneProcessCreated() { 1.147 + var p = expectProcessCreated(); 1.148 + p.then(function() { 1.149 + expectProcessCreated().then(function(childID) { 1.150 + ok(false, 'Got unexpected process creation, childID=' + childID); 1.151 + }); 1.152 + }); 1.153 + return p; 1.154 +} 1.155 + 1.156 +// Returns a promise which is resolved or rejected the next time the process 1.157 +// childID changes its priority. We resolve if the (priority, CPU priority) 1.158 +// tuple matches (expectedPriority, expectedCPUPriority) and we reject 1.159 +// otherwise. 1.160 +// 1.161 +// expectedCPUPriority is an optional argument; if it's not specified, we 1.162 +// resolve if priority matches expectedPriority. 1.163 + 1.164 +function expectPriorityChange(childID, expectedPriority, 1.165 + /* optional */ expectedCPUPriority) { 1.166 + var deferred = Promise.defer(); 1.167 + 1.168 + var observed = false; 1.169 + browserElementTestHelpers.addProcessPriorityObserver( 1.170 + 'process-priority-set', 1.171 + function(subject, topic, data) { 1.172 + if (observed) { 1.173 + return; 1.174 + } 1.175 + 1.176 + var [id, priority, cpuPriority] = data.split(":"); 1.177 + if (id != childID) { 1.178 + return; 1.179 + } 1.180 + 1.181 + // Make sure we run the is() calls in this observer only once, otherwise 1.182 + // we'll expect /every/ priority change to match expectedPriority. 1.183 + observed = true; 1.184 + 1.185 + is(priority, expectedPriority, 1.186 + 'Expected priority of childID ' + childID + 1.187 + ' to change to ' + expectedPriority); 1.188 + 1.189 + if (expectedCPUPriority) { 1.190 + is(cpuPriority, expectedCPUPriority, 1.191 + 'Expected CPU priority of childID ' + childID + 1.192 + ' to change to ' + expectedCPUPriority); 1.193 + } 1.194 + 1.195 + if (priority == expectedPriority && 1.196 + (!expectedCPUPriority || expectedCPUPriority == cpuPriority)) { 1.197 + deferred.resolve(); 1.198 + } else { 1.199 + deferred.reject(); 1.200 + } 1.201 + } 1.202 + ); 1.203 + 1.204 + return deferred.promise; 1.205 +} 1.206 + 1.207 +// Returns a promise which is resolved or rejected the next time the background 1.208 +// process childID changes its priority. We resolve if the backgroundLRU 1.209 +// matches expectedBackgroundLRU and we reject otherwise. 1.210 + 1.211 +function expectPriorityWithBackgroundLRUSet(childID, expectedBackgroundLRU) { 1.212 + var deferred = Promise.defer(); 1.213 + 1.214 + browserElementTestHelpers.addProcessPriorityObserver( 1.215 + 'process-priority-with-background-LRU-set', 1.216 + function(subject, topic, data) { 1.217 + 1.218 + var [id, priority, cpuPriority, backgroundLRU] = data.split(":"); 1.219 + if (id != childID) { 1.220 + return; 1.221 + } 1.222 + 1.223 + is(backgroundLRU, expectedBackgroundLRU, 1.224 + 'Expected backgroundLRU ' + backgroundLRU + ' of childID ' + childID + 1.225 + ' to change to ' + expectedBackgroundLRU); 1.226 + 1.227 + if (backgroundLRU == expectedBackgroundLRU) { 1.228 + deferred.resolve(); 1.229 + } else { 1.230 + deferred.reject(); 1.231 + } 1.232 + } 1.233 + ); 1.234 + 1.235 + return deferred.promise; 1.236 +} 1.237 + 1.238 +// Returns a promise which is resolved the first time the given iframe fires 1.239 +// the mozbrowser##eventName event. 1.240 +function expectMozbrowserEvent(iframe, eventName) { 1.241 + var deferred = Promise.defer(); 1.242 + iframe.addEventListener('mozbrowser' + eventName, function handler(e) { 1.243 + iframe.removeEventListener('mozbrowser' + eventName, handler); 1.244 + deferred.resolve(e); 1.245 + }); 1.246 + return deferred.promise; 1.247 +} 1.248 + 1.249 +// Set some prefs: 1.250 +// 1.251 +// * browser.pagethumbnails.capturing_disabled: true 1.252 +// 1.253 +// Disable tab view; it seriously messes us up. 1.254 +// 1.255 +// * dom.ipc.browser_frames.oop_by_default 1.256 +// 1.257 +// Enable or disable OOP-by-default depending on the test's filename. You 1.258 +// can still force OOP on or off with <iframe mozbrowser remote=true/false>, 1.259 +// at least until bug 756376 lands. 1.260 +// 1.261 +// * dom.ipc.tabs.disabled: false 1.262 +// 1.263 +// Allow us to create OOP frames. Even if they're not the default, some 1.264 +// "in-process" tests create OOP frames. 1.265 +// 1.266 +// * network.disable.ipc.security: true 1.267 +// 1.268 +// Disable the networking security checks; our test harness just tests 1.269 +// browser elements without sticking them in apps, and the security checks 1.270 +// dislike that. 1.271 +// 1.272 +// Unfortunately setting network.disable.ipc.security to false before the 1.273 +// child process(es) created by this test have shut down can cause us to 1.274 +// assert and kill the child process. That doesn't cause the tests to fail, 1.275 +// but it's still scary looking. So we just set the pref to true and never 1.276 +// pop that value. We'll rely on the tests which test IPC security to set 1.277 +// it to false. 1.278 +// 1.279 +// * security.mixed_content.block_active_content: false 1.280 +// 1.281 +// Disable mixed active content blocking, so that tests can confirm that mixed 1.282 +// content results in a broken security state. 1.283 + 1.284 +(function() { 1.285 + var oop = location.pathname.indexOf('_inproc_') == -1; 1.286 + 1.287 + browserElementTestHelpers.lockTestReady(); 1.288 + SpecialPowers.setBoolPref("network.disable.ipc.security", true); 1.289 + SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", true], 1.290 + ["dom.ipc.browser_frames.oop_by_default", oop], 1.291 + ["dom.ipc.tabs.disabled", false], 1.292 + ["security.mixed_content.block_active_content", false]]}, 1.293 + browserElementTestHelpers.unlockTestReady.bind(browserElementTestHelpers)); 1.294 +})(); 1.295 + 1.296 +addEventListener('unload', function() { 1.297 + browserElementTestHelpers.cleanUp(); 1.298 +}); 1.299 + 1.300 +// Wait for the load event before unlocking the test-ready event. 1.301 +browserElementTestHelpers.lockTestReady(); 1.302 +addEventListener('load', function() { 1.303 + SimpleTest.executeSoon(browserElementTestHelpers.unlockTestReady.bind(browserElementTestHelpers)); 1.304 +}); 1.305 + 1.306 +////////////////////////////////// 1.307 +// promise.js from the addon SDK with some modifications to the module 1.308 +// boilerplate. 1.309 +////////////////////////////////// 1.310 + 1.311 +;(function(id, factory) { // Module boilerplate :( 1.312 + var globals = this; 1.313 + factory(function require(id) { 1.314 + return globals[id]; 1.315 + }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id }); 1.316 +}).call(this, 'Promise', function Promise(require, exports, module) { 1.317 + 1.318 +'use strict'; 1.319 + 1.320 +module.metadata = { 1.321 + "stability": "unstable" 1.322 +}; 1.323 + 1.324 +/** 1.325 + * Internal utility: Wraps given `value` into simplified promise, successfully 1.326 + * fulfilled to a given `value`. Note the result is not a complete promise 1.327 + * implementation, as its method `then` does not returns anything. 1.328 + */ 1.329 +function fulfilled(value) { 1.330 + return { then: function then(fulfill) { fulfill(value); } }; 1.331 +} 1.332 + 1.333 +/** 1.334 + * Internal utility: Wraps given input into simplified promise, pre-rejected 1.335 + * with a given `reason`. Note the result is not a complete promise 1.336 + * implementation, as its method `then` does not returns anything. 1.337 + */ 1.338 +function rejected(reason) { 1.339 + return { then: function then(fulfill, reject) { reject(reason); } }; 1.340 +} 1.341 + 1.342 +/** 1.343 + * Internal utility: Returns `true` if given `value` is a promise. Value is 1.344 + * assumed to be a promise if it implements method `then`. 1.345 + */ 1.346 +function isPromise(value) { 1.347 + return value && typeof(value.then) === 'function'; 1.348 +} 1.349 + 1.350 +/** 1.351 + * Creates deferred object containing fresh promise & methods to either resolve 1.352 + * or reject it. The result is an object with the following properties: 1.353 + * - `promise` Eventual value representation implementing CommonJS [Promises/A] 1.354 + * (http://wiki.commonjs.org/wiki/Promises/A) API. 1.355 + * - `resolve` Single shot function that resolves enclosed `promise` with a 1.356 + * given `value`. 1.357 + * - `reject` Single shot function that rejects enclosed `promise` with a given 1.358 + * `reason`. 1.359 + * 1.360 + * An optional `prototype` argument is used as a prototype of the returned 1.361 + * `promise` allowing one to implement additional API. If prototype is not 1.362 + * passed then it falls back to `Object.prototype`. 1.363 + * 1.364 + * ## Example 1.365 + * 1.366 + * function fetchURI(uri, type) { 1.367 + * var deferred = defer(); 1.368 + * var request = new XMLHttpRequest(); 1.369 + * request.open("GET", uri, true); 1.370 + * request.responseType = type; 1.371 + * request.onload = function onload() { 1.372 + * deferred.resolve(request.response); 1.373 + * } 1.374 + * request.onerror = function(event) { 1.375 + * deferred.reject(event); 1.376 + * } 1.377 + * request.send(); 1.378 + * 1.379 + * return deferred.promise; 1.380 + * } 1.381 + */ 1.382 +function defer(prototype) { 1.383 + // Define FIFO queue of observer pairs. Once promise is resolved & all queued 1.384 + // observers are forwarded to `result` and variable is set to `null`. 1.385 + var observers = []; 1.386 + 1.387 + // Promise `result`, which will be assigned a resolution value once promise 1.388 + // is resolved. Note that result will always be assigned promise (or alike) 1.389 + // object to take care of propagation through promise chains. If result is 1.390 + // `null` promise is not resolved yet. 1.391 + var result = null; 1.392 + 1.393 + prototype = (prototype || prototype === null) ? prototype : Object.prototype; 1.394 + 1.395 + // Create an object implementing promise API. 1.396 + var promise = Object.create(prototype, { 1.397 + then: { value: function then(onFulfill, onError) { 1.398 + var deferred = defer(prototype); 1.399 + 1.400 + function resolve(value) { 1.401 + // If `onFulfill` handler is provided resolve `deferred.promise` with 1.402 + // result of invoking it with a resolution value. If handler is not 1.403 + // provided propagate value through. 1.404 + try { 1.405 + deferred.resolve(onFulfill ? onFulfill(value) : value); 1.406 + } 1.407 + // `onFulfill` may throw exception in which case resulting promise 1.408 + // is rejected with thrown exception. 1.409 + catch(error) { 1.410 + if (exports._reportErrors && typeof(console) === 'object') 1.411 + console.error(error); 1.412 + // Note: Following is equivalent of `deferred.reject(error)`, 1.413 + // we use this shortcut to reduce a stack. 1.414 + deferred.resolve(rejected(error)); 1.415 + } 1.416 + } 1.417 + 1.418 + function reject(reason) { 1.419 + try { 1.420 + if (onError) deferred.resolve(onError(reason)); 1.421 + else deferred.resolve(rejected(reason)); 1.422 + } 1.423 + catch(error) { 1.424 + if (exports._reportErrors && typeof(console) === 'object') 1.425 + console.error(error) 1.426 + deferred.resolve(rejected(error)); 1.427 + } 1.428 + } 1.429 + 1.430 + // If enclosed promise (`this.promise`) observers queue is still alive 1.431 + // enqueue a new observer pair into it. Note that this does not 1.432 + // necessary means that promise is pending, it may already be resolved, 1.433 + // but we still have to queue observers to guarantee an order of 1.434 + // propagation. 1.435 + if (observers) { 1.436 + observers.push({ resolve: resolve, reject: reject }); 1.437 + } 1.438 + // Otherwise just forward observer pair right to a `result` promise. 1.439 + else { 1.440 + result.then(resolve, reject); 1.441 + } 1.442 + 1.443 + return deferred.promise; 1.444 + }} 1.445 + }) 1.446 + 1.447 + var deferred = { 1.448 + promise: promise, 1.449 + /** 1.450 + * Resolves associated `promise` to a given `value`, unless it's already 1.451 + * resolved or rejected. Note that resolved promise is not necessary a 1.452 + * successfully fulfilled. Promise may be resolved with a promise `value` 1.453 + * in which case `value` promise's fulfillment / rejection will propagate 1.454 + * up to a promise resolved with `value`. 1.455 + */ 1.456 + resolve: function resolve(value) { 1.457 + if (!result) { 1.458 + // Store resolution `value` in a `result` as a promise, so that all 1.459 + // the subsequent handlers can be simply forwarded to it. Since 1.460 + // `result` will be a promise all the value / error propagation will 1.461 + // be uniformly taken care of. 1.462 + result = isPromise(value) ? value : fulfilled(value); 1.463 + 1.464 + // Forward already registered observers to a `result` promise in the 1.465 + // order they were registered. Note that we intentionally dequeue 1.466 + // observer at a time until queue is exhausted. This makes sure that 1.467 + // handlers registered as side effect of observer forwarding are 1.468 + // queued instead of being invoked immediately, guaranteeing FIFO 1.469 + // order. 1.470 + while (observers.length) { 1.471 + var observer = observers.shift(); 1.472 + result.then(observer.resolve, observer.reject); 1.473 + } 1.474 + 1.475 + // Once `observers` queue is exhausted we `null`-ify it, so that 1.476 + // new handlers are forwarded straight to the `result`. 1.477 + observers = null; 1.478 + } 1.479 + }, 1.480 + /** 1.481 + * Rejects associated `promise` with a given `reason`, unless it's already 1.482 + * resolved / rejected. This is just a (better performing) convenience 1.483 + * shortcut for `deferred.resolve(reject(reason))`. 1.484 + */ 1.485 + reject: function reject(reason) { 1.486 + // Note that if promise is resolved that does not necessary means that it 1.487 + // is successfully fulfilled. Resolution value may be a promise in which 1.488 + // case its result propagates. In other words if promise `a` is resolved 1.489 + // with promise `b`, `a` is either fulfilled or rejected depending 1.490 + // on weather `b` is fulfilled or rejected. Here `deferred.promise` is 1.491 + // resolved with a promise pre-rejected with a given `reason`, there for 1.492 + // `deferred.promise` is rejected with a given `reason`. This may feel 1.493 + // little awkward first, but doing it this way greatly simplifies 1.494 + // propagation through promise chains. 1.495 + deferred.resolve(rejected(reason)); 1.496 + } 1.497 + }; 1.498 + 1.499 + return deferred; 1.500 +} 1.501 +exports.defer = defer; 1.502 + 1.503 +/** 1.504 + * Returns a promise resolved to a given `value`. Optionally a second 1.505 + * `prototype` argument may be provided to be used as a prototype for the 1.506 + * returned promise. 1.507 + */ 1.508 +function resolve(value, prototype) { 1.509 + var deferred = defer(prototype); 1.510 + deferred.resolve(value); 1.511 + return deferred.promise; 1.512 +} 1.513 +exports.resolve = resolve; 1.514 + 1.515 +/** 1.516 + * Returns a promise rejected with a given `reason`. Optionally a second 1.517 + * `prototype` argument may be provided to be used as a prototype for the 1.518 + * returned promise. 1.519 + */ 1.520 +function reject(reason, prototype) { 1.521 + var deferred = defer(prototype); 1.522 + deferred.reject(reason); 1.523 + return deferred.promise; 1.524 +} 1.525 +exports.reject = reject; 1.526 + 1.527 +var promised = (function() { 1.528 + // Note: Define shortcuts and utility functions here in order to avoid 1.529 + // slower property accesses and unnecessary closure creations on each 1.530 + // call of this popular function. 1.531 + 1.532 + var call = Function.call; 1.533 + var concat = Array.prototype.concat; 1.534 + 1.535 + // Utility function that does following: 1.536 + // execute([ f, self, args...]) => f.apply(self, args) 1.537 + function execute(args) { return call.apply(call, args) } 1.538 + 1.539 + // Utility function that takes promise of `a` array and maybe promise `b` 1.540 + // as arguments and returns promise for `a.concat(b)`. 1.541 + function promisedConcat(promises, unknown) { 1.542 + return promises.then(function(values) { 1.543 + return resolve(unknown).then(function(value) { 1.544 + return values.concat([ value ]) 1.545 + }); 1.546 + }); 1.547 + } 1.548 + 1.549 + return function promised(f, prototype) { 1.550 + /** 1.551 + Returns a wrapped `f`, which when called returns a promise that resolves to 1.552 + `f(...)` passing all the given arguments to it, which by the way may be 1.553 + promises. Optionally second `prototype` argument may be provided to be used 1.554 + a prototype for a returned promise. 1.555 + 1.556 + ## Example 1.557 + 1.558 + var promise = promised(Array)(1, promise(2), promise(3)) 1.559 + promise.then(console.log) // => [ 1, 2, 3 ] 1.560 + **/ 1.561 + 1.562 + return function promised() { 1.563 + // create array of [ f, this, args... ] 1.564 + return concat.apply([ f, this ], arguments). 1.565 + // reduce it via `promisedConcat` to get promised array of fulfillments 1.566 + reduce(promisedConcat, resolve([], prototype)). 1.567 + // finally map that to promise of `f.apply(this, args...)` 1.568 + then(execute); 1.569 + } 1.570 + } 1.571 +})(); 1.572 +exports.promised = promised; 1.573 + 1.574 +var all = promised(Array); 1.575 +exports.all = all; 1.576 + 1.577 +});