dom/browser-element/mochitest/browserElementTestHelpers.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial