Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

asynctypes-685526: Sync with changes in Firefox

domtemplate and promise had changed in Firefox. This brings them all
together.

Signed-off-by: Joe Walker <jwalker@mozilla.com>
  • Loading branch information...
commit 9b1a1bb09d2511a7917f6f64d6a395873249db73 1 parent 4f08e10
@joewalker joewalker authored
Showing with 236 additions and 203 deletions.
  1. +54 −37 lib/util/domtemplate.js
  2. +182 −166 lib/util/promise.js
View
91 lib/util/domtemplate.js
@@ -15,6 +15,9 @@
*/
define(function(require, exports, module) {
+//
+//
+//
@campd
campd added a note

//

@joewalker Owner

There is some logic, bizarrely. This file exists in mozilla-central, where the header is bigger. By adding these blank lines the line numbers in the 2 files are the same, which makes looking at diffs easier (it may also help debugging on occasion, but that's not really the point)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
// WARNING: do not 'use_strict' without reading the notes in _envEval();
@@ -128,6 +131,7 @@ Templater.prototype.processNode = function(node, data) {
for (var i = 0; i < attrs.length; i++) {
var value = attrs[i].value;
var name = attrs[i].name;
+
this.stack.push(name);
try {
if (name === 'save') {
@@ -137,55 +141,57 @@ Templater.prototype.processNode = function(node, data) {
node.removeAttribute('save');
}
else if (name.substring(0, 2) === 'on') {
- // Event registration relies on property doing a bind
- value = this._stripBraces(value);
- var func = this._property(value, data);
- if (typeof func !== 'function') {
- this._handleError('Expected ' + value +
- ' to resolve to a function, but got ' + typeof func);
+ // If this attribute value contains only an expression
+ if (value.substring(0, 2) === '${' && value.slice(-1) === '}' &&
+ value.indexOf('${', 2) === -1) {
+ value = this._stripBraces(value);
+ var func = this._property(value, data);
+ if (typeof func === 'function') {
+ node.removeAttribute(name);
+ var capture = node.hasAttribute('capture' + name.substring(2));
+ node.addEventListener(name.substring(2), func, capture);
+ if (capture) {
+ node.removeAttribute('capture' + name.substring(2));
+ }
+ }
+ else {
+ // Attribute value is not a function - use as a DOM-L0 string
+ node.setAttribute(name, func);
+ }
}
- node.removeAttribute(name);
- var capture = node.hasAttribute('capture' + name.substring(2));
- node.addEventListener(name.substring(2), func, capture);
- if (capture) {
- node.removeAttribute('capture' + name.substring(2));
+ else {
+ // Attribute value is not a single expression use as DOM-L0
+ node.setAttribute(name, this._processString(value, data));
}
}
else {
+ node.removeAttribute(name);
+ // Remove '_' prefix of attribute names so the DOM won't try
+ // to use them before we've processed the template
+ if (name.charAt(0) === '_') {
+ name = name.substring(1);
+ }
+
// Async attributes can only work if the whole attribute is async
+ var replacement;
if (value.indexOf('${') === 0 && value.charAt(value.length - 1) === '}') {
- var replacement = this._envEval(value.slice(2, -1), data, value);
+ replacement = this._envEval(value.slice(2, -1), data, value);
if (replacement && typeof replacement.then === 'function') {
- attrs[i].value = '';
replacement.then(function(newValue) {
- attrs[i].value = newValue;
- }.bind(this)).then(null, function(error) {
- console.error(error.stack);
- });
+ node.setAttribute(name, newValue);
+ }.bind(this)).then(null, console.error);
+ replacement = '';
}
else {
- attrs[i].value = replacement;
+ if (this.options.blankNullUndefined && replacement == null) {
+ replacement = '';
+ }
}
}
else {
- // Replace references in all other attributes
- var newValue = value.replace(this._templateRegion, function(path) {
- var insert = this._envEval(path.slice(2, -1), data, value);
- if (this.options.blankNullUndefined && insert == null) {
- insert = '';
- }
- return insert;
- }.bind(this));
- // Remove '_' prefix of attribute names so the DOM won't try
- // to use them before we've processed the template
- if (name.charAt(0) === '_') {
- node.removeAttribute(name);
- node.setAttribute(name.substring(1), newValue);
- }
- else if (value !== newValue) {
- attrs[i].value = newValue;
- }
+ replacement = this._processString(value, data);
}
+ node.setAttribute(name, replacement);
}
}
finally {
@@ -214,6 +220,16 @@ Templater.prototype.processNode = function(node, data) {
};
/**
+ * Handle attribute values where the output can only be a string
+ */
+Templater.prototype._processString = function(value, data) {
+ return value.replace(this._templateRegion, function(path) {
+ var insert = this._envEval(path.slice(2, -1), data, value);
+ return this.options.blankNullUndefined && insert == null ? '' : insert;
+ }.bind(this));
+};
+
+/**
* Handle <x if="${...}">
* @param node An element with an 'if' attribute
* @param data The data to use with _envEval()
@@ -571,7 +587,8 @@ Templater.prototype._logError = function(message) {
console.log(message);
};
-exports.template = template;
+if (typeof exports !== 'undefined') {
+ exports.template = template;
+}
});
-
View
348 lib/util/promise.js
@@ -18,194 +18,210 @@ define(function(require, exports, module) {
'use strict';
-/**
- * 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); } };
+module.metadata = {
+ "stability": "unstable"
+};
+
+function resolution(value) {
+ /**
+ Returns non-standard compliant (`then` does not returns a promise) promise
+ that resolves to a given `value`. Used just internally only.
+ **/
+ return { then: function then(resolve) { resolve(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); } };
+function rejection(reason) {
+ /**
+ Returns non-standard compliant promise (`then` does not returns a promise)
+ that rejects with a given `reason`. This is used internally only.
+ **/
+ return { then: function then(resolve, reject) { reject(reason) } }
}
-/**
- * Internal utility: Decorates given `f` function, so that on exception promise
- * rejected with thrown error is returned.
- */
function attempt(f) {
- return function effort(input) {
- try {
- return f(input);
- }
+ /**
+ Returns wrapper function that delegates to `f`. If `f` throws then captures
+ error and returns promise that rejects with a thrown error. Otherwise returns
+ return value. (Internal utility)
+ **/
+ return function effort(options) {
+ try { return f(options) }
catch(error) {
- return rejected(error);
+//console.error(error);
+ return rejection(error);
}
- };
+ }
}
-/**
- * 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';
+ /**
+ Returns true if given `value` is promise. Value is assumed to be promise if
+ it implements `then` method.
+ **/
+ 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`.
- *
- * ## 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() {
- // 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;
+function defer(prototype) {
+ /**
+ Returns object containing following properties:
+ - `promise` Eventual value representation implementing CommonJS [Promises/A]
+ (http://wiki.commonjs.org/wiki/Promises/A) API.
+ - `resolve` Single shot function that resolves returned `promise` with a given
+ `value` argument.
+ - `reject` Single shot function that rejects returned `promise` with a given
+ `reason` argument.
+
+ Given `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`.
+
+ ## Examples
+
+ // Simple usage.
+ var deferred = defer()
+ deferred.promise.then(console.log, console.error)
+ deferred.resolve(value)
+
+ // Advanced usage
+ var prototype = {
+ get: function get(name) {
+ return this.then(function(value) {
+ return value[name];
+ })
+ }
+ }
+
+ var foo = defer(prototype)
+ deferred.promise.get('name').then(console.log)
+ deferred.resolve({ name: 'Foo' })
+ //=> 'Foo'
+ */
+ var pending = [], result
+ prototype = (prototype || prototype === null) ? prototype : Object.prototype
+
+ // Create an object implementing promise API.
+ var promise = Object.create(prototype, {
+ then: { value: function then(resolve, reject) {
+ // create a new deferred using a same `prototype`.
+ var deferred = defer(prototype)
+ // If `resolve / reject` callbacks are not provided.
+ resolve = resolve ? attempt(resolve) : resolution
+ reject = reject ? attempt(reject) : rejection
+
+ // Create a listeners for a enclosed promise resolution / rejection that
+ // delegate to an actual callbacks and resolve / reject returned promise.
+ function resolved(value) { deferred.resolve(resolve(value)) }
+ function rejected(reason) { deferred.resolve(reject(reason)) }
+
+ // If promise is pending register listeners. Otherwise forward them to
+ // resulting resolution.
+ if (pending) pending.push([ resolved, rejected ])
+ else result.then(resolved, rejected)
+
+ return deferred.promise
+ }}
+ })
var deferred = {
- promise: {
- then: function then(onFulfill, onError) {
- var deferred = defer();
-
- // Decorate `onFulfill` / `onError` handlers with `attempt`, that
- // way if wrapped handler throws exception decorator will catch and
- // return promise rejected with it, which will cause rejection of
- // `deferred.promise`. If handler is missing, substitute it with an
- // utility function that takes one argument and returns promise
- // fulfilled / rejected with it. This takes care of propagation
- // through the rest of the promise chain.
- onFulfill = onFulfill ? attempt(onFulfill) : fulfilled;
- onError = onError ? attempt(onError) : rejected;
-
- // Create a pair of observers that invoke given handlers & propagate
- // results to `deferred.promise`.
- function resolveDeferred(value) { deferred.resolve(onFulfill(value)); }
- function rejectDeferred(reason) { deferred.resolve(onError(reason)); }
-
- // 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: resolveDeferred, reject: rejectDeferred });
- }
- // Otherwise just forward observer pair right to a `result` promise.
- else {
- result.then(resolveDeferred, rejectDeferred);
- }
-
- return deferred.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`.
- */
+ promise: promise,
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;
+ /**
+ Resolves associated `promise` to a given `value`, unless it's already
+ resolved or rejected.
+ **/
+ if (pending) {
+ // store resolution `value` as a promise (`value` itself may be a
+ // promise), so that all subsequent listeners can be forwarded to it,
+ // which either resolves immediately or forwards if `value` is
+ // a promise.
+ result = isPromise(value) ? value : resolution(value)
+ // forward all pending observers.
+ while (pending.length) result.then.apply(result, pending.shift())
+ // mark promise as resolved.
+ pending = 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));
+ /**
+ Rejects associated `promise` with a given `reason`, unless it's already
+ resolved / rejected.
+ **/
+ deferred.resolve(rejection(reason))
}
- };
+ }
- return deferred;
+ return deferred
}
-exports.defer = defer;
-
-/**
- * Returns a promise resolved to a given `value`.
- */
-function resolve(value) {
- var deferred = defer();
- deferred.resolve(value);
- return deferred.promise;
+exports.defer = defer
+
+function resolve(value, prototype) {
+ /**
+ Returns a promise resolved to a given `value`. Optionally second `prototype`
+ arguments my be provided to be used as a prototype for a returned promise.
+ **/
+ var deferred = defer(prototype)
+ deferred.resolve(value)
+ return deferred.promise
}
-exports.resolve = resolve;
-
-/**
- * Returns a promise rejected with a given `reason`.
- */
-function reject(reason) {
- var deferred = defer();
- deferred.reject(reason);
- return deferred.promise;
+exports.resolve = resolve
+
+function reject(reason, prototype) {
+ /**
+ Returns a promise that is rejected with a given `reason`. Optionally second
+ `prototype` arguments my be provided to be used as a prototype for a returned
+ promise.
+ **/
+ var deferred = defer(prototype)
+ deferred.reject(reason)
+ return deferred.promise
}
-exports.reject = reject;
+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
});

1 comment on commit 9b1a1bb

@campd

r+ through here.

@joewalker

There is some logic, bizarrely. This file exists in mozilla-central, where the header is bigger. By adding these blank lines the line numbers in the 2 files are the same, which makes looking at diffs easier (it may also help debugging on occasion, but that's not really the point)

Please sign in to comment.
Something went wrong with that request. Please try again.