|
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 }); |