mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 06:35:42 +00:00
536 lines
18 KiB
JavaScript
536 lines
18 KiB
JavaScript
/* Any copyright is dedicated to the public domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
// Helpers for managing the browser frame preferences.
|
|
"use strict";
|
|
|
|
function _getPath() {
|
|
return window.location.pathname
|
|
.substring(0, window.location.pathname.lastIndexOf('/'))
|
|
.replace("/priority", "");
|
|
}
|
|
|
|
const browserElementTestHelpers = {
|
|
_getBoolPref: function(pref) {
|
|
try {
|
|
return SpecialPowers.getBoolPref(pref);
|
|
}
|
|
catch (e) {
|
|
return undefined;
|
|
}
|
|
},
|
|
|
|
_setPref: function(pref, value) {
|
|
this.lockTestReady();
|
|
if (value !== undefined && value !== null) {
|
|
SpecialPowers.pushPrefEnv({'set': [[pref, value]]}, this.unlockTestReady.bind(this));
|
|
} else {
|
|
SpecialPowers.pushPrefEnv({'clear': [[pref]]}, this.unlockTestReady.bind(this));
|
|
}
|
|
},
|
|
|
|
_testReadyLockCount: 0,
|
|
_firedTestReady: false,
|
|
lockTestReady: function() {
|
|
this._testReadyLockCount++;
|
|
},
|
|
|
|
unlockTestReady: function() {
|
|
this._testReadyLockCount--;
|
|
if (this._testReadyLockCount == 0 && !this._firedTestReady) {
|
|
this._firedTestReady = true;
|
|
dispatchEvent(new Event("testready"));
|
|
}
|
|
},
|
|
|
|
enableProcessPriorityManager: function() {
|
|
this._setPref('dom.ipc.processPriorityManager.testMode', true);
|
|
this._setPref('dom.ipc.processPriorityManager.enabled', true);
|
|
},
|
|
|
|
setEnabledPref: function(value) {
|
|
this._setPref('dom.mozBrowserFramesEnabled', value);
|
|
},
|
|
|
|
getOOPByDefaultPref: function() {
|
|
return this._getBoolPref("dom.ipc.browser_frames.oop_by_default");
|
|
},
|
|
|
|
addPermission: function() {
|
|
SpecialPowers.addPermission("browser", true, document);
|
|
this.tempPermissions.push(location.href)
|
|
},
|
|
|
|
'tempPermissions': [],
|
|
addPermissionForUrl: function(url) {
|
|
SpecialPowers.addPermission("browser", true, url);
|
|
this.tempPermissions.push(url);
|
|
},
|
|
|
|
_observers: [],
|
|
|
|
// This function is a wrapper which lets you register an observer to one of
|
|
// the process priority manager's test-only topics. observerFn should be a
|
|
// function which takes (subject, topic, data).
|
|
//
|
|
// We'll clean up any observers you add at the end of the test.
|
|
addProcessPriorityObserver: function(processPriorityTopic, observerFn) {
|
|
var topic = "process-priority-manager:TEST-ONLY:" + processPriorityTopic;
|
|
|
|
// SpecialPowers appears to require that the observer be an object, not a
|
|
// function.
|
|
var observer = {
|
|
observe: observerFn
|
|
};
|
|
|
|
SpecialPowers.addObserver(observer, topic, /* weak = */ false);
|
|
this._observers.push([observer, topic]);
|
|
},
|
|
|
|
cleanUp: function() {
|
|
for (var i = 0; i < this.tempPermissions.length; i++) {
|
|
SpecialPowers.removePermission("browser", this.tempPermissions[i]);
|
|
}
|
|
|
|
for (var i = 0; i < this._observers.length; i++) {
|
|
SpecialPowers.removeObserver(this._observers[i][0],
|
|
this._observers[i][1]);
|
|
}
|
|
},
|
|
|
|
// Some basically-empty pages from different domains you can load.
|
|
'emptyPage1': 'http://example.com' + _getPath() + '/file_empty.html',
|
|
'emptyPage2': 'http://example.org' + _getPath() + '/file_empty.html',
|
|
'emptyPage3': 'http://test1.example.org' + _getPath() + '/file_empty.html',
|
|
'focusPage': 'http://example.org' + _getPath() + '/file_focus.html',
|
|
};
|
|
|
|
// Returns a promise which is resolved when a subprocess is created. The
|
|
// argument to resolve() is the childID of the subprocess.
|
|
function expectProcessCreated() {
|
|
var deferred = Promise.defer();
|
|
|
|
var observed = false;
|
|
browserElementTestHelpers.addProcessPriorityObserver(
|
|
"process-created",
|
|
function(subject, topic, data) {
|
|
// Don't run this observer twice, so we don't ok(true) twice. (It's fine
|
|
// to resolve a promise twice; the second resolve() call does nothing.)
|
|
if (observed) {
|
|
return;
|
|
}
|
|
observed = true;
|
|
|
|
var childID = parseInt(data);
|
|
ok(true, 'Got new process, id=' + childID);
|
|
deferred.resolve(childID);
|
|
}
|
|
);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
// Just like expectProcessCreated(), except we'll call ok(false) if a second
|
|
// process is created.
|
|
function expectOnlyOneProcessCreated() {
|
|
var p = expectProcessCreated();
|
|
p.then(function() {
|
|
expectProcessCreated().then(function(childID) {
|
|
ok(false, 'Got unexpected process creation, childID=' + childID);
|
|
});
|
|
});
|
|
return p;
|
|
}
|
|
|
|
// Returns a promise which is resolved or rejected the next time the process
|
|
// childID changes its priority. We resolve if the (priority, CPU priority)
|
|
// tuple matches (expectedPriority, expectedCPUPriority) and we reject
|
|
// otherwise.
|
|
//
|
|
// expectedCPUPriority is an optional argument; if it's not specified, we
|
|
// resolve if priority matches expectedPriority.
|
|
|
|
function expectPriorityChange(childID, expectedPriority,
|
|
/* optional */ expectedCPUPriority) {
|
|
var deferred = Promise.defer();
|
|
|
|
var observed = false;
|
|
browserElementTestHelpers.addProcessPriorityObserver(
|
|
'process-priority-set',
|
|
function(subject, topic, data) {
|
|
if (observed) {
|
|
return;
|
|
}
|
|
|
|
[id, priority, cpuPriority] = data.split(":");
|
|
if (id != childID) {
|
|
return;
|
|
}
|
|
|
|
// Make sure we run the is() calls in this observer only once, otherwise
|
|
// we'll expect /every/ priority change to match expectedPriority.
|
|
observed = true;
|
|
|
|
is(priority, expectedPriority,
|
|
'Expected priority of childID ' + childID +
|
|
' to change to ' + expectedPriority);
|
|
|
|
if (expectedCPUPriority) {
|
|
is(cpuPriority, expectedCPUPriority,
|
|
'Expected CPU priority of childID ' + childID +
|
|
' to change to ' + expectedCPUPriority);
|
|
}
|
|
|
|
if (priority == expectedPriority &&
|
|
(!expectedCPUPriority || expectedCPUPriority == cpuPriority)) {
|
|
deferred.resolve();
|
|
} else {
|
|
deferred.reject();
|
|
}
|
|
}
|
|
);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
// Returns a promise which is resolved the first time the given iframe fires
|
|
// the mozbrowser##eventName event.
|
|
function expectMozbrowserEvent(iframe, eventName) {
|
|
var deferred = Promise.defer();
|
|
iframe.addEventListener('mozbrowser' + eventName, function handler(e) {
|
|
iframe.removeEventListener('mozbrowser' + eventName, handler);
|
|
deferred.resolve(e);
|
|
});
|
|
return deferred.promise;
|
|
}
|
|
|
|
// Set some prefs:
|
|
//
|
|
// * browser.pageThumbs.enabled: false
|
|
//
|
|
// Disable tab view; it seriously messes us up.
|
|
//
|
|
// * dom.ipc.browser_frames.oop_by_default
|
|
//
|
|
// Enable or disable OOP-by-default depending on the test's filename. You
|
|
// can still force OOP on or off with <iframe mozbrowser remote=true/false>,
|
|
// at least until bug 756376 lands.
|
|
//
|
|
// * dom.ipc.tabs.disabled: false
|
|
//
|
|
// Allow us to create OOP frames. Even if they're not the default, some
|
|
// "in-process" tests create OOP frames.
|
|
//
|
|
// * network.disable.ipc.security: true
|
|
//
|
|
// Disable the networking security checks; our test harness just tests
|
|
// browser elements without sticking them in apps, and the security checks
|
|
// dislike that.
|
|
//
|
|
// Unfortunately setting network.disable.ipc.security to false before the
|
|
// child process(es) created by this test have shut down can cause us to
|
|
// assert and kill the child process. That doesn't cause the tests to fail,
|
|
// but it's still scary looking. So we just set the pref to true and never
|
|
// pop that value. We'll rely on the tests which test IPC security to set
|
|
// it to false.
|
|
//
|
|
// * security.mixed_content.block_active_content: false
|
|
//
|
|
// Disable mixed active content blocking, so that tests can confirm that mixed
|
|
// content results in a broken security state.
|
|
|
|
(function() {
|
|
var oop = location.pathname.indexOf('_inproc_') == -1;
|
|
|
|
browserElementTestHelpers.lockTestReady();
|
|
SpecialPowers.setBoolPref("network.disable.ipc.security", true);
|
|
SpecialPowers.pushPrefEnv({set: [["browser.pageThumbs.enabled", false],
|
|
["dom.ipc.browser_frames.oop_by_default", oop],
|
|
["dom.ipc.tabs.disabled", false],
|
|
["security.mixed_content.block_active_content", false]]},
|
|
browserElementTestHelpers.unlockTestReady.bind(browserElementTestHelpers));
|
|
})();
|
|
|
|
addEventListener('unload', function() {
|
|
browserElementTestHelpers.cleanUp();
|
|
});
|
|
|
|
// Wait for the load event before unlocking the test-ready event.
|
|
browserElementTestHelpers.lockTestReady();
|
|
addEventListener('load', function() {
|
|
SimpleTest.executeSoon(browserElementTestHelpers.unlockTestReady.bind(browserElementTestHelpers));
|
|
});
|
|
|
|
//////////////////////////////////
|
|
// promise.js from the addon SDK with some modifications to the module
|
|
// boilerplate.
|
|
//////////////////////////////////
|
|
|
|
;(function(id, factory) { // Module boilerplate :(
|
|
var globals = this;
|
|
factory(function require(id) {
|
|
return globals[id];
|
|
}, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
|
|
}).call(this, 'Promise', function Promise(require, exports, module) {
|
|
|
|
'use strict';
|
|
|
|
module.metadata = {
|
|
"stability": "unstable"
|
|
};
|
|
|
|
/**
|
|
* Internal utility: Wraps given `value` into simplified promise, successfully
|
|
* fulfilled to a given `value`. Note the result is not a complete promise
|
|
* implementation, as its method `then` does not returns anything.
|
|
*/
|
|
function fulfilled(value) {
|
|
return { then: function then(fulfill) { fulfill(value); } };
|
|
}
|
|
|
|
/**
|
|
* Internal utility: Wraps given input into simplified promise, pre-rejected
|
|
* with a given `reason`. Note the result is not a complete promise
|
|
* implementation, as its method `then` does not returns anything.
|
|
*/
|
|
function rejected(reason) {
|
|
return { then: function then(fulfill, reject) { reject(reason); } };
|
|
}
|
|
|
|
/**
|
|
* Internal utility: Returns `true` if given `value` is a promise. Value is
|
|
* assumed to be a promise if it implements method `then`.
|
|
*/
|
|
function isPromise(value) {
|
|
return value && typeof(value.then) === 'function';
|
|
}
|
|
|
|
/**
|
|
* Creates deferred object containing fresh promise & methods to either resolve
|
|
* or reject it. The result is an object with the following properties:
|
|
* - `promise` Eventual value representation implementing CommonJS [Promises/A]
|
|
* (http://wiki.commonjs.org/wiki/Promises/A) API.
|
|
* - `resolve` Single shot function that resolves enclosed `promise` with a
|
|
* given `value`.
|
|
* - `reject` Single shot function that rejects enclosed `promise` with a given
|
|
* `reason`.
|
|
*
|
|
* An optional `prototype` argument is used as a prototype of the returned
|
|
* `promise` allowing one to implement additional API. If prototype is not
|
|
* passed then it falls back to `Object.prototype`.
|
|
*
|
|
* ## Example
|
|
*
|
|
* function fetchURI(uri, type) {
|
|
* var deferred = defer();
|
|
* var request = new XMLHttpRequest();
|
|
* request.open("GET", uri, true);
|
|
* request.responseType = type;
|
|
* request.onload = function onload() {
|
|
* deferred.resolve(request.response);
|
|
* }
|
|
* request.onerror = function(event) {
|
|
* deferred.reject(event);
|
|
* }
|
|
* request.send();
|
|
*
|
|
* return deferred.promise;
|
|
* }
|
|
*/
|
|
function defer(prototype) {
|
|
// Define FIFO queue of observer pairs. Once promise is resolved & all queued
|
|
// observers are forwarded to `result` and variable is set to `null`.
|
|
var observers = [];
|
|
|
|
// Promise `result`, which will be assigned a resolution value once promise
|
|
// is resolved. Note that result will always be assigned promise (or alike)
|
|
// object to take care of propagation through promise chains. If result is
|
|
// `null` promise is not resolved yet.
|
|
var result = null;
|
|
|
|
prototype = (prototype || prototype === null) ? prototype : Object.prototype;
|
|
|
|
// Create an object implementing promise API.
|
|
var promise = Object.create(prototype, {
|
|
then: { value: function then(onFulfill, onError) {
|
|
var deferred = defer(prototype);
|
|
|
|
function resolve(value) {
|
|
// If `onFulfill` handler is provided resolve `deferred.promise` with
|
|
// result of invoking it with a resolution value. If handler is not
|
|
// provided propagate value through.
|
|
try {
|
|
deferred.resolve(onFulfill ? onFulfill(value) : value);
|
|
}
|
|
// `onFulfill` may throw exception in which case resulting promise
|
|
// is rejected with thrown exception.
|
|
catch(error) {
|
|
if (exports._reportErrors && typeof(console) === 'object')
|
|
console.error(error);
|
|
// Note: Following is equivalent of `deferred.reject(error)`,
|
|
// we use this shortcut to reduce a stack.
|
|
deferred.resolve(rejected(error));
|
|
}
|
|
}
|
|
|
|
function reject(reason) {
|
|
try {
|
|
if (onError) deferred.resolve(onError(reason));
|
|
else deferred.resolve(rejected(reason));
|
|
}
|
|
catch(error) {
|
|
if (exports._reportErrors && typeof(console) === 'object')
|
|
console.error(error)
|
|
deferred.resolve(rejected(error));
|
|
}
|
|
}
|
|
|
|
// If enclosed promise (`this.promise`) observers queue is still alive
|
|
// enqueue a new observer pair into it. Note that this does not
|
|
// necessary means that promise is pending, it may already be resolved,
|
|
// but we still have to queue observers to guarantee an order of
|
|
// propagation.
|
|
if (observers) {
|
|
observers.push({ resolve: resolve, reject: reject });
|
|
}
|
|
// Otherwise just forward observer pair right to a `result` promise.
|
|
else {
|
|
result.then(resolve, reject);
|
|
}
|
|
|
|
return deferred.promise;
|
|
}}
|
|
})
|
|
|
|
var deferred = {
|
|
promise: promise,
|
|
/**
|
|
* Resolves associated `promise` to a given `value`, unless it's already
|
|
* resolved or rejected. Note that resolved promise is not necessary a
|
|
* successfully fulfilled. Promise may be resolved with a promise `value`
|
|
* in which case `value` promise's fulfillment / rejection will propagate
|
|
* up to a promise resolved with `value`.
|
|
*/
|
|
resolve: function resolve(value) {
|
|
if (!result) {
|
|
// Store resolution `value` in a `result` as a promise, so that all
|
|
// the subsequent handlers can be simply forwarded to it. Since
|
|
// `result` will be a promise all the value / error propagation will
|
|
// be uniformly taken care of.
|
|
result = isPromise(value) ? value : fulfilled(value);
|
|
|
|
// Forward already registered observers to a `result` promise in the
|
|
// order they were registered. Note that we intentionally dequeue
|
|
// observer at a time until queue is exhausted. This makes sure that
|
|
// handlers registered as side effect of observer forwarding are
|
|
// queued instead of being invoked immediately, guaranteeing FIFO
|
|
// order.
|
|
while (observers.length) {
|
|
var observer = observers.shift();
|
|
result.then(observer.resolve, observer.reject);
|
|
}
|
|
|
|
// Once `observers` queue is exhausted we `null`-ify it, so that
|
|
// new handlers are forwarded straight to the `result`.
|
|
observers = null;
|
|
}
|
|
},
|
|
/**
|
|
* Rejects associated `promise` with a given `reason`, unless it's already
|
|
* resolved / rejected. This is just a (better performing) convenience
|
|
* shortcut for `deferred.resolve(reject(reason))`.
|
|
*/
|
|
reject: function reject(reason) {
|
|
// Note that if promise is resolved that does not necessary means that it
|
|
// is successfully fulfilled. Resolution value may be a promise in which
|
|
// case its result propagates. In other words if promise `a` is resolved
|
|
// with promise `b`, `a` is either fulfilled or rejected depending
|
|
// on weather `b` is fulfilled or rejected. Here `deferred.promise` is
|
|
// resolved with a promise pre-rejected with a given `reason`, there for
|
|
// `deferred.promise` is rejected with a given `reason`. This may feel
|
|
// little awkward first, but doing it this way greatly simplifies
|
|
// propagation through promise chains.
|
|
deferred.resolve(rejected(reason));
|
|
}
|
|
};
|
|
|
|
return deferred;
|
|
}
|
|
exports.defer = defer;
|
|
|
|
/**
|
|
* Returns a promise resolved to a given `value`. Optionally a second
|
|
* `prototype` argument may be provided to be used as a prototype for the
|
|
* returned promise.
|
|
*/
|
|
function resolve(value, prototype) {
|
|
var deferred = defer(prototype);
|
|
deferred.resolve(value);
|
|
return deferred.promise;
|
|
}
|
|
exports.resolve = resolve;
|
|
|
|
/**
|
|
* Returns a promise rejected with a given `reason`. Optionally a second
|
|
* `prototype` argument may be provided to be used as a prototype for the
|
|
* returned promise.
|
|
*/
|
|
function reject(reason, prototype) {
|
|
var deferred = defer(prototype);
|
|
deferred.reject(reason);
|
|
return deferred.promise;
|
|
}
|
|
exports.reject = reject;
|
|
|
|
var promised = (function() {
|
|
// Note: Define shortcuts and utility functions here in order to avoid
|
|
// slower property accesses and unnecessary closure creations on each
|
|
// call of this popular function.
|
|
|
|
var call = Function.call;
|
|
var concat = Array.prototype.concat;
|
|
|
|
// Utility function that does following:
|
|
// execute([ f, self, args...]) => f.apply(self, args)
|
|
function execute(args) { return call.apply(call, args) }
|
|
|
|
// Utility function that takes promise of `a` array and maybe promise `b`
|
|
// as arguments and returns promise for `a.concat(b)`.
|
|
function promisedConcat(promises, unknown) {
|
|
return promises.then(function(values) {
|
|
return resolve(unknown).then(function(value) {
|
|
return values.concat([ value ])
|
|
});
|
|
});
|
|
}
|
|
|
|
return function promised(f, prototype) {
|
|
/**
|
|
Returns a wrapped `f`, which when called returns a promise that resolves to
|
|
`f(...)` passing all the given arguments to it, which by the way may be
|
|
promises. Optionally second `prototype` argument may be provided to be used
|
|
a prototype for a returned promise.
|
|
|
|
## Example
|
|
|
|
var promise = promised(Array)(1, promise(2), promise(3))
|
|
promise.then(console.log) // => [ 1, 2, 3 ]
|
|
**/
|
|
|
|
return function promised() {
|
|
// create array of [ f, this, args... ]
|
|
return concat.apply([ f, this ], arguments).
|
|
// reduce it via `promisedConcat` to get promised array of fulfillments
|
|
reduce(promisedConcat, resolve([], prototype)).
|
|
// finally map that to promise of `f.apply(this, args...)`
|
|
then(execute);
|
|
}
|
|
}
|
|
})();
|
|
exports.promised = promised;
|
|
|
|
var all = promised(Array);
|
|
exports.all = all;
|
|
|
|
});
|