dom/browser-element/mochitest/browserElementTestHelpers.js

changeset 2
7e26c7da4463
equal deleted inserted replaced
-1:000000000000 0:35c899fbd4cb
1 /* Any copyright is dedicated to the public domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
3
4 // Helpers for managing the browser frame preferences.
5 "use strict";
6
7 function _getPath() {
8 return window.location.pathname
9 .substring(0, window.location.pathname.lastIndexOf('/'))
10 .replace("/priority", "");
11 }
12
13 const browserElementTestHelpers = {
14 _getBoolPref: function(pref) {
15 try {
16 return SpecialPowers.getBoolPref(pref);
17 }
18 catch (e) {
19 return undefined;
20 }
21 },
22
23 _setPref: function(pref, value) {
24 this.lockTestReady();
25 if (value !== undefined && value !== null) {
26 SpecialPowers.pushPrefEnv({'set': [[pref, value]]}, this.unlockTestReady.bind(this));
27 } else {
28 SpecialPowers.pushPrefEnv({'clear': [[pref]]}, this.unlockTestReady.bind(this));
29 }
30 },
31
32 _setPrefs: function() {
33 this.lockTestReady();
34 SpecialPowers.pushPrefEnv({'set': Array.slice(arguments)}, this.unlockTestReady.bind(this));
35 },
36
37 _testReadyLockCount: 0,
38 _firedTestReady: false,
39 lockTestReady: function() {
40 this._testReadyLockCount++;
41 },
42
43 unlockTestReady: function() {
44 this._testReadyLockCount--;
45 if (this._testReadyLockCount == 0 && !this._firedTestReady) {
46 this._firedTestReady = true;
47 dispatchEvent(new Event("testready"));
48 }
49 },
50
51 enableProcessPriorityManager: function() {
52 this._setPrefs(
53 ['dom.ipc.processPriorityManager.testMode', true],
54 ['dom.ipc.processPriorityManager.enabled', true],
55 ['dom.ipc.processPriorityManager.backgroundLRUPoolLevels', 2]
56 );
57 },
58
59 setEnabledPref: function(value) {
60 this._setPref('dom.mozBrowserFramesEnabled', value);
61 },
62
63 getOOPByDefaultPref: function() {
64 return this._getBoolPref("dom.ipc.browser_frames.oop_by_default");
65 },
66
67 addPermission: function() {
68 SpecialPowers.addPermission("browser", true, document);
69 this.tempPermissions.push(location.href)
70 },
71
72 'tempPermissions': [],
73 addPermissionForUrl: function(url) {
74 SpecialPowers.addPermission("browser", true, url);
75 this.tempPermissions.push(url);
76 },
77
78 _observers: [],
79
80 // This function is a wrapper which lets you register an observer to one of
81 // the process priority manager's test-only topics. observerFn should be a
82 // function which takes (subject, topic, data).
83 //
84 // We'll clean up any observers you add at the end of the test.
85 addProcessPriorityObserver: function(processPriorityTopic, observerFn) {
86 var topic = "process-priority-manager:TEST-ONLY:" + processPriorityTopic;
87
88 // SpecialPowers appears to require that the observer be an object, not a
89 // function.
90 var observer = {
91 observe: observerFn
92 };
93
94 SpecialPowers.addObserver(observer, topic, /* weak = */ false);
95 this._observers.push([observer, topic]);
96 },
97
98 cleanUp: function() {
99 for (var i = 0; i < this.tempPermissions.length; i++) {
100 SpecialPowers.removePermission("browser", this.tempPermissions[i]);
101 }
102
103 for (var i = 0; i < this._observers.length; i++) {
104 SpecialPowers.removeObserver(this._observers[i][0],
105 this._observers[i][1]);
106 }
107 },
108
109 // Some basically-empty pages from different domains you can load.
110 'emptyPage1': 'http://example.com' + _getPath() + '/file_empty.html',
111 'emptyPage2': 'http://example.org' + _getPath() + '/file_empty.html',
112 'emptyPage3': 'http://test1.example.org' + _getPath() + '/file_empty.html',
113 'focusPage': 'http://example.org' + _getPath() + '/file_focus.html',
114 };
115
116 // Returns a promise which is resolved when a subprocess is created. The
117 // argument to resolve() is the childID of the subprocess.
118 function expectProcessCreated() {
119 var deferred = Promise.defer();
120
121 var observed = false;
122 browserElementTestHelpers.addProcessPriorityObserver(
123 "process-created",
124 function(subject, topic, data) {
125 // Don't run this observer twice, so we don't ok(true) twice. (It's fine
126 // to resolve a promise twice; the second resolve() call does nothing.)
127 if (observed) {
128 return;
129 }
130 observed = true;
131
132 var childID = parseInt(data);
133 ok(true, 'Got new process, id=' + childID);
134 deferred.resolve(childID);
135 }
136 );
137
138 return deferred.promise;
139 }
140
141 // Just like expectProcessCreated(), except we'll call ok(false) if a second
142 // process is created.
143 function expectOnlyOneProcessCreated() {
144 var p = expectProcessCreated();
145 p.then(function() {
146 expectProcessCreated().then(function(childID) {
147 ok(false, 'Got unexpected process creation, childID=' + childID);
148 });
149 });
150 return p;
151 }
152
153 // Returns a promise which is resolved or rejected the next time the process
154 // childID changes its priority. We resolve if the (priority, CPU priority)
155 // tuple matches (expectedPriority, expectedCPUPriority) and we reject
156 // otherwise.
157 //
158 // expectedCPUPriority is an optional argument; if it's not specified, we
159 // resolve if priority matches expectedPriority.
160
161 function expectPriorityChange(childID, expectedPriority,
162 /* optional */ expectedCPUPriority) {
163 var deferred = Promise.defer();
164
165 var observed = false;
166 browserElementTestHelpers.addProcessPriorityObserver(
167 'process-priority-set',
168 function(subject, topic, data) {
169 if (observed) {
170 return;
171 }
172
173 var [id, priority, cpuPriority] = data.split(":");
174 if (id != childID) {
175 return;
176 }
177
178 // Make sure we run the is() calls in this observer only once, otherwise
179 // we'll expect /every/ priority change to match expectedPriority.
180 observed = true;
181
182 is(priority, expectedPriority,
183 'Expected priority of childID ' + childID +
184 ' to change to ' + expectedPriority);
185
186 if (expectedCPUPriority) {
187 is(cpuPriority, expectedCPUPriority,
188 'Expected CPU priority of childID ' + childID +
189 ' to change to ' + expectedCPUPriority);
190 }
191
192 if (priority == expectedPriority &&
193 (!expectedCPUPriority || expectedCPUPriority == cpuPriority)) {
194 deferred.resolve();
195 } else {
196 deferred.reject();
197 }
198 }
199 );
200
201 return deferred.promise;
202 }
203
204 // Returns a promise which is resolved or rejected the next time the background
205 // process childID changes its priority. We resolve if the backgroundLRU
206 // matches expectedBackgroundLRU and we reject otherwise.
207
208 function expectPriorityWithBackgroundLRUSet(childID, expectedBackgroundLRU) {
209 var deferred = Promise.defer();
210
211 browserElementTestHelpers.addProcessPriorityObserver(
212 'process-priority-with-background-LRU-set',
213 function(subject, topic, data) {
214
215 var [id, priority, cpuPriority, backgroundLRU] = data.split(":");
216 if (id != childID) {
217 return;
218 }
219
220 is(backgroundLRU, expectedBackgroundLRU,
221 'Expected backgroundLRU ' + backgroundLRU + ' of childID ' + childID +
222 ' to change to ' + expectedBackgroundLRU);
223
224 if (backgroundLRU == expectedBackgroundLRU) {
225 deferred.resolve();
226 } else {
227 deferred.reject();
228 }
229 }
230 );
231
232 return deferred.promise;
233 }
234
235 // Returns a promise which is resolved the first time the given iframe fires
236 // the mozbrowser##eventName event.
237 function expectMozbrowserEvent(iframe, eventName) {
238 var deferred = Promise.defer();
239 iframe.addEventListener('mozbrowser' + eventName, function handler(e) {
240 iframe.removeEventListener('mozbrowser' + eventName, handler);
241 deferred.resolve(e);
242 });
243 return deferred.promise;
244 }
245
246 // Set some prefs:
247 //
248 // * browser.pagethumbnails.capturing_disabled: true
249 //
250 // Disable tab view; it seriously messes us up.
251 //
252 // * dom.ipc.browser_frames.oop_by_default
253 //
254 // Enable or disable OOP-by-default depending on the test's filename. You
255 // can still force OOP on or off with <iframe mozbrowser remote=true/false>,
256 // at least until bug 756376 lands.
257 //
258 // * dom.ipc.tabs.disabled: false
259 //
260 // Allow us to create OOP frames. Even if they're not the default, some
261 // "in-process" tests create OOP frames.
262 //
263 // * network.disable.ipc.security: true
264 //
265 // Disable the networking security checks; our test harness just tests
266 // browser elements without sticking them in apps, and the security checks
267 // dislike that.
268 //
269 // Unfortunately setting network.disable.ipc.security to false before the
270 // child process(es) created by this test have shut down can cause us to
271 // assert and kill the child process. That doesn't cause the tests to fail,
272 // but it's still scary looking. So we just set the pref to true and never
273 // pop that value. We'll rely on the tests which test IPC security to set
274 // it to false.
275 //
276 // * security.mixed_content.block_active_content: false
277 //
278 // Disable mixed active content blocking, so that tests can confirm that mixed
279 // content results in a broken security state.
280
281 (function() {
282 var oop = location.pathname.indexOf('_inproc_') == -1;
283
284 browserElementTestHelpers.lockTestReady();
285 SpecialPowers.setBoolPref("network.disable.ipc.security", true);
286 SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", true],
287 ["dom.ipc.browser_frames.oop_by_default", oop],
288 ["dom.ipc.tabs.disabled", false],
289 ["security.mixed_content.block_active_content", false]]},
290 browserElementTestHelpers.unlockTestReady.bind(browserElementTestHelpers));
291 })();
292
293 addEventListener('unload', function() {
294 browserElementTestHelpers.cleanUp();
295 });
296
297 // Wait for the load event before unlocking the test-ready event.
298 browserElementTestHelpers.lockTestReady();
299 addEventListener('load', function() {
300 SimpleTest.executeSoon(browserElementTestHelpers.unlockTestReady.bind(browserElementTestHelpers));
301 });
302
303 //////////////////////////////////
304 // promise.js from the addon SDK with some modifications to the module
305 // boilerplate.
306 //////////////////////////////////
307
308 ;(function(id, factory) { // Module boilerplate :(
309 var globals = this;
310 factory(function require(id) {
311 return globals[id];
312 }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
313 }).call(this, 'Promise', function Promise(require, exports, module) {
314
315 'use strict';
316
317 module.metadata = {
318 "stability": "unstable"
319 };
320
321 /**
322 * Internal utility: Wraps given `value` into simplified promise, successfully
323 * fulfilled to a given `value`. Note the result is not a complete promise
324 * implementation, as its method `then` does not returns anything.
325 */
326 function fulfilled(value) {
327 return { then: function then(fulfill) { fulfill(value); } };
328 }
329
330 /**
331 * Internal utility: Wraps given input into simplified promise, pre-rejected
332 * with a given `reason`. Note the result is not a complete promise
333 * implementation, as its method `then` does not returns anything.
334 */
335 function rejected(reason) {
336 return { then: function then(fulfill, reject) { reject(reason); } };
337 }
338
339 /**
340 * Internal utility: Returns `true` if given `value` is a promise. Value is
341 * assumed to be a promise if it implements method `then`.
342 */
343 function isPromise(value) {
344 return value && typeof(value.then) === 'function';
345 }
346
347 /**
348 * Creates deferred object containing fresh promise & methods to either resolve
349 * or reject it. The result is an object with the following properties:
350 * - `promise` Eventual value representation implementing CommonJS [Promises/A]
351 * (http://wiki.commonjs.org/wiki/Promises/A) API.
352 * - `resolve` Single shot function that resolves enclosed `promise` with a
353 * given `value`.
354 * - `reject` Single shot function that rejects enclosed `promise` with a given
355 * `reason`.
356 *
357 * An optional `prototype` argument is used as a prototype of the returned
358 * `promise` allowing one to implement additional API. If prototype is not
359 * passed then it falls back to `Object.prototype`.
360 *
361 * ## Example
362 *
363 * function fetchURI(uri, type) {
364 * var deferred = defer();
365 * var request = new XMLHttpRequest();
366 * request.open("GET", uri, true);
367 * request.responseType = type;
368 * request.onload = function onload() {
369 * deferred.resolve(request.response);
370 * }
371 * request.onerror = function(event) {
372 * deferred.reject(event);
373 * }
374 * request.send();
375 *
376 * return deferred.promise;
377 * }
378 */
379 function defer(prototype) {
380 // Define FIFO queue of observer pairs. Once promise is resolved & all queued
381 // observers are forwarded to `result` and variable is set to `null`.
382 var observers = [];
383
384 // Promise `result`, which will be assigned a resolution value once promise
385 // is resolved. Note that result will always be assigned promise (or alike)
386 // object to take care of propagation through promise chains. If result is
387 // `null` promise is not resolved yet.
388 var result = null;
389
390 prototype = (prototype || prototype === null) ? prototype : Object.prototype;
391
392 // Create an object implementing promise API.
393 var promise = Object.create(prototype, {
394 then: { value: function then(onFulfill, onError) {
395 var deferred = defer(prototype);
396
397 function resolve(value) {
398 // If `onFulfill` handler is provided resolve `deferred.promise` with
399 // result of invoking it with a resolution value. If handler is not
400 // provided propagate value through.
401 try {
402 deferred.resolve(onFulfill ? onFulfill(value) : value);
403 }
404 // `onFulfill` may throw exception in which case resulting promise
405 // is rejected with thrown exception.
406 catch(error) {
407 if (exports._reportErrors && typeof(console) === 'object')
408 console.error(error);
409 // Note: Following is equivalent of `deferred.reject(error)`,
410 // we use this shortcut to reduce a stack.
411 deferred.resolve(rejected(error));
412 }
413 }
414
415 function reject(reason) {
416 try {
417 if (onError) deferred.resolve(onError(reason));
418 else deferred.resolve(rejected(reason));
419 }
420 catch(error) {
421 if (exports._reportErrors && typeof(console) === 'object')
422 console.error(error)
423 deferred.resolve(rejected(error));
424 }
425 }
426
427 // If enclosed promise (`this.promise`) observers queue is still alive
428 // enqueue a new observer pair into it. Note that this does not
429 // necessary means that promise is pending, it may already be resolved,
430 // but we still have to queue observers to guarantee an order of
431 // propagation.
432 if (observers) {
433 observers.push({ resolve: resolve, reject: reject });
434 }
435 // Otherwise just forward observer pair right to a `result` promise.
436 else {
437 result.then(resolve, reject);
438 }
439
440 return deferred.promise;
441 }}
442 })
443
444 var deferred = {
445 promise: promise,
446 /**
447 * Resolves associated `promise` to a given `value`, unless it's already
448 * resolved or rejected. Note that resolved promise is not necessary a
449 * successfully fulfilled. Promise may be resolved with a promise `value`
450 * in which case `value` promise's fulfillment / rejection will propagate
451 * up to a promise resolved with `value`.
452 */
453 resolve: function resolve(value) {
454 if (!result) {
455 // Store resolution `value` in a `result` as a promise, so that all
456 // the subsequent handlers can be simply forwarded to it. Since
457 // `result` will be a promise all the value / error propagation will
458 // be uniformly taken care of.
459 result = isPromise(value) ? value : fulfilled(value);
460
461 // Forward already registered observers to a `result` promise in the
462 // order they were registered. Note that we intentionally dequeue
463 // observer at a time until queue is exhausted. This makes sure that
464 // handlers registered as side effect of observer forwarding are
465 // queued instead of being invoked immediately, guaranteeing FIFO
466 // order.
467 while (observers.length) {
468 var observer = observers.shift();
469 result.then(observer.resolve, observer.reject);
470 }
471
472 // Once `observers` queue is exhausted we `null`-ify it, so that
473 // new handlers are forwarded straight to the `result`.
474 observers = null;
475 }
476 },
477 /**
478 * Rejects associated `promise` with a given `reason`, unless it's already
479 * resolved / rejected. This is just a (better performing) convenience
480 * shortcut for `deferred.resolve(reject(reason))`.
481 */
482 reject: function reject(reason) {
483 // Note that if promise is resolved that does not necessary means that it
484 // is successfully fulfilled. Resolution value may be a promise in which
485 // case its result propagates. In other words if promise `a` is resolved
486 // with promise `b`, `a` is either fulfilled or rejected depending
487 // on weather `b` is fulfilled or rejected. Here `deferred.promise` is
488 // resolved with a promise pre-rejected with a given `reason`, there for
489 // `deferred.promise` is rejected with a given `reason`. This may feel
490 // little awkward first, but doing it this way greatly simplifies
491 // propagation through promise chains.
492 deferred.resolve(rejected(reason));
493 }
494 };
495
496 return deferred;
497 }
498 exports.defer = defer;
499
500 /**
501 * Returns a promise resolved to a given `value`. Optionally a second
502 * `prototype` argument may be provided to be used as a prototype for the
503 * returned promise.
504 */
505 function resolve(value, prototype) {
506 var deferred = defer(prototype);
507 deferred.resolve(value);
508 return deferred.promise;
509 }
510 exports.resolve = resolve;
511
512 /**
513 * Returns a promise rejected with a given `reason`. Optionally a second
514 * `prototype` argument may be provided to be used as a prototype for the
515 * returned promise.
516 */
517 function reject(reason, prototype) {
518 var deferred = defer(prototype);
519 deferred.reject(reason);
520 return deferred.promise;
521 }
522 exports.reject = reject;
523
524 var promised = (function() {
525 // Note: Define shortcuts and utility functions here in order to avoid
526 // slower property accesses and unnecessary closure creations on each
527 // call of this popular function.
528
529 var call = Function.call;
530 var concat = Array.prototype.concat;
531
532 // Utility function that does following:
533 // execute([ f, self, args...]) => f.apply(self, args)
534 function execute(args) { return call.apply(call, args) }
535
536 // Utility function that takes promise of `a` array and maybe promise `b`
537 // as arguments and returns promise for `a.concat(b)`.
538 function promisedConcat(promises, unknown) {
539 return promises.then(function(values) {
540 return resolve(unknown).then(function(value) {
541 return values.concat([ value ])
542 });
543 });
544 }
545
546 return function promised(f, prototype) {
547 /**
548 Returns a wrapped `f`, which when called returns a promise that resolves to
549 `f(...)` passing all the given arguments to it, which by the way may be
550 promises. Optionally second `prototype` argument may be provided to be used
551 a prototype for a returned promise.
552
553 ## Example
554
555 var promise = promised(Array)(1, promise(2), promise(3))
556 promise.then(console.log) // => [ 1, 2, 3 ]
557 **/
558
559 return function promised() {
560 // create array of [ f, this, args... ]
561 return concat.apply([ f, this ], arguments).
562 // reduce it via `promisedConcat` to get promised array of fulfillments
563 reduce(promisedConcat, resolve([], prototype)).
564 // finally map that to promise of `f.apply(this, args...)`
565 then(execute);
566 }
567 }
568 })();
569 exports.promised = promised;
570
571 var all = promised(Array);
572 exports.all = all;
573
574 });

mercurial