Commit a1ab9293 authored by Cédric Le Ninivin's avatar Cédric Le Ninivin

Update RSVP and RenderJS to latest version

parent 0c29519c
...@@ -665,30 +665,216 @@ if (typeof document.contains !== 'function') { ...@@ -665,30 +665,216 @@ if (typeof document.contains !== 'function') {
return this.documentElement.contains(node); return this.documentElement.contains(node);
} }
} }
;/*! RenderJs */ ;(function (DOMParser) {
/*jslint nomen: true*/ "use strict";
try {
if ((new window.URL("../a", "https://example.com/")).href === "https://example.com/a") {
return;
}
} catch (ignore) {}
var isAbsoluteOrDataURL = /^(?:[a-z]+:)?\/\/|data:/i;
function resolveUrl(url, base_url) {
var doc, base, link,
html = "<!doctype><html><head></head></html>";
if (url && base_url) {
doc = (new DOMParser()).parseFromString(html, 'text/html');
base = doc.createElement('base');
link = doc.createElement('link');
doc.head.appendChild(base);
doc.head.appendChild(link);
base.href = base_url;
link.href = url;
return link.href;
}
return url;
}
function URL(url, base) {
if (base !== undefined) {
if (!isAbsoluteOrDataURL.test(base)) {
throw new TypeError("Failed to construct 'URL': Invalid base URL");
}
url = resolveUrl(url, base);
}
if (!isAbsoluteOrDataURL.test(url)) {
throw new TypeError("Failed to construct 'URL': Invalid URL");
}
this.href = url;
}
URL.prototype.href = "";
if (window.URL && window.URL.createObjectURL) {
URL.createObjectURL = window.URL.createObjectURL;
}
if (window.URL && window.URL.revokeObjectURL) {
URL.revokeObjectURL = window.URL.revokeObjectURL;
}
window.URL = URL;
}(DOMParser));;/*
* Copyright 2012, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/* /*
* renderJs - Generic Gadget library renderer. * renderJs - Generic Gadget library renderer.
* http://www.renderjs.org/documentation * https://renderjs.nexedi.com/
*/ */
(function (document, window, RSVP, DOMParser, Channel, MutationObserver, (function wrapRenderJS(document, window, RSVP, DOMParser, Channel,
Node, FileReader, Blob) { MutationObserver, Node, FileReader, Blob, navigator,
Event, URL) {
"use strict"; "use strict";
// Error propagation in jschannel uses JSON.stringify
// Sadly, ...
// JSON.stringify(new TypeError('lala')) -> '{}'
// Change the browser default behaviour to propagate at least the message
// See https://stackoverflow.com/a/18391400
if (!Error.prototype.hasOwnProperty('toJSON')) {
Object.defineProperty(Error.prototype, 'toJSON', {
value: function () {
var alt = {};
Object.getOwnPropertyNames(this).forEach(function (key) {
alt[key] = this[key];
}, this);
return alt;
},
configurable: true,
writable: true
});
}
/////////////////////////////////////////////////////////////////
// Error
/////////////////////////////////////////////////////////////////
function ScopeError(message) {
this.name = "scopeerror";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Scope Error";
}
ScopeError.prototype = new Error();
ScopeError.prototype.constructor = ScopeError;
function ensurePushableQueue(callback, argument_list, context) {
var result;
try {
result = callback.apply(context, argument_list);
} catch (e) {
return new RSVP.Queue(RSVP.reject(e));
}
if (result instanceof RSVP.Queue) {
return result;
}
return new RSVP.Queue(result);
}
function readBlobAsDataURL(blob) { function readBlobAsDataURL(blob) {
var fr = new FileReader(); var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject) { return new RSVP.Promise(function waitFormDataURLRead(resolve, reject) {
fr.addEventListener("load", function (evt) { fr.addEventListener("load", function handleDataURLRead(evt) {
resolve(evt.target.result); resolve(evt.target.result);
}); });
fr.addEventListener("error", reject); fr.addEventListener("error", reject);
fr.readAsDataURL(blob); fr.readAsDataURL(blob);
}, function () { }, function cancelReadBlobAsDataURL() {
fr.abort(); fr.abort();
}); });
} }
function loopEventListener(target, type, useCapture, callback,
prevent_default) {
//////////////////////////
// Infinite event listener (promise is never resolved)
// eventListener is removed when promise is cancelled/rejected
//////////////////////////
var handle_event_callback,
callback_promise;
if (prevent_default === undefined) {
prevent_default = true;
}
function cancelResolver() {
if ((callback_promise !== undefined) &&
(typeof callback_promise.cancel === "function")) {
callback_promise.cancel();
}
}
function canceller() {
if (handle_event_callback !== undefined) {
target.removeEventListener(type, handle_event_callback, useCapture);
}
cancelResolver();
}
function itsANonResolvableTrap(resolve, reject) {
var result;
handle_event_callback = function handleEventCallback(evt) {
if (prevent_default) {
evt.stopPropagation();
evt.preventDefault();
}
cancelResolver();
try {
result = callback(evt);
} catch (e) {
return reject(e);
}
callback_promise = new RSVP.Queue(result)
.push(undefined, function handleEventCallbackError(error) {
// Prevent rejecting the loop, if the result cancelled itself
if (!(error instanceof RSVP.CancellationError)) {
canceller();
reject(error);
}
});
};
target.addEventListener(type, handle_event_callback, useCapture);
}
return new RSVP.Promise(itsANonResolvableTrap, canceller);
}
function promiseAnimationFrame() {
var request_id;
function canceller() {
window.cancelAnimationFrame(request_id);
}
function resolver(resolve) {
request_id = window.requestAnimationFrame(resolve);
}
return new RSVP.Promise(resolver, canceller);
}
function ajax(url) { function ajax(url) {
var xhr; var xhr;
function resolver(resolve, reject) { function resolver(resolve, reject) {
...@@ -729,14 +915,54 @@ if (typeof document.contains !== 'function') { ...@@ -729,14 +915,54 @@ if (typeof document.contains !== 'function') {
return new RSVP.Promise(resolver, canceller); return new RSVP.Promise(resolver, canceller);
} }
var gadget_model_dict = {}, var gadget_model_defer_dict = {},
javascript_registration_dict = {}, javascript_registration_dict = {},
stylesheet_registration_dict = {}, stylesheet_registration_dict = {},
gadget_loading_klass, gadget_loading_klass_list = [],
loading_klass_promise,
renderJS, renderJS,
Monitor, Monitor,
isAbsoluteOrDataURL = new RegExp('^(?:[a-z]+:)?//|data:', 'i'); Mutex,
scope_increment = 0,
isAbsoluteOrDataURL = new RegExp('^(?:[a-z]+:)?//|data:', 'i'),
is_page_unloaded = false,
error_list = [],
all_dependency_loaded_deferred;
window.addEventListener('error', function handleGlobalError(error) {
error_list.push(error);
});
window.addEventListener('beforeunload', function handleBeforeUnload() {
// XXX If another listener cancel the page unload,
// it will not restore renderJS crash report
is_page_unloaded = true;
});
/////////////////////////////////////////////////////////////////
// Mutex
/////////////////////////////////////////////////////////////////
Mutex = function createMutex() {
if (!(this instanceof Mutex)) {
return new Mutex();
}
this._latest_promise = null;
};
Mutex.prototype = {
constructor: Mutex,
lockAndRun: function lockMutexAndRun(callback) {
var previous_promise = this._latest_promise;
if (previous_promise === null) {
this._latest_promise = RSVP.resolve(callback());
} else {
this._latest_promise = this._latest_promise
.always(function () {
return callback();
});
}
return this._latest_promise;
}
};
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// Helper functions // Helper functions
...@@ -750,23 +976,111 @@ if (typeof document.contains !== 'function') { ...@@ -750,23 +976,111 @@ if (typeof document.contains !== 'function') {
} }
function letsCrash(e) { function letsCrash(e) {
if (e.constructor === XMLHttpRequest) { var i,
e = { body,
readyState: e.readyState, container,
status: e.status, paragraph,
statusText: e.statusText, link,
response_headers: e.getAllResponseHeaders() error;
}; if (is_page_unloaded) {
/*global console*/
console.info('-- Error dropped, as page is unloaded');
console.info(e);
return;
} }
if (e.constructor === Array ||
e.constructor === String || error_list.push(e);
e.constructor === Object) { // Add error handling stack
try { error_list.push(new Error('stopping renderJS'));
e = JSON.stringify(e);
} catch (ignore) { body = document.getElementsByTagName('body')[0];
while (body.firstChild) {
body.removeChild(body.firstChild);
}
container = document.createElement("section");
paragraph = document.createElement("h1");
paragraph.textContent = 'Unhandled Error';
container.appendChild(paragraph);
paragraph = document.createElement("p");
paragraph.textContent = 'Please report this error to the support team';
container.appendChild(paragraph);
paragraph = document.createElement("p");
paragraph.textContent = 'Location: ';
link = document.createElement("a");
link.href = link.textContent = window.location.toString();
paragraph.appendChild(link);
container.appendChild(paragraph);
paragraph = document.createElement("p");
paragraph.textContent = 'User-agent: ' + navigator.userAgent;
container.appendChild(paragraph);
paragraph = document.createElement("p");
paragraph.textContent = 'Date: ' + new Date(Date.now()).toISOString();
container.appendChild(paragraph);
body.appendChild(container);
for (i = 0; i < error_list.length; i += 1) {
error = error_list[i];
if (error instanceof Event) {
error = {
string: error.toString(),
message: error.message,
type: error.type,
target: error.target
};
if (error.target !== undefined) {
error_list.splice(i + 1, 0, error.target);
}
} }
if (error instanceof XMLHttpRequest) {
error = {
message: error.toString(),
readyState: error.readyState,
status: error.status,
statusText: error.statusText,
response: error.response,
responseUrl: error.responseUrl,
response_headers: error.getAllResponseHeaders()
};
}
if (error.constructor === Array ||
error.constructor === String ||
error.constructor === Object) {
try {
error = JSON.stringify(error);
} catch (ignore) {
}
}
container = document.createElement("section");
paragraph = document.createElement("h2");
paragraph.textContent = error.message || error;
container.appendChild(paragraph);
if (error.fileName !== undefined) {
paragraph = document.createElement("p");
paragraph.textContent = 'File: ' +
error.fileName +
': ' + error.lineNumber;
container.appendChild(paragraph);
}
if (error.stack !== undefined) {
paragraph = document.createElement("pre");
paragraph.textContent = 'Stack: ' + error.stack;
container.appendChild(paragraph);
}
body.appendChild(container);
} }
document.getElementsByTagName('body')[0].textContent = e;
// XXX Do not crash the application if it fails // XXX Do not crash the application if it fails
// Where to write the error? // Where to write the error?
/*global console*/ /*global console*/
...@@ -787,12 +1101,11 @@ if (typeof document.contains !== 'function') { ...@@ -787,12 +1101,11 @@ if (typeof document.contains !== 'function') {
ResolvedMonitorError.prototype = new Error(); ResolvedMonitorError.prototype = new Error();
ResolvedMonitorError.prototype.constructor = ResolvedMonitorError; ResolvedMonitorError.prototype.constructor = ResolvedMonitorError;
Monitor = function () { Monitor = function createMonitor() {
var monitor = this, var monitor = this,
promise_list = [], promise_list = [],
promise, promise,
reject, reject,
notify,
resolved; resolved;
if (!(this instanceof Monitor)) { if (!(this instanceof Monitor)) {
...@@ -809,8 +1122,8 @@ if (typeof document.contains !== 'function') { ...@@ -809,8 +1122,8 @@ if (typeof document.contains !== 'function') {
promise_list = []; promise_list = [];
} }
promise = new RSVP.Promise(function (done, fail, progress) { promise = new RSVP.Promise(function promiseMonitor(done, fail) {
reject = function (rejectedReason) { reject = function rejectMonitor(rejectedReason) {
if (resolved) { if (resolved) {
return; return;
} }
...@@ -820,36 +1133,28 @@ if (typeof document.contains !== 'function') { ...@@ -820,36 +1133,28 @@ if (typeof document.contains !== 'function') {
canceller(); canceller();
return fail(rejectedReason); return fail(rejectedReason);
}; };
notify = progress;
}, canceller); }, canceller);
monitor.cancel = function () { monitor.cancel = function cancelMonitor() {
if (resolved) { if (resolved) {
return; return;
} }
resolved = true; resolved = true;
promise.cancel(); promise.cancel();
promise.fail(function (rejectedReason) { promise.fail(function rejectMonitorPromise(rejectedReason) {
monitor.isRejected = true; monitor.isRejected = true;
monitor.rejectedReason = rejectedReason; monitor.rejectedReason = rejectedReason;
}); });
}; };
monitor.then = function () { monitor.then = promise.then.bind(promise);
return promise.then.apply(promise, arguments); monitor.fail = promise.fail.bind(promise);
};
monitor.fail = function () {
return promise.fail.apply(promise, arguments);
};
monitor.monitor = function (promise_to_monitor) { monitor.monitor = function startMonitor(promise_to_monitor) {
if (resolved) { if (resolved) {
throw new ResolvedMonitorError(); throw new ResolvedMonitorError();
} }
var queue = new RSVP.Queue() var queue = new RSVP.Queue(promise_to_monitor)
.push(function () { .push(function handlePromiseToMonitorSuccess(fulfillmentValue) {
return promise_to_monitor;
})
.push(function (fulfillmentValue) {
// Promise to monitor is fullfilled, remove it from the list // Promise to monitor is fullfilled, remove it from the list
var len = promise_list.length, var len = promise_list.length,
sub_promise_to_monitor, sub_promise_to_monitor,
...@@ -863,19 +1168,9 @@ if (typeof document.contains !== 'function') { ...@@ -863,19 +1168,9 @@ if (typeof document.contains !== 'function') {
} }
} }
promise_list = new_promise_list; promise_list = new_promise_list;
}, function (rejectedReason) { }, function handlePromiseToMonitorError(rejectedReason) {
if (rejectedReason instanceof RSVP.CancellationError) {
if (!(promise_to_monitor.isFulfilled &&
promise_to_monitor.isRejected)) {
// The queue could be cancelled before the first push is run
promise_to_monitor.cancel();
}
}
reject(rejectedReason); reject(rejectedReason);
throw rejectedReason; throw rejectedReason;
}, function (notificationValue) {
notify(notificationValue);
return notificationValue;
}); });
promise_list.push(queue); promise_list.push(queue);
...@@ -902,131 +1197,326 @@ if (typeof document.contains !== 'function') { ...@@ -902,131 +1197,326 @@ if (typeof document.contains !== 'function') {
RenderJSGadget.prototype.__required_css_list = []; RenderJSGadget.prototype.__required_css_list = [];
RenderJSGadget.prototype.__required_js_list = []; RenderJSGadget.prototype.__required_js_list = [];
function createMonitor(g) { function deleteGadgetMonitor(g) {
if (g.__monitor !== undefined) { if (g.hasOwnProperty('__monitor')) {
g.__monitor.cancel(); g.__monitor.cancel();
delete g.__monitor;
g.__job_list = [];
} }
}
function createGadgetMonitor(g) {
g.__monitor = new Monitor(); g.__monitor = new Monitor();
g.__monitor.fail(function (error) { g.__job_dict = {};
g.__job_triggered = false;
g.__monitor.fail(function handleGadgetMonitorError(error) {
if (!(error instanceof RSVP.CancellationError)) { if (!(error instanceof RSVP.CancellationError)) {
return g.aq_reportServiceError(error); return g.aq_reportServiceError(error);
} }
}).fail(function (error) { // Crash the application if the acquisition generates an error.
// Crash the application if the acquisition generates an error. }).fail(letsCrash);
return letsCrash(error);
});
} }
function clearGadgetInternalParameters(g) { function clearGadgetInternalParameters(gadget) {
g.__sub_gadget_dict = {}; gadget.__sub_gadget_dict = {};
createMonitor(g); gadget.__job_list = [];
if (gadget.__json_state !== undefined) {
gadget.state = JSON.parse(gadget.__json_state);
}
} }
function loadSubGadgetDOMDeclaration(g) { function loadSubGadgetDOMDeclaration() {
var element_list = g.__element.querySelectorAll('[data-gadget-scope]'), var element_list = this.element.querySelectorAll('[data-gadget-url]'),
element, element,
promise_list = [], promise_list = [],
scope, scope,
url, url,
sandbox, sandbox,
i; i,
context = this;
function prepareReportGadgetDeclarationError(scope) {
return function reportGadgetDeclarationError(error) {
var aq_dict = context.__acquired_method_dict || {},
method_name = 'reportGadgetDeclarationError';
if (aq_dict.hasOwnProperty(method_name)) {
return aq_dict[method_name].apply(context,
[arguments, scope]);
}
throw error;
};
}
for (i = 0; i < element_list.length; i += 1) { for (i = 0; i < element_list.length; i += 1) {
element = element_list[i]; element = element_list[i];
scope = element.getAttribute("data-gadget-scope"); scope = element.getAttribute("data-gadget-scope");
url = element.getAttribute("data-gadget-url"); url = element.getAttribute("data-gadget-url");
sandbox = element.getAttribute("data-gadget-sandbox"); sandbox = element.getAttribute("data-gadget-sandbox");
if ((scope !== null) && (url !== null)) { if (url !== null) {
promise_list.push(g.declareGadget(url, { promise_list.push(
element: element, context.declareGadget(url, {
scope: scope || undefined, element: element,
sandbox: sandbox || undefined scope: scope || undefined,
})); sandbox: sandbox || undefined
})
.push(undefined, prepareReportGadgetDeclarationError(scope))
);
} }
} }
return RSVP.all(promise_list); return RSVP.all(promise_list);
} }
RenderJSGadget.__ready_list = [clearGadgetInternalParameters, RenderJSGadget.__ready_list = [];
loadSubGadgetDOMDeclaration]; RenderJSGadget.ready = function ready(callback) {
RenderJSGadget.ready = function (callback) {
this.__ready_list.push(callback); this.__ready_list.push(callback);
return this; return this;
}; };
RenderJSGadget.setState = function setState(state_dict) {
this.prototype.__json_state = JSON.stringify(state_dict);
return this;
};
RenderJSGadget.onStateChange = function onStateChange(callback) {
this.prototype.__state_change_callback = callback;
return this;
};
RenderJSGadget.__service_list = []; RenderJSGadget.__service_list = [];
RenderJSGadget.declareService = function (callback) { RenderJSGadget.declareService = function declareService(callback) {
this.__service_list.push(callback); this.__service_list.push(callback);
return this; return this;
}; };
RenderJSGadget.onEvent = function onEvent(type, callback, use_capture,
prevent_default) {
this.__service_list.push(function startLoopEventListenerService() {
return loopEventListener(this.element, type, use_capture,
callback.bind(this), prevent_default);
});
return this;
};
RenderJSGadget.onLoop = function onLoop(callback, delay) {
if (delay === undefined) {
delay = 0;
}
this.__service_list.push(function handleServiceCallback() {
var queue_loop = new RSVP.Queue(),
context = this,
wait = function waitForLoopIteration() {
queue_loop
.push(function waitNextOnLoopDelay() {
return RSVP.delay(delay);
})
.push(function waitNextOnLoopAnimationFrame() {
// Only loop when the app has the focus
return promiseAnimationFrame();
})
.push(function executeOnLoopCallback() {
return callback.apply(context, []);
})
.push(wait);
};
wait();
return queue_loop;
});
return this;
};
function runJob(gadget, name, callback, argument_list) {
if (gadget.__job_dict.hasOwnProperty(name)) {
gadget.__job_dict[name].cancel();
}
var job_promise = ensurePushableQueue(callback, argument_list, gadget);
gadget.__job_dict[name] = job_promise;
// gadget.__monitor.monitor(job_promise
gadget.__monitor.monitor(new RSVP.Queue(job_promise)
.push(undefined, function handleJobError(error) {
// Do not crash monitor if the job has been cancelled
// by a new execution
if (!(error instanceof RSVP.CancellationError)) {
throw error;
}
}));
}
function startService(gadget) { function startService(gadget) {
if (((gadget.constructor.__service_list.length === 0) &&
(!gadget.constructor.__job_declared)) ||
(gadget.hasOwnProperty('__monitor'))) {
return;
}
createGadgetMonitor(gadget);
gadget.__monitor.monitor(new RSVP.Queue() gadget.__monitor.monitor(new RSVP.Queue()
.push(function () { .push(function monitorAllServiceList() {
var i, var i,
service_list = gadget.constructor.__service_list; service_list = gadget.constructor.__service_list,
job_list = gadget.__job_list;
for (i = 0; i < service_list.length; i += 1) { for (i = 0; i < service_list.length; i += 1) {
gadget.__monitor.monitor(service_list[i].apply(gadget)); gadget.__monitor.monitor(service_list[i].apply(gadget));
} }
for (i = 0; i < job_list.length; i += 1) {
runJob(gadget, job_list[i][0], job_list[i][1], job_list[i][2]);
}
gadget.__job_list = [];
gadget.__job_triggered = true;
}) })
); );
} }
function registerMethod(gadget_klass, method_name, method_type) {
if (!gadget_klass.hasOwnProperty('__method_type_dict')) {
gadget_klass.__method_type_dict = {};
}
gadget_klass.__method_type_dict[method_name] = method_type;
}
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// RenderJSGadget.declareMethod // RenderJSGadget.declareJob
// gadget internal method, which trigger execution
// of a function inside a service
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
RenderJSGadget.declareMethod = function (name, callback) { RenderJSGadget.declareJob = function declareJob(name, callback) {
this.prototype[name] = function () { this.__job_declared = true;
this.prototype[name] = function triggerJob() {
var context = this, var context = this,
argument_list = arguments; argument_list = arguments;
return new RSVP.Queue() if (context.__job_triggered) {
.push(function () { runJob(context, name, callback, argument_list);
return callback.apply(context, argument_list); } else {
}); context.__job_list.push([name, callback, argument_list]);
}
}; };
registerMethod(this, name, 'job');
// Allow chain
return this;
};
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareMethod
/////////////////////////////////////////////////////////////////
RenderJSGadget.declareMethod = function declareMethod(name, callback,
options) {
this.prototype[name] = function triggerMethod() {
var context = this,
argument_list = arguments,
mutex_name;
function waitForMethodCallback() {
return callback.apply(context, argument_list);
}
if ((options !== undefined) && (options.hasOwnProperty('mutex'))) {
mutex_name = '__mutex_' + options.mutex;
if (!context.hasOwnProperty(mutex_name)) {
context[mutex_name] = new Mutex();
}
return ensurePushableQueue(context[mutex_name].lockAndRun,
[waitForMethodCallback],
context[mutex_name]);
}
return ensurePushableQueue(callback, argument_list, context);
};
registerMethod(this, name, 'method');
// Allow chain // Allow chain
return this; return this;
}; };
RenderJSGadget RenderJSGadget
.declareMethod('getInterfaceList', function () { .declareMethod('getInterfaceList', function getInterfaceList() {
// Returns the list of gadget prototype // Returns the list of gadget prototype
return this.__interface_list; return this.__interface_list;
}) })
.declareMethod('getRequiredCSSList', function () { .declareMethod('getMethodList', function getMethodList(type) {
// Returns the list of gadget methods
var key,
method_list = [],
method_dict = this.constructor.__method_type_dict || {};
for (key in method_dict) {
if (method_dict.hasOwnProperty(key)) {
if ((type === undefined) ||
(type === method_dict[key])) {
method_list.push(key);
}
}
}
return method_list;
})
.declareMethod('getRequiredCSSList', function getRequiredCSSList() {
// Returns a list of CSS required by the gadget // Returns a list of CSS required by the gadget
return this.__required_css_list; return this.__required_css_list;
}) })
.declareMethod('getRequiredJSList', function () { .declareMethod('getRequiredJSList', function getRequiredJSList() {
// Returns a list of JS required by the gadget // Returns a list of JS required by the gadget
return this.__required_js_list; return this.__required_js_list;
}) })
.declareMethod('getPath', function () { .declareMethod('getPath', function getPath() {
// Returns the path of the code of a gadget // Returns the path of the code of a gadget
return this.__path; return this.__path;
}) })
.declareMethod('getTitle', function () { .declareMethod('getTitle', function getTitle() {
// Returns the title of a gadget // Returns the title of a gadget
return this.__title; return this.__title;
}) })
.declareMethod('getElement', function () { .declareMethod('getElement', function getElement() {
// Returns the DOM Element of a gadget // Returns the DOM Element of a gadget
if (this.__element === undefined) { // XXX Kept for compatibility. Use element property directly
if (this.element === undefined) {
throw new Error("No element defined"); throw new Error("No element defined");
} }
return this.__element; return this.element;
}); })
.declareMethod('changeState', function changeState(state_dict) {
var context = this,
key,
modified = false,
previous_cancelled = context.hasOwnProperty('__modification_dict'),
modification_dict;
if (previous_cancelled) {
modification_dict = context.__modification_dict;
modified = true;
} else {
modification_dict = {};
}
for (key in state_dict) {
if (state_dict.hasOwnProperty(key) &&
(state_dict[key] !== context.state[key])) {
context.state[key] = state_dict[key];
modification_dict[key] = state_dict[key];
modified = true;
}
}
if (modified && context.__state_change_callback !== undefined) {
context.__modification_dict = modification_dict;
return ensurePushableQueue(
context.__state_change_callback,
[modification_dict],
context
)
.push(function handleStateChangeSuccess(result) {
delete context.__modification_dict;
return result;
});
}
}, {mutex: 'changestate'});
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// RenderJSGadget.declareAcquiredMethod // RenderJSGadget.declareAcquiredMethod
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
function acquire(child_gadget, method_name, argument_list) { function acquire(child_gadget, method_name, argument_list) {
var gadget = this, var gadget = this,
// Do not specify default __acquired_method_dict on prototype
// to prevent modifying this default value (with
// allowPublicAcquiredMethod for example)
aq_dict = gadget.__acquired_method_dict || {},
key, key,
gadget_scope; gadget_scope;
if (!aq_dict.hasOwnProperty(method_name)) {
// "aq_dynamic is not defined"
return gadget.__aq_parent(method_name, argument_list);
}
for (key in gadget.__sub_gadget_dict) { for (key in gadget.__sub_gadget_dict) {
if (gadget.__sub_gadget_dict.hasOwnProperty(key)) { if (gadget.__sub_gadget_dict.hasOwnProperty(key)) {
if (gadget.__sub_gadget_dict[key] === child_gadget) { if (gadget.__sub_gadget_dict[key] === child_gadget) {
...@@ -1034,19 +1524,13 @@ if (typeof document.contains !== 'function') { ...@@ -1034,19 +1524,13 @@ if (typeof document.contains !== 'function') {
} }
} }
} }
return new RSVP.Queue()
.push(function () { return ensurePushableQueue(
// Do not specify default __acquired_method_dict on prototype aq_dict[method_name],
// to prevent modifying this default value (with [argument_list, gadget_scope],
// allowPublicAcquiredMethod for example) gadget
var aq_dict = gadget.__acquired_method_dict || {}; )
if (aq_dict.hasOwnProperty(method_name)) { .push(undefined, function handleAcquireMethodError(error) {
return aq_dict[method_name].apply(gadget,
[argument_list, gadget_scope]);
}
throw new renderJS.AcquisitionError("aq_dynamic is not defined");
})
.push(undefined, function (error) {
if (error instanceof renderJS.AcquisitionError) { if (error instanceof renderJS.AcquisitionError) {
return gadget.__aq_parent(method_name, argument_list); return gadget.__aq_parent(method_name, argument_list);
} }
...@@ -1055,27 +1539,30 @@ if (typeof document.contains !== 'function') { ...@@ -1055,27 +1539,30 @@ if (typeof document.contains !== 'function') {
} }
RenderJSGadget.declareAcquiredMethod = RenderJSGadget.declareAcquiredMethod =
function (name, method_name_to_acquire) { function declareAcquiredMethod(name, method_name_to_acquire) {
this.prototype[name] = function () { this.prototype[name] = function acquireMethod() {
var argument_list = Array.prototype.slice.call(arguments, 0), var argument_list = Array.prototype.slice.call(arguments, 0),
gadget = this; gadget = this;
return new RSVP.Queue() return ensurePushableQueue(
.push(function () { gadget.__aq_parent,
return gadget.__aq_parent(method_name_to_acquire, argument_list); [method_name_to_acquire, argument_list],
}); gadget
);
}; };
registerMethod(this, name, 'acquired_method');
// Allow chain // Allow chain
return this; return this;
}; };
RenderJSGadget.declareAcquiredMethod("aq_reportServiceError", RenderJSGadget.declareAcquiredMethod("aq_reportServiceError",
"reportServiceError"); "reportServiceError");
RenderJSGadget.declareAcquiredMethod("aq_reportGadgetDeclarationError",
"reportGadgetDeclarationError");
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// RenderJSGadget.allowPublicAcquisition // RenderJSGadget.allowPublicAcquisition
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
RenderJSGadget.allowPublicAcquisition = RenderJSGadget.allowPublicAcquisition =
function (method_name, callback) { function allowPublicAcquisition(method_name, callback) {
this.prototype.__acquired_method_dict[method_name] = callback; this.prototype.__acquired_method_dict[method_name] = callback;
// Allow chain // Allow chain
...@@ -1084,10 +1571,11 @@ if (typeof document.contains !== 'function') { ...@@ -1084,10 +1571,11 @@ if (typeof document.contains !== 'function') {
// Set aq_parent on gadget_instance which call acquire on parent_gadget // Set aq_parent on gadget_instance which call acquire on parent_gadget
function setAqParent(gadget_instance, parent_gadget) { function setAqParent(gadget_instance, parent_gadget) {
gadget_instance.__aq_parent = function (method_name, argument_list) { gadget_instance.__aq_parent =
return acquire.apply(parent_gadget, [gadget_instance, method_name, function __aq_parent(method_name, argument_list) {
argument_list]); return acquire.apply(parent_gadget, [gadget_instance, method_name,
}; argument_list]);
};
} }
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
...@@ -1100,71 +1588,69 @@ if (typeof document.contains !== 'function') { ...@@ -1100,71 +1588,69 @@ if (typeof document.contains !== 'function') {
} }
RenderJSGadget.call(this); RenderJSGadget.call(this);
} }
RenderJSEmbeddedGadget.__ready_list = RenderJSGadget.__ready_list.slice(); RenderJSEmbeddedGadget.__ready_list = [];
RenderJSEmbeddedGadget.__service_list = RenderJSEmbeddedGadget.__service_list =
RenderJSGadget.__service_list.slice(); RenderJSGadget.__service_list.slice();
RenderJSEmbeddedGadget.ready = RenderJSEmbeddedGadget.ready =
RenderJSGadget.ready; RenderJSGadget.ready;
RenderJSEmbeddedGadget.setState =
RenderJSGadget.setState;
RenderJSEmbeddedGadget.onStateChange =
RenderJSGadget.onStateChange;
RenderJSEmbeddedGadget.declareService = RenderJSEmbeddedGadget.declareService =
RenderJSGadget.declareService; RenderJSGadget.declareService;
RenderJSEmbeddedGadget.onEvent =
RenderJSGadget.onEvent;
RenderJSEmbeddedGadget.onLoop =
RenderJSGadget.onLoop;
RenderJSEmbeddedGadget.prototype = new RenderJSGadget(); RenderJSEmbeddedGadget.prototype = new RenderJSGadget();
RenderJSEmbeddedGadget.prototype.constructor = RenderJSEmbeddedGadget; RenderJSEmbeddedGadget.prototype.constructor = RenderJSEmbeddedGadget;
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// privateDeclarePublicGadget // privateDeclarePublicGadget
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
function privateDeclarePublicGadget(url, options, parent_gadget) { function createPrivateInstanceFromKlass(Klass, options, parent_gadget,
var gadget_instance; old_element) {
if (options.element === undefined) { // Get the gadget class and instanciate it
options.element = document.createElement("div"); var i,
gadget_instance,
template_node_list = Klass.__template_element.body.childNodes,
fragment = document.createDocumentFragment();
gadget_instance = new Klass();
gadget_instance.element = options.element;
gadget_instance.state = {};
for (i = 0; i < template_node_list.length; i += 1) {
fragment.appendChild(
template_node_list[i].cloneNode(true)
);
} }
gadget_instance.element.appendChild(fragment);
function loadDependency(method, url) { setAqParent(gadget_instance, parent_gadget);
return function () { clearGadgetInternalParameters(gadget_instance);
return method(url); if (old_element !== undefined) {
}; // Add gadget to the DOM if needed
// Do it when all DOM modifications are done
old_element.parentNode.replaceChild(options.element,
old_element);
} }
return gadget_instance;
}
return new RSVP.Queue() function privateDeclarePublicGadget(url, options, parent_gadget,
.push(function () { old_element) {
return renderJS.declareGadgetKlass(url); var klass = renderJS.declareGadgetKlass(url);
}) // gadget loading should not be interrupted
// Get the gadget class and instanciate it // if not, gadget's definition will not be complete
.push(function (Klass) { //.then will return another promise
var i, //so loading_klass_promise can't be cancel
template_node_list = Klass.__template_element.body.childNodes; if (typeof klass.then === 'function') {
gadget_loading_klass = Klass; return klass.then(function createAsyncPrivateInstanceFromKlass(Klass) {
gadget_instance = new Klass(); return createPrivateInstanceFromKlass(Klass, options, parent_gadget,
gadget_instance.__element = options.element; old_element);
for (i = 0; i < template_node_list.length; i += 1) {
gadget_instance.__element.appendChild(
template_node_list[i].cloneNode(true)
);
}
setAqParent(gadget_instance, parent_gadget);
// Load dependencies if needed
return RSVP.all([
gadget_instance.getRequiredJSList(),
gadget_instance.getRequiredCSSList()
]);
})
// Load all JS/CSS
.push(function (all_list) {
var q = new RSVP.Queue(),
i;
// Load JS
for (i = 0; i < all_list[0].length; i += 1) {
q.push(loadDependency(renderJS.declareJS, all_list[0][i]));
}
// Load CSS
for (i = 0; i < all_list[1].length; i += 1) {
q.push(loadDependency(renderJS.declareCSS, all_list[1][i]));
}
return q;
})
.push(function () {
return gadget_instance;
}); });
}
return createPrivateInstanceFromKlass(klass, options, parent_gadget,
old_element);
} }
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
...@@ -1176,29 +1662,38 @@ if (typeof document.contains !== 'function') { ...@@ -1176,29 +1662,38 @@ if (typeof document.contains !== 'function') {
} }
RenderJSGadget.call(this); RenderJSGadget.call(this);
} }
RenderJSIframeGadget.__ready_list = RenderJSGadget.__ready_list.slice(); RenderJSIframeGadget.__ready_list = [];
RenderJSIframeGadget.ready = RenderJSIframeGadget.ready =
RenderJSGadget.ready; RenderJSGadget.ready;
RenderJSIframeGadget.__service_list = RenderJSGadget.__service_list.slice(); RenderJSIframeGadget.setState =
RenderJSGadget.setState;
RenderJSIframeGadget.onStateChange =
RenderJSGadget.onStateChange;
RenderJSIframeGadget.__service_list = [];
RenderJSIframeGadget.declareService = RenderJSIframeGadget.declareService =
RenderJSGadget.declareService; RenderJSGadget.declareService;
RenderJSIframeGadget.onEvent =
RenderJSGadget.onEvent;
RenderJSIframeGadget.onLoop =
RenderJSGadget.onLoop;
RenderJSIframeGadget.prototype = new RenderJSGadget(); RenderJSIframeGadget.prototype = new RenderJSGadget();
RenderJSIframeGadget.prototype.constructor = RenderJSIframeGadget; RenderJSIframeGadget.prototype.constructor = RenderJSIframeGadget;
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// privateDeclareIframeGadget // privateDeclareIframeGadget
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
function privateDeclareIframeGadget(url, options, parent_gadget) { function privateDeclareIframeGadget(url, options, parent_gadget,
old_element) {
var gadget_instance, var gadget_instance,
iframe, iframe,
iframe_loading_deferred = RSVP.defer(); iframe_loading_deferred = RSVP.defer();
if (options.element === undefined) { if (old_element === undefined) {
throw new Error("DOM element is required to create Iframe Gadget " + throw new Error("DOM element is required to create Iframe Gadget " +
url); url);
} }
// Check if the element is attached to the DOM // Check if the element is attached to the DOM
if (!document.contains(options.element)) { if (!document.contains(old_element)) {
throw new Error("The parent element is not attached to the DOM for " + throw new Error("The parent element is not attached to the DOM for " +
url); url);
} }
...@@ -1206,86 +1701,103 @@ if (typeof document.contains !== 'function') { ...@@ -1206,86 +1701,103 @@ if (typeof document.contains !== 'function') {
gadget_instance = new RenderJSIframeGadget(); gadget_instance = new RenderJSIframeGadget();
setAqParent(gadget_instance, parent_gadget); setAqParent(gadget_instance, parent_gadget);
iframe = document.createElement("iframe"); iframe = document.createElement("iframe");
iframe.addEventListener('error', function handleIframeError(error) {
iframe_loading_deferred.reject(error);
});
iframe.addEventListener('load', function handleIframeLoad() {
return RSVP.timeout(5000)
.fail(function triggerIframeTimeout() {
iframe_loading_deferred.reject(
new Error('Timeout while loading: ' + url)
);
});
});
// gadget_instance.element.setAttribute("seamless", "seamless"); // gadget_instance.element.setAttribute("seamless", "seamless");
iframe.setAttribute("src", url); iframe.setAttribute("src", url);
gadget_instance.__path = url; gadget_instance.__path = url;
gadget_instance.__element = options.element; gadget_instance.element = options.element;
// Attach it to the DOM gadget_instance.state = {};
options.element.appendChild(iframe); options.element.appendChild(iframe);
clearGadgetInternalParameters(gadget_instance);
// Add gadget to the DOM if needed
// Do it when all DOM modifications are done
old_element.parentNode.replaceChild(options.element,
old_element);
// XXX Manage unbind when deleting the gadget // XXX Manage unbind when deleting the gadget
// Create the communication channel with the iframe // Create the communication channel with the iframe
gadget_instance.__chan = Channel.build({ gadget_instance.__chan = Channel.build({
window: iframe.contentWindow, window: iframe.contentWindow,
origin: "*", // origin: (new URL(url, window.location)).origin,
origin: '*',
scope: "renderJS" scope: "renderJS"
}); });
// Create new method from the declareMethod call inside the iframe // Create new method from the declareMethod call inside the iframe
gadget_instance.__chan.bind("declareMethod", gadget_instance.__chan.bind(
function (trans, method_name) { "declareMethod",
gadget_instance[method_name] = function () { function handleChannelDeclareMethod(trans, method_name) {
gadget_instance[method_name] = function triggerChannelDeclareMethod() {
var argument_list = arguments, var argument_list = arguments,
wait_promise = new RSVP.Promise(function (resolve, reject) { wait_promise = new RSVP.Promise(
gadget_instance.__chan.call({ function handleChannelCall(resolve, reject) {
method: "methodCall", gadget_instance.__chan.call({
params: [ method: "methodCall",
method_name, params: [
Array.prototype.slice.call(argument_list, 0)], method_name,
success: function (s) { Array.prototype.slice.call(argument_list, 0)],
resolve(s); success: resolve,
}, error: reject
error: function (e) { });
reject(e); }
} );
});
}); return ensurePushableQueue(function waitForChannelCall() {
return new RSVP.Queue() return wait_promise;
.push(function () { });
return wait_promise;
});
}; };
return "OK"; return "OK";
}); }
);
// Wait for the iframe to be loaded before continuing // Wait for the iframe to be loaded before continuing
gadget_instance.__chan.bind("ready", function (trans) { gadget_instance.__chan.bind("ready", function handleChannelReady(trans) {
iframe_loading_deferred.resolve(gadget_instance); iframe_loading_deferred.resolve(gadget_instance);
return "OK"; return "OK";
}); });
gadget_instance.__chan.bind("failed", function (trans, params) { gadget_instance.__chan.bind("failed",
iframe_loading_deferred.reject(params); function handleChannelFail(trans, params) {
return "OK"; iframe_loading_deferred.reject(params);
}); return "OK";
gadget_instance.__chan.bind("acquire", function (trans, params) { });
gadget_instance.__aq_parent.apply(gadget_instance, params) gadget_instance.__chan.bind("acquire",
.then(function (g) { function handleChannelAcquire(trans, params) {
trans.complete(g); new RSVP.Queue()
}).fail(function (e) { .push(function () {
trans.error(e.toString()); return gadget_instance.__aq_parent.apply(gadget_instance, params);
}); })
trans.delayReturn(true); .then(trans.complete)
}); .fail(function handleChannelAcquireError(e) {
trans.error(e.toString());
});
trans.delayReturn(true);
});
return RSVP.any([ return iframe_loading_deferred.promise;
iframe_loading_deferred.promise,
// Timeout to prevent non renderJS embeddable gadget
// XXX Maybe using iframe.onload/onerror would be safer?
RSVP.timeout(5000)
]);
} }
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// privateDeclareDataUrlGadget // privateDeclareDataUrlGadget
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
function privateDeclareDataUrlGadget(url, options, parent_gadget) { function privateDeclareDataUrlGadget(url, options, parent_gadget,
old_element) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function waitForDataUrlAjax() {
return ajax(url); return ajax(url);
}) })
.push(function (xhr) { .push(function handleDataURLAjaxResponse(xhr) {
// Insert a "base" element, in order to resolve all relative links // Insert a "base" element, in order to resolve all relative links
// which could get broken with a data url // which could get broken with a data url
var doc = (new DOMParser()).parseFromString(xhr.responseText, var doc = (new DOMParser()).parseFromString(xhr.responseText,
...@@ -1298,20 +1810,63 @@ if (typeof document.contains !== 'function') { ...@@ -1298,20 +1810,63 @@ if (typeof document.contains !== 'function') {
{type: "text/html;charset=UTF-8"}); {type: "text/html;charset=UTF-8"});
return readBlobAsDataURL(blob); return readBlobAsDataURL(blob);
}) })
.push(function (data_url) { .push(function handleDataURL(data_url) {
return privateDeclareIframeGadget(data_url, options, parent_gadget); return privateDeclareIframeGadget(data_url, options, parent_gadget,
old_element);
}); });
} }
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// RenderJSGadget.declareGadget // RenderJSGadget.declareGadget
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
function setGadgetInstanceHTMLContext(gadget_instance, options,
parent_gadget, url,
old_element, scope) {
var i,
queue;
function ready_executable_wrapper(fct) {
return function executeReadyWrapper() {
return fct.call(gadget_instance, gadget_instance);
};
}
function ready_wrapper() {
// Always set the parent reference when all ready are finished
// in case the gadget declaration is cancelled
// (and ready are not finished)
gadget_instance.element._gadget = gadget_instance;
parent_gadget.__sub_gadget_dict[scope] = gadget_instance;
if (document.contains(gadget_instance.element)) {
startService(gadget_instance);
}
// Always return the gadget instance after ready function
return gadget_instance;
}
if (gadget_instance.constructor.__ready_list.length) {
queue = new RSVP.Queue();
// Trigger calling of all ready callback
for (i = 0; i < gadget_instance.constructor.__ready_list.length;
i += 1) {
// Put a timeout?
queue.push(ready_executable_wrapper(
gadget_instance.constructor.__ready_list[i]
));
}
queue.push(ready_wrapper);
return queue;
}
return ready_wrapper();
}
RenderJSGadget RenderJSGadget
.declareMethod('declareGadget', function (url, options) { .declareMethod('declareGadget', function declareGadget(url, options) {
var queue, var parent_gadget = this,
parent_gadget = this, method,
local_loading_klass_promise, result,
previous_loading_klass_promise = loading_klass_promise; scope,
old_element;
if (options === undefined) { if (options === undefined) {
options = {}; options = {};
...@@ -1319,99 +1874,75 @@ if (typeof document.contains !== 'function') { ...@@ -1319,99 +1874,75 @@ if (typeof document.contains !== 'function') {
if (options.sandbox === undefined) { if (options.sandbox === undefined) {
options.sandbox = "public"; options.sandbox = "public";
} }
if (options.element === undefined) {
options.element = document.createElement('div');
} else if (typeof options.element === 'string') {
options.element = document.createElement(options.element);
} else if (options.element.parentNode) {
old_element = options.element;
// Clean up the element content
// Remove all existing event listener
options.element = old_element.cloneNode(false);
} else {
throw new Error('No need to manually provide a DOM element ' +
'without a parentNode: ' + url);
}
// transform url to absolute url if it is relative // transform url to absolute url if it is relative
url = renderJS.getAbsoluteURL(url, this.__path); url = renderJS.getAbsoluteURL(url, this.__path);
// Change the global variable to update the loading queue
loading_klass_promise = new RSVP.Queue()
// Wait for previous gadget loading to finish first
.push(function () {
return previous_loading_klass_promise;
})
.push(undefined, function () {
// Forget previous declareGadget error
return;
})
.push(function () {
var method;
if (options.sandbox === "public") {
method = privateDeclarePublicGadget;
} else if (options.sandbox === "iframe") {
method = privateDeclareIframeGadget;
} else if (options.sandbox === "dataurl") {
method = privateDeclareDataUrlGadget;
} else {
throw new Error("Unsupported sandbox options '" +
options.sandbox + "'");
}
return method(url, options, parent_gadget);
})
// Set the HTML context
.push(function (gadget_instance) {
// Drop the current loading klass info used by selector
gadget_loading_klass = undefined;
return gadget_instance;
})
.push(undefined, function (e) {
// Drop the current loading klass info used by selector
// even in case of error
gadget_loading_klass = undefined;
throw e;
});
local_loading_klass_promise = loading_klass_promise;
queue = new RSVP.Queue()
.push(function () {
return local_loading_klass_promise;
})
// Set the HTML context
.push(function (gadget_instance) {
var i;
// Trigger calling of all ready callback
function ready_wrapper() {
return gadget_instance;
}
for (i = 0; i < gadget_instance.constructor.__ready_list.length;
i += 1) {
// Put a timeout?
queue.push(gadget_instance.constructor.__ready_list[i]);
// Always return the gadget instance after ready function
queue.push(ready_wrapper);
}
// Store local reference to the gadget instance // Store local reference to the gadget instance
if (options.scope !== undefined) { scope = options.scope;
parent_gadget.__sub_gadget_dict[options.scope] = gadget_instance; if (scope === undefined) {
gadget_instance.__element.setAttribute("data-gadget-scope", scope = 'RJS_' + scope_increment;
options.scope); scope_increment += 1;
} while (parent_gadget.__sub_gadget_dict.hasOwnProperty(scope)) {
scope = 'RJS_' + scope_increment;
// Put some attribute to ease page layout comprehension scope_increment += 1;
gadget_instance.__element.setAttribute("data-gadget-url", url); }
gadget_instance.__element.setAttribute("data-gadget-sandbox", }
options.sandbox); options.element.setAttribute("data-gadget-scope", scope);
gadget_instance.__element._gadget = gadget_instance;
// Put some attribute to ease page layout comprehension
if (document.contains(gadget_instance.__element)) { options.element.setAttribute("data-gadget-url", url);
// Put a timeout options.element.setAttribute("data-gadget-sandbox", options.sandbox);
queue.push(startService);
} if (options.sandbox === "public") {
// Always return the gadget instance after ready function method = privateDeclarePublicGadget;
queue.push(ready_wrapper); } else if (options.sandbox === "iframe") {
method = privateDeclareIframeGadget;
return gadget_instance; } else if (options.sandbox === "dataurl") {
}); method = privateDeclareDataUrlGadget;
return queue; } else {
}) throw new Error("Unsupported sandbox options '" +
.declareMethod('getDeclaredGadget', function (gadget_scope) { options.sandbox + "'");
if (!this.__sub_gadget_dict.hasOwnProperty(gadget_scope)) {
throw new Error("Gadget scope '" + gadget_scope + "' is not known.");
} }
return this.__sub_gadget_dict[gadget_scope]; result = method(url, options, parent_gadget, old_element);
// Set the HTML context
if (typeof result.then === 'function') {
return new RSVP.Queue(result)
.push(function setAsyncGadgetInstanceHTMLContext(gadget_instance) {
return setGadgetInstanceHTMLContext(gadget_instance, options,
parent_gadget, url,
old_element, scope);
});
}
return setGadgetInstanceHTMLContext(result, options,
parent_gadget, url, old_element,
scope);
}) })
.declareMethod('dropGadget', function (gadget_scope) { .declareMethod('getDeclaredGadget',
function getDeclaredGadget(gadget_scope) {
if (!this.__sub_gadget_dict.hasOwnProperty(gadget_scope)) {
throw new ScopeError("Gadget scope '" + gadget_scope +
"' is not known.");
}
return this.__sub_gadget_dict[gadget_scope];
})
.declareMethod('dropGadget', function dropGadget(gadget_scope) {
if (!this.__sub_gadget_dict.hasOwnProperty(gadget_scope)) { if (!this.__sub_gadget_dict.hasOwnProperty(gadget_scope)) {
throw new Error("Gadget scope '" + gadget_scope + "' is not known."); throw new ScopeError("Gadget scope '" + gadget_scope +
"' is not known.");
} }
// http://perfectionkills.com/understanding-delete/ // http://perfectionkills.com/understanding-delete/
delete this.__sub_gadget_dict[gadget_scope]; delete this.__sub_gadget_dict[gadget_scope];
...@@ -1420,12 +1951,12 @@ if (typeof document.contains !== 'function') { ...@@ -1420,12 +1951,12 @@ if (typeof document.contains !== 'function') {
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// renderJS selector // renderJS selector
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
renderJS = function (selector) { renderJS = function getLoadingGadget(selector) {
var result; var result;
if (selector === window) { if (selector === window) {
// window is the 'this' value when loading a javascript file // window is the 'this' value when loading a javascript file
// In this case, use the current loading gadget constructor // In this case, use the current loading gadget constructor
result = gadget_loading_klass; result = gadget_loading_klass_list[0];
} }
if (result === undefined) { if (result === undefined) {
throw new Error("Unknown selector '" + selector + "'"); throw new Error("Unknown selector '" + selector + "'");
...@@ -1436,7 +1967,7 @@ if (typeof document.contains !== 'function') { ...@@ -1436,7 +1967,7 @@ if (typeof document.contains !== 'function') {
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// renderJS.AcquisitionError // renderJS.AcquisitionError
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
renderJS.AcquisitionError = function (message) { renderJS.AcquisitionError = function createAcquisitionError(message) {
this.name = "AcquisitionError"; this.name = "AcquisitionError";
if ((message !== undefined) && (typeof message !== "string")) { if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.'); throw new TypeError('You must pass a string.');
...@@ -1450,19 +1981,9 @@ if (typeof document.contains !== 'function') { ...@@ -1450,19 +1981,9 @@ if (typeof document.contains !== 'function') {
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// renderJS.getAbsoluteURL // renderJS.getAbsoluteURL
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
renderJS.getAbsoluteURL = function (url, base_url) { renderJS.getAbsoluteURL = function getAbsoluteURL(url, base_url) {
var doc, base, link, if (base_url && url) {
html = "<!doctype><html><head></head></html>"; return new URL(url, base_url).href;
if (url && base_url && !isAbsoluteOrDataURL.test(url)) {
doc = (new DOMParser()).parseFromString(html, 'text/html');
base = doc.createElement('base');
link = doc.createElement('link');
doc.head.appendChild(base);
doc.head.appendChild(link);
base.href = base_url;
link.href = url;
return link.href;
} }
return url; return url;
}; };
...@@ -1470,27 +1991,39 @@ if (typeof document.contains !== 'function') { ...@@ -1470,27 +1991,39 @@ if (typeof document.contains !== 'function') {
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// renderJS.declareJS // renderJS.declareJS
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
renderJS.declareJS = function (url) { renderJS.declareJS = function declareJS(url, container, pop) {
// https://www.html5rocks.com/en/tutorials/speed/script-loading/
// Prevent infinite recursion if loading render.js // Prevent infinite recursion if loading render.js
// more than once // more than once
var result; var result;
if (javascript_registration_dict.hasOwnProperty(url)) { if (javascript_registration_dict.hasOwnProperty(url)) {
result = RSVP.resolve(); result = RSVP.resolve();
} else { } else {
result = new RSVP.Promise(function (resolve, reject) { javascript_registration_dict[url] = null;
var newScript; result = new RSVP.Promise(
newScript = document.createElement('script'); function waitForJSLoadEvent(resolve, reject) {
newScript.type = 'text/javascript'; var newScript;
newScript.src = url; newScript = document.createElement('script');
newScript.onload = function () { newScript.async = false;
javascript_registration_dict[url] = null; newScript.type = 'text/javascript';
resolve(); newScript.onload = function triggerJSLoaded() {
}; if (pop === true) {
newScript.onerror = function (e) { // Drop the current loading klass info used by selector
reject(e); gadget_loading_klass_list.shift();
}; }
document.head.appendChild(newScript); resolve();
}); };
newScript.onerror = function triggerJSNotLoaded(e) {
if (pop === true) {
// Drop the current loading klass info used by selector
gadget_loading_klass_list.shift();
}
reject(e);
};
newScript.src = url;
container.appendChild(newScript);
}
);
} }
return result; return result;
}; };
...@@ -1498,7 +2031,7 @@ if (typeof document.contains !== 'function') { ...@@ -1498,7 +2031,7 @@ if (typeof document.contains !== 'function') {
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// renderJS.declareCSS // renderJS.declareCSS
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
renderJS.declareCSS = function (url) { renderJS.declareCSS = function declareCSS(url, container) {
// https://github.com/furf/jquery-getCSS/blob/master/jquery.getCSS.js // https://github.com/furf/jquery-getCSS/blob/master/jquery.getCSS.js
// No way to cleanly check if a css has been loaded // No way to cleanly check if a css has been loaded
// So, always resolve the promise... // So, always resolve the promise...
...@@ -1507,20 +2040,18 @@ if (typeof document.contains !== 'function') { ...@@ -1507,20 +2040,18 @@ if (typeof document.contains !== 'function') {
if (stylesheet_registration_dict.hasOwnProperty(url)) { if (stylesheet_registration_dict.hasOwnProperty(url)) {
result = RSVP.resolve(); result = RSVP.resolve();
} else { } else {
result = new RSVP.Promise(function (resolve, reject) { result = new RSVP.Promise(function waitForCSSLoadEvent(resolve, reject) {
var link; var link;
link = document.createElement('link'); link = document.createElement('link');
link.rel = 'stylesheet'; link.rel = 'stylesheet';
link.type = 'text/css'; link.type = 'text/css';
link.href = url; link.href = url;
link.onload = function () { link.onload = function triggerCSSLoaded() {
stylesheet_registration_dict[url] = null; stylesheet_registration_dict[url] = null;
resolve(); resolve();
}; };
link.onerror = function (e) { link.onerror = reject;
reject(e); container.appendChild(link);
};
document.head.appendChild(link);
}); });
} }
return result; return result;
...@@ -1529,77 +2060,146 @@ if (typeof document.contains !== 'function') { ...@@ -1529,77 +2060,146 @@ if (typeof document.contains !== 'function') {
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// renderJS.declareGadgetKlass // renderJS.declareGadgetKlass
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
renderJS.declareGadgetKlass = function (url) {
var result;
function parse(xhr) { function parse(xhr, url) {
var tmp_constructor, var tmp_constructor,
key, key,
parsed_html; parsed_html;
if (!gadget_model_dict.hasOwnProperty(url)) { // Class inheritance
// Class inheritance tmp_constructor = function createSuperKlass() {
tmp_constructor = function () { RenderJSGadget.call(this);
RenderJSGadget.call(this); };
}; tmp_constructor.__ready_list = [];
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice(); tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice(); tmp_constructor.declareMethod =
tmp_constructor.declareMethod = RenderJSGadget.declareMethod;
RenderJSGadget.declareMethod; tmp_constructor.declareJob =
tmp_constructor.declareAcquiredMethod = RenderJSGadget.declareJob;
RenderJSGadget.declareAcquiredMethod; tmp_constructor.declareAcquiredMethod =
tmp_constructor.allowPublicAcquisition = RenderJSGadget.declareAcquiredMethod;
RenderJSGadget.allowPublicAcquisition; tmp_constructor.allowPublicAcquisition =
tmp_constructor.ready = RenderJSGadget.allowPublicAcquisition;
RenderJSGadget.ready; tmp_constructor.ready =
tmp_constructor.declareService = RenderJSGadget.ready;
RenderJSGadget.declareService; tmp_constructor.setState =
tmp_constructor.prototype = new RenderJSGadget(); RenderJSGadget.setState;
tmp_constructor.prototype.constructor = tmp_constructor; tmp_constructor.onStateChange =
tmp_constructor.prototype.__path = url; RenderJSGadget.onStateChange;
tmp_constructor.prototype.__acquired_method_dict = {}; tmp_constructor.declareService =
// https://developer.mozilla.org/en-US/docs/HTML_in_XMLHttpRequest RenderJSGadget.declareService;
// https://developer.mozilla.org/en-US/docs/Web/API/DOMParser tmp_constructor.onEvent =
// https://developer.mozilla.org/en-US/docs/Code_snippets/HTML_to_DOM RenderJSGadget.onEvent;
tmp_constructor.__template_element = tmp_constructor.onLoop =
(new DOMParser()).parseFromString(xhr.responseText, "text/html"); RenderJSGadget.onLoop;
parsed_html = renderJS.parseGadgetHTMLDocument( tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.__template_element, tmp_constructor.prototype.constructor = tmp_constructor;
url tmp_constructor.prototype.__path = url;
); tmp_constructor.prototype.__acquired_method_dict = {};
for (key in parsed_html) { // https://developer.mozilla.org/en-US/docs/HTML_in_XMLHttpRequest
if (parsed_html.hasOwnProperty(key)) { // https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
tmp_constructor.prototype['__' + key] = parsed_html[key]; // https://developer.mozilla.org/en-US/docs/Code_snippets/HTML_to_DOM
} tmp_constructor.__template_element =
} (new DOMParser()).parseFromString(xhr.responseText, "text/html");
parsed_html = renderJS.parseGadgetHTMLDocument(
gadget_model_dict[url] = tmp_constructor; tmp_constructor.__template_element,
url,
true
);
for (key in parsed_html) {
if (parsed_html.hasOwnProperty(key)) {
tmp_constructor.prototype['__' + key] = parsed_html[key];
} }
return gadget_model_dict[url];
} }
// Check if there is a HTML declared subgadget
if (tmp_constructor.__template_element
.querySelectorAll('[data-gadget-url]').length) {
tmp_constructor.__ready_list.push(loadSubGadgetDOMDeclaration);
}
return tmp_constructor;
}
if (gadget_model_dict.hasOwnProperty(url)) { renderJS.declareGadgetKlass = function declareGadgetKlass(url) {
var tmp_constructor,
defer;
if (gadget_model_defer_dict.hasOwnProperty(url)) {
// Return klass object if it already exists // Return klass object if it already exists
result = RSVP.resolve(gadget_model_dict[url]); if (gadget_model_defer_dict[url].hasOwnProperty('defer_list')) {
} else { // Klass not yet loaded.
// Fetch the HTML page and parse it // Add a new defer
result = new RSVP.Queue() defer = RSVP.defer();
.push(function () { gadget_model_defer_dict[url].defer_list.push(defer);
return ajax(url); return defer.promise;
}) }
.push(function (xhr) { if (gadget_model_defer_dict[url].is_resolved) {
return parse(xhr); return gadget_model_defer_dict[url].result;
}); }
throw gadget_model_defer_dict[url].result;
} }
return result;
gadget_model_defer_dict[url] = {
defer_list: []
};
// Fetch the HTML page and parse it
return new RSVP.Queue()
.push(function waitForGadgetKlassAjax() {
return ajax(url);
})
.push(function handleGadgetKlassAjax(result) {
tmp_constructor = parse(result, url);
var fragment = document.createDocumentFragment(),
promise_list = [],
i,
js_list = tmp_constructor.prototype.__required_js_list,
css_list = tmp_constructor.prototype.__required_css_list;
// Load JS
if (js_list.length) {
gadget_loading_klass_list.push(tmp_constructor);
for (i = 0; i < js_list.length - 1; i += 1) {
promise_list.push(renderJS.declareJS(js_list[i], fragment));
}
promise_list.push(renderJS.declareJS(js_list[i], fragment, true));
}
// Load CSS
for (i = 0; i < css_list.length; i += 1) {
promise_list.push(renderJS.declareCSS(css_list[i], fragment));
}
document.head.appendChild(fragment);
return RSVP.all(promise_list);
})
.push(function handleGadgetKlassLoadingSuccess() {
var i,
len = gadget_model_defer_dict[url].defer_list.length;
for (i = 0; i < len; i += 1) {
gadget_model_defer_dict[url].defer_list[i].resolve(tmp_constructor);
}
delete gadget_model_defer_dict[url].defer_list;
gadget_model_defer_dict[url].result = tmp_constructor;
gadget_model_defer_dict[url].is_resolved = true;
return tmp_constructor;
})
.push(undefined, function handleGadgetKlassLoadingError(e) {
// Drop the current loading klass info used by selector
// even in case of error
var i,
len = gadget_model_defer_dict[url].defer_list.length;
for (i = 0; i < len; i += 1) {
gadget_model_defer_dict[url].defer_list[i].reject(e);
}
delete gadget_model_defer_dict[url].defer_list;
gadget_model_defer_dict[url].result = e;
gadget_model_defer_dict[url].is_resolved = false;
throw e;
});
}; };
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// renderJS.clearGadgetKlassList // renderJS.clearGadgetKlassList
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// For test purpose only // For test purpose only
renderJS.clearGadgetKlassList = function () { renderJS.clearGadgetKlassList = function clearGadgetKlassList() {
gadget_model_dict = {}; gadget_model_defer_dict = {};
javascript_registration_dict = {}; javascript_registration_dict = {};
stylesheet_registration_dict = {}; stylesheet_registration_dict = {};
}; };
...@@ -1607,57 +2207,102 @@ if (typeof document.contains !== 'function') { ...@@ -1607,57 +2207,102 @@ if (typeof document.contains !== 'function') {
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// renderJS.parseGadgetHTMLDocument // renderJS.parseGadgetHTMLDocument
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
renderJS.parseGadgetHTMLDocument = function (document_element, url) { renderJS.parseGadgetHTMLDocument =
var settings = { function parseGadgetHTMLDocument(document_element, url,
title: "", update_relative_url) {
interface_list: [], var settings = {
required_css_list: [], title: "",
required_js_list: [] interface_list: [],
}, required_css_list: [],
i, required_js_list: [],
element; path: url
},
if (!url || !isAbsoluteOrDataURL.test(url)) { i,
throw new Error("The url should be absolute: " + url); element,
} element_list,
j,
if (document_element.nodeType === 9) { url_attribute_list = ['src', 'href', 'srcset'],
settings.title = document_element.title; url_attribute,
base_found = false;
if (document_element.head !== null) {
for (i = 0; i < document_element.head.children.length; i += 1) { if (!url || !isAbsoluteOrDataURL.test(url)) {
element = document_element.head.children[i]; throw new Error("The url should be absolute: " + url);
if (element.href !== null) { }
// XXX Manage relative URL during extraction of URLs
// element.href returns absolute URL in firefox but "" in chrome; if (update_relative_url === undefined) {
if (element.rel === "stylesheet") { update_relative_url = false;
settings.required_css_list.push( }
renderJS.getAbsoluteURL(element.getAttribute("href"), url)
); if (document_element.nodeType === 9) {
} else if (element.nodeName === "SCRIPT" && settings.title = document_element.title;
(element.type === "text/javascript" ||
!element.type)) { if (document_element.head !== null) {
settings.required_js_list.push( for (i = 0; i < document_element.head.children.length; i += 1) {
renderJS.getAbsoluteURL(element.getAttribute("src"), url) element = document_element.head.children[i];
); if (element.href !== null) {
} else if (element.rel === // XXX Manage relative URL during extraction of URLs
"http://www.renderjs.org/rel/interface") { // element.href returns absolute URL in firefox but "" in chrome;
settings.interface_list.push( if (element.rel === "stylesheet") {
renderJS.getAbsoluteURL(element.getAttribute("href"), url) settings.required_css_list.push(
); renderJS.getAbsoluteURL(element.getAttribute("href"),
settings.path)
);
} else if (element.nodeName === "SCRIPT" &&
(element.type === "text/javascript" ||
!element.type)) {
settings.required_js_list.push(
renderJS.getAbsoluteURL(element.getAttribute("src"),
settings.path)
);
} else if (element.rel ===
"http://www.renderjs.org/rel/interface") {
settings.interface_list.push(
renderJS.getAbsoluteURL(element.getAttribute("href"),
settings.path)
);
} else if ((element.nodeName === "BASE") && !base_found &&
element.getAttribute("href")) {
settings.path = renderJS.getAbsoluteURL(
element.getAttribute("href"),
settings.path
);
// Only use the first base element found
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#Usage_notes
base_found = true;
}
} }
} }
} }
if (update_relative_url && (document_element.body !== null)) {
// Resolve all relativeurl configure in the dom as absolute from
// the gadget url
for (j = 0; j < url_attribute_list.length; j += 1) {
url_attribute = url_attribute_list[j];
element_list = document_element.body.querySelectorAll(
'[' + url_attribute + ']'
);
for (i = 0; i < element_list.length; i += 1) {
element = element_list[i];
element.setAttribute(url_attribute, renderJS.getAbsoluteURL(
element.getAttribute(url_attribute),
settings.path
));
}
}
}
} else {
throw new Error("The first parameter should be an HTMLDocument");
} }
} else { return settings;
throw new Error("The first parameter should be an HTMLDocument"); };
}
return settings;
};
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// global // global
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
renderJS.Mutex = Mutex;
renderJS.ScopeError = ScopeError;
window.rJS = window.renderJS = renderJS; window.rJS = window.renderJS = renderJS;
window.__RenderJSGadget = RenderJSGadget; window.__RenderJSGadget = RenderJSGadget;
window.__RenderJSEmbeddedGadget = RenderJSEmbeddedGadget; window.__RenderJSEmbeddedGadget = RenderJSEmbeddedGadget;
...@@ -1667,358 +2312,379 @@ if (typeof document.contains !== 'function') { ...@@ -1667,358 +2312,379 @@ if (typeof document.contains !== 'function') {
// Bootstrap process. Register the self gadget. // Bootstrap process. Register the self gadget.
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
function bootstrap() { // Detect when all JS dependencies have been loaded
var url = removeHash(window.location.href), all_dependency_loaded_deferred = new RSVP.defer();
tmp_constructor, // Manually initializes the self gadget if the DOMContentLoaded event
// is triggered before everything was ready.
// (For instance, the HTML-tag for the self gadget gets inserted after
// page load)
renderJS.manualBootstrap = function manualBootstrap() {
all_dependency_loaded_deferred.resolve();
};
document.addEventListener('DOMContentLoaded',
all_dependency_loaded_deferred.resolve, false);
function configureMutationObserver(TmpConstructor, url, root_gadget) {
// XXX HTML properties can only be set when the DOM is fully loaded
var settings = renderJS.parseGadgetHTMLDocument(document, url),
j,
key,
fragment = document.createDocumentFragment();
for (key in settings) {
if (settings.hasOwnProperty(key)) {
TmpConstructor.prototype['__' + key] = settings[key];
}
}
TmpConstructor.__template_element = document.createElement("div");
root_gadget.element = document.body;
root_gadget.state = {};
for (j = 0; j < root_gadget.element.childNodes.length; j += 1) {
fragment.appendChild(
root_gadget.element.childNodes[j].cloneNode(true)
);
}
TmpConstructor.__template_element.appendChild(fragment);
return RSVP.all([root_gadget.getRequiredJSList(),
root_gadget.getRequiredCSSList()])
.then(function handleRequireDependencyList(all_list) {
var i,
js_list = all_list[0],
css_list = all_list[1];
for (i = 0; i < js_list.length; i += 1) {
javascript_registration_dict[js_list[i]] = null;
}
for (i = 0; i < css_list.length; i += 1) {
stylesheet_registration_dict[css_list[i]] = null;
}
gadget_loading_klass_list.shift();
}).then(function createMutationObserver() {
// select the target node
var target = document.querySelector('body'),
// create an observer instance
observer = new MutationObserver(function observeMutatios(mutations) {
var i, k, len, len2, node, added_list;
mutations.forEach(function observerMutation(mutation) {
if (mutation.type === 'childList') {
len = mutation.removedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.removedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
deleteGadgetMonitor(node._gadget);
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (node._gadget !== undefined) {
deleteGadgetMonitor(node._gadget);
}
}
}
}
len = mutation.addedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.addedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
if (document.contains(node)) {
startService(node._gadget);
}
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (document.contains(node)) {
if (node._gadget !== undefined) {
startService(node._gadget);
}
}
}
}
}
}
});
}),
// configuration of the observer:
config = {
childList: true,
subtree: true,
attributes: false,
characterData: false
};
// pass in the target node, as well as the observer options
observer.observe(target, config);
return root_gadget;
});
}
function createLastAcquisitionGadget() {
var last_acquisition_gadget = new RenderJSGadget();
last_acquisition_gadget.__acquired_method_dict = {
reportServiceError: function reportServiceError(param_list) {
letsCrash(param_list[0]);
}
};
// Stop acquisition on the last acquisition gadget
// Do not put this on the klass, as their could be multiple instances
last_acquisition_gadget.__aq_parent = function __aq_parent(method_name) {
throw new renderJS.AcquisitionError(
"No gadget provides " + method_name
);
};
return last_acquisition_gadget;
}
/*
function notifyAllMethodToParent() {
;
}
*/
function createLoadingGadget(url) {
var TmpConstructor,
root_gadget, root_gadget,
loading_gadget_promise = new RSVP.Queue(),
declare_method_count = 0,
embedded_channel, embedded_channel,
notifyReady,
notifyDeclareMethod, notifyDeclareMethod,
gadget_ready = false, declare_method_list_waiting,
iframe_top_gadget, loading_result,
last_acquisition_gadget; channel_defer,
real_result_list;
// gadget_failed = false,
// connection_ready = false;
// Create the gadget class for the current url // Create the gadget class for the current url
if (gadget_model_dict.hasOwnProperty(url)) { if (gadget_model_defer_dict.hasOwnProperty(url)) {
throw new Error("bootstrap should not be called twice"); throw new Error("bootstrap should not be called twice");
} }
loading_klass_promise = new RSVP.Promise(function (resolve, reject) {
last_acquisition_gadget = new RenderJSGadget(); // Create the root gadget instance and put it in the loading stack
last_acquisition_gadget.__acquired_method_dict = { TmpConstructor = RenderJSEmbeddedGadget;
getTopURL: function () { TmpConstructor.__ready_list = [];
return url; TmpConstructor.__service_list = RenderJSGadget.__service_list.slice();
}, TmpConstructor.prototype.__path = url;
reportServiceError: function (param_list) { root_gadget = new TmpConstructor();
letsCrash(param_list[0]); setAqParent(root_gadget, createLastAcquisitionGadget());
declare_method_list_waiting = [
"getInterfaceList",
"getRequiredCSSList",
"getRequiredJSList",
"getPath",
"getTitle",
"getMethodList"
];
// Inform parent gadget about declareMethod calls here.
notifyDeclareMethod = function notifyDeclareMethod(name) {
declare_method_list_waiting.push(name);
};
real_result_list = [TmpConstructor, root_gadget, embedded_channel,
declare_method_list_waiting];
if (window.self === window.top) {
loading_result = real_result_list;
} else {
channel_defer = RSVP.defer();
loading_result = RSVP.any([
channel_defer.promise,
new RSVP.Queue()
.push(function waitForParentChannelCreation() {
// Expect the channel to parent to be usable after 1 second
// If not, consider the gadget as the root
// Drop all iframe channel communication
return RSVP.delay(1000);
})
.push(function handleParentChannelCreation() {
real_result_list[2] = undefined;
return real_result_list;
})
]);
// Create the communication channel
embedded_channel = Channel.build({
window: window.parent,
origin: "*",
scope: "renderJS",
onReady: function onChannelReady() {
var k,
len;
// Channel is ready, so now declare all methods
notifyDeclareMethod = function notifyDeclareMethod(name) {
declare_method_list_waiting.push(
new RSVP.Promise(
function promiseChannelDeclareMethodCall(resolve, reject) {
embedded_channel.call({
method: "declareMethod",
params: name,
success: resolve,
error: reject
});
}
)
);
};
len = declare_method_list_waiting.length;
for (k = 0; k < len; k += 1) {
notifyDeclareMethod(declare_method_list_waiting[k]);
}
channel_defer.resolve(real_result_list);
} }
}; });
// Stop acquisition on the last acquisition gadget real_result_list[2] = embedded_channel;
// Do not put this on the klass, as their could be multiple instances }
last_acquisition_gadget.__aq_parent = function (method_name) {
throw new renderJS.AcquisitionError( // Surcharge declareMethod to inform parent window
"No gadget provides " + method_name TmpConstructor.declareMethod = function declareMethod(name, callback,
options) {
var result = RenderJSGadget.declareMethod.apply(
this,
[name, callback, options]
); );
}; notifyDeclareMethod(name);
return result;
};
//we need to determine tmp_constructor's value before exit bootstrap TmpConstructor.declareService =
//because of function : renderJS RenderJSGadget.declareService;
//but since the channel checking is async, TmpConstructor.declareJob =
//we can't use code structure like: RenderJSGadget.declareJob;
// if channel communication is ok TmpConstructor.onEvent =
// tmp_constructor = RenderJSGadget RenderJSGadget.onEvent;
// else TmpConstructor.onLoop =
// tmp_constructor = RenderJSEmbeddedGadget RenderJSGadget.onLoop;
if (window.self === window.top) { TmpConstructor.declareAcquiredMethod =
// XXX Copy/Paste from declareGadgetKlass RenderJSGadget.declareAcquiredMethod;
tmp_constructor = function () { TmpConstructor.allowPublicAcquisition =
RenderJSGadget.call(this); RenderJSGadget.allowPublicAcquisition;
};
tmp_constructor.declareMethod = RenderJSGadget.declareMethod; TmpConstructor.prototype.__acquired_method_dict = {};
tmp_constructor.declareAcquiredMethod = gadget_loading_klass_list.push(TmpConstructor);
RenderJSGadget.declareAcquiredMethod;
tmp_constructor.allowPublicAcquisition = return loading_result;
RenderJSGadget.allowPublicAcquisition; }
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.ready = RenderJSGadget.ready;
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.__path = url;
gadget_model_dict[url] = tmp_constructor;
// Create the root gadget instance and put it in the loading stack
root_gadget = new gadget_model_dict[url]();
setAqParent(root_gadget, last_acquisition_gadget);
} else { function triggerReadyList(TmpConstructor, root_gadget) {
// Create the communication channel // XXX Probably duplicated
embedded_channel = Channel.build({ var i,
window: window.parent, ready_queue = new RSVP.Queue();
origin: "*",
scope: "renderJS"
});
// Create the root gadget instance and put it in the loading stack
tmp_constructor = RenderJSEmbeddedGadget;
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.prototype.__path = url;
root_gadget = new RenderJSEmbeddedGadget();
// Notify parent about gadget instanciation
notifyReady = function () {
if ((declare_method_count === 0) && (gadget_ready === true)) {
embedded_channel.notify({method: "ready"});
}
};
// Inform parent gadget about declareMethod calls here. function ready_executable_wrapper(fct) {
notifyDeclareMethod = function (name) { return function wrapReadyFunction() {
declare_method_count += 1; return fct.call(root_gadget, root_gadget);
embedded_channel.call({ };
method: "declareMethod", }
params: name, TmpConstructor.ready(function startServiceInReady() {
success: function () { return startService(root_gadget);
declare_method_count -= 1; });
notifyReady();
},
error: function () {
declare_method_count -= 1;
}
});
};
notifyDeclareMethod("getInterfaceList"); for (i = 0; i < TmpConstructor.__ready_list.length; i += 1) {
notifyDeclareMethod("getRequiredCSSList"); // Put a timeout?
notifyDeclareMethod("getRequiredJSList"); ready_queue
notifyDeclareMethod("getPath"); .push(ready_executable_wrapper(TmpConstructor.__ready_list[i]));
notifyDeclareMethod("getTitle"); }
return ready_queue;
// Surcharge declareMethod to inform parent window }
tmp_constructor.declareMethod = function (name, callback) {
var result = RenderJSGadget.declareMethod.apply(
this,
[name, callback]
);
notifyDeclareMethod(name);
return result;
};
tmp_constructor.declareService = function finishAqParentConfiguration(TmpConstructor, root_gadget,
RenderJSGadget.declareService; embedded_channel) {
tmp_constructor.declareAcquiredMethod = // Define __aq_parent to inform parent window
RenderJSGadget.declareAcquiredMethod; root_gadget.__aq_parent =
tmp_constructor.allowPublicAcquisition = TmpConstructor.prototype.__aq_parent = function aq_parent(method_name,
RenderJSGadget.allowPublicAcquisition; argument_list,
time_out) {
//Default: Define __aq_parent to inform parent window return new RSVP.Promise(
tmp_constructor.prototype.__aq_parent = function (method_name, function waitForChannelAcquire(resolve, reject) {
argument_list, time_out) {
return new RSVP.Promise(function (resolve, reject) {
embedded_channel.call({ embedded_channel.call({
method: "acquire", method: "acquire",
params: [ params: [
method_name, method_name,
argument_list argument_list
], ],
success: function (s) { success: resolve,
resolve(s); error: reject,
},
error: function (e) {
reject(e);
},
timeout: time_out timeout: time_out
}); });
});
};
}
tmp_constructor.prototype.__acquired_method_dict = {};
gadget_loading_klass = tmp_constructor;
function init() {
// XXX HTML properties can only be set when the DOM is fully loaded
var settings = renderJS.parseGadgetHTMLDocument(document, url),
j,
key;
for (key in settings) {
if (settings.hasOwnProperty(key)) {
tmp_constructor.prototype['__' + key] = settings[key];
} }
} );
tmp_constructor.__template_element = document.createElement("div"); };
root_gadget.__element = document.body;
for (j = 0; j < root_gadget.__element.childNodes.length; j += 1) {
tmp_constructor.__template_element.appendChild(
root_gadget.__element.childNodes[j].cloneNode(true)
);
}
RSVP.all([root_gadget.getRequiredJSList(),
root_gadget.getRequiredCSSList()])
.then(function (all_list) {
var i,
js_list = all_list[0],
css_list = all_list[1];
for (i = 0; i < js_list.length; i += 1) {
javascript_registration_dict[js_list[i]] = null;
}
for (i = 0; i < css_list.length; i += 1) {
stylesheet_registration_dict[css_list[i]] = null;
}
gadget_loading_klass = undefined;
}).then(function () {
// select the target node
var target = document.querySelector('body'),
// create an observer instance
observer = new MutationObserver(function (mutations) {
var i, k, len, len2, node, added_list;
mutations.forEach(function (mutation) {
if (mutation.type === 'childList') {
len = mutation.removedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.removedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
createMonitor(node._gadget);
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (node._gadget !== undefined) {
createMonitor(node._gadget);
}
}
}
}
len = mutation.addedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.addedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
if (document.contains(node)) {
startService(node._gadget);
}
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (document.contains(node)) {
if (node._gadget !== undefined) {
startService(node._gadget);
}
}
}
}
}
} // bind calls to renderJS method on the instance
}); embedded_channel.bind("methodCall", function methodCall(trans, v) {
}), root_gadget[v[0]].apply(root_gadget, v[1])
// configuration of the observer: .push(trans.complete,
config = { function handleMethodCallError(e) {
childList: true, trans.error(e.toString());
subtree: true,
attributes: false,
characterData: false
};
// pass in the target node, as well as the observer options
observer.observe(target, config);
return root_gadget;
}).then(resolve, function (e) {
reject(e);
console.error(e);
throw e;
}); });
} trans.delayReturn(true);
document.addEventListener('DOMContentLoaded', init, false);
}); });
}
loading_gadget_promise function bootstrap(url) {
.push(function () { // Create the loading gadget
return loading_klass_promise; var wait_for_gadget_loaded = createLoadingGadget(url),
TmpConstructor,
root_gadget,
embedded_channel,
declare_method_list_waiting;
// Wait for the loading gadget to be created
return new RSVP.Queue(wait_for_gadget_loaded)
.push(function handleLoadingGadget(result_list) {
TmpConstructor = result_list[0];
root_gadget = result_list[1];
embedded_channel = result_list[2];
declare_method_list_waiting = result_list[3];
// Wait for all the gadget dependencies to be loaded
return all_dependency_loaded_deferred.promise;
}) })
.push(function (root_gadget) { .push(function waitForDeclareMethodList() {
var i; // Wait for all methods to be correctly declared
return RSVP.all(declare_method_list_waiting);
function ready_wrapper() { })
return root_gadget; .push(function waitForMutationObserver(result_list) {
if (embedded_channel !== undefined) {
finishAqParentConfiguration(TmpConstructor, root_gadget,
embedded_channel);
} }
// Check all DOM modifications to correctly start/stop services
if (window.top !== window.self) { return configureMutationObserver(TmpConstructor, url, root_gadget);
//checking channel should be done before sub gadget's declaration })
//__ready_list: .push(function waitForReadyList() {
//0: clearGadgetInternalParameters clearGadgetInternalParameters(root_gadget);
//1: loadSubGadgetDOMDeclaration TmpConstructor.__ready_list.unshift(loadSubGadgetDOMDeclaration);
//..... // Trigger all ready functions
tmp_constructor.__ready_list.splice(1, 0, function () { return triggerReadyList(TmpConstructor, root_gadget);
return root_gadget.__aq_parent('getTopURL', [], 100) })
.then(function (topURL) { .push(function notifyReady() {
var base = document.createElement('base'); if (embedded_channel !== undefined) {
base.href = topURL; embedded_channel.notify({method: "ready"});
base.target = "_top";
document.head.appendChild(base);
//the channel is ok
//so bind calls to renderJS method on the instance
embedded_channel.bind("methodCall", function (trans, v) {
root_gadget[v[0]].apply(root_gadget, v[1])
.then(function (g) {
trans.complete(g);
}).fail(function (e) {
trans.error(e.toString());
});
trans.delayReturn(true);
});
})
.fail(function (error) {
if (error === "timeout_error") {
//the channel fail
//we consider current gadget is parent gadget
//redifine last acquisition gadget
iframe_top_gadget = true;
setAqParent(root_gadget, last_acquisition_gadget);
} else {
throw error;
}
});
});
} }
})
tmp_constructor.ready(function (g) { .push(undefined, function handleBootstrapError(e) {
return startService(g); letsCrash(e);
}); if (embedded_channel !== undefined) {
embedded_channel.notify({method: "failed", params: e.toString()});
loading_gadget_promise.push(ready_wrapper);
for (i = 0; i < tmp_constructor.__ready_list.length; i += 1) {
// Put a timeout?
loading_gadget_promise
.push(tmp_constructor.__ready_list[i])
// Always return the gadget instance after ready function
.push(ready_wrapper);
} }
throw e;
}); });
if (window.self === window.top) {
loading_gadget_promise
.fail(function (e) {
letsCrash(e);
throw e;
});
} else {
// Inform parent window that gadget is correctly loaded
loading_gadget_promise
.then(function () {
gadget_ready = true;
notifyReady();
})
.fail(function (e) {
//top gadget in iframe
if (iframe_top_gadget) {
letsCrash(e);
} else {
embedded_channel.notify({method: "failed", params: e.toString()});
}
throw e;
});
}
} }
bootstrap();
bootstrap(
removeHash(window.location.href)
);
}(document, window, RSVP, DOMParser, Channel, MutationObserver, Node, }(document, window, RSVP, DOMParser, Channel, MutationObserver, Node,
FileReader, Blob)); FileReader, Blob, navigator, Event, URL));
\ No newline at end of file \ No newline at end of file
...@@ -60,7 +60,7 @@ define("rsvp/all", ...@@ -60,7 +60,7 @@ define("rsvp/all",
} }
} }
return new Promise(function(resolve, reject, notify) { return new Promise(function(resolve, reject) {
var results = [], remaining = promises.length, var results = [], remaining = promises.length,
promise, remaining_count = promises.length - expected_count; promise, remaining_count = promises.length - expected_count;
...@@ -90,12 +90,6 @@ define("rsvp/all", ...@@ -90,12 +90,6 @@ define("rsvp/all",
} }
} }
function notifier(index) {
return function(value) {
notify({"index": index, "value": value});
};
}
function cancelAll(rejectionValue) { function cancelAll(rejectionValue) {
reject(rejectionValue); reject(rejectionValue);
canceller(); canceller();
...@@ -105,7 +99,7 @@ define("rsvp/all", ...@@ -105,7 +99,7 @@ define("rsvp/all",
promise = promises[i]; promise = promises[i];
if (promise && typeof promise.then === 'function') { if (promise && typeof promise.then === 'function') {
promise.then(resolver(i), cancelAll, notifier(i)); promise.then(resolver(i), cancelAll);
} else { } else {
resolveAll(i, promise); resolveAll(i, promise);
} }
...@@ -135,6 +129,29 @@ define("rsvp/async", ...@@ -135,6 +129,29 @@ define("rsvp/async",
var async; var async;
var local = (typeof global !== 'undefined') ? global : this; var local = (typeof global !== 'undefined') ? global : this;
function checkNativePromise() {
if (typeof Promise === "function" &&
typeof Promise.resolve === "function") {
try {
/* global Promise */
var promise = new Promise(function(){});
if ({}.toString.call(promise) === "[object Promise]") {
return true;
}
} catch (e) {}
}
return false;
}
function useNativePromise() {
var nativePromise = Promise.resolve();
return function(callback, arg) {
nativePromise.then(function () {
callback(arg);
});
};
}
// old node // old node
function useNextTick() { function useNextTick() {
return function(callback, arg) { return function(callback, arg) {
...@@ -190,7 +207,9 @@ define("rsvp/async", ...@@ -190,7 +207,9 @@ define("rsvp/async",
}; };
} }
if (typeof setImmediate === 'function') { if (checkNativePromise()) {
async = useNativePromise();
} else if (typeof setImmediate === 'function') {
async = useSetImmediate(); async = useSetImmediate();
} else if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { } else if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
async = useNextTick(); async = useNextTick();
...@@ -360,10 +379,10 @@ define("rsvp/events", ...@@ -360,10 +379,10 @@ define("rsvp/events",
__exports__.EventTarget = EventTarget; __exports__.EventTarget = EventTarget;
}); });
define("rsvp/hash", define("rsvp/hash",
["rsvp/defer","exports"], ["rsvp/promise","exports"],
function(__dependency1__, __exports__) { function(__dependency1__, __exports__) {
"use strict"; "use strict";
var defer = __dependency1__.defer; var Promise = __dependency1__.Promise;
function size(object) { function size(object) {
var s = 0; var s = 0;
...@@ -376,38 +395,61 @@ define("rsvp/hash", ...@@ -376,38 +395,61 @@ define("rsvp/hash",
} }
function hash(promises) { function hash(promises) {
var results = {}, deferred = defer(), remaining = size(promises);
if (remaining === 0) { function canceller() {
deferred.resolve({}); var promise,
key;
for (key in promises) {
if (promises.hasOwnProperty(key)) {
promise = promises[key];
if (promise && typeof promise.then === 'function' &&
typeof promise.cancel === 'function') {
promise.cancel();
}
}
}
} }
var resolver = function(prop) { return new Promise(function(resolve, reject) {
return function(value) { var results = {}, remaining = size(promises),
resolveAll(prop, value); promise;
};
};
var resolveAll = function(prop, value) { if (remaining === 0) {
results[prop] = value; resolve(results);
if (--remaining === 0) {
deferred.resolve(results);
} }
};
var rejectAll = function(error) { function resolver(key) {
deferred.reject(error); return function(value) {
}; resolveAll(key, value);
};
}
for (var prop in promises) { function resolveAll(key, value) {
if (promises[prop] && typeof promises[prop].then === 'function') { results[key] = value;
promises[prop].then(resolver(prop), rejectAll); if (--remaining === 0) {
} else { resolve(results);
resolveAll(prop, promises[prop]); }
}
function cancelAll(rejectionValue) {
reject(rejectionValue);
canceller();
}
for (var prop in promises) {
promise = promises[prop];
if (promise && typeof promise.then === 'function') {
promise.then(resolver(prop), cancelAll);
} else {
resolveAll(prop, promise);
}
} }
}
return deferred.promise; }, canceller
);
} }
...@@ -504,11 +546,6 @@ define("rsvp/promise", ...@@ -504,11 +546,6 @@ define("rsvp/promise",
reject(promise, value); reject(promise, value);
}; };
var notifyPromise = function(value) {
if (resolved) { return; }
notify(promise, value);
};
this.on('promise:failed', function(event) { this.on('promise:failed', function(event) {
this.trigger('error', { detail: event.detail }); this.trigger('error', { detail: event.detail });
}, this); }, this);
...@@ -519,6 +556,7 @@ define("rsvp/promise", ...@@ -519,6 +556,7 @@ define("rsvp/promise",
// For now, simply reject the promise and does not propagate the cancel // For now, simply reject the promise and does not propagate the cancel
// to parent or children // to parent or children
if (resolved) { return; } if (resolved) { return; }
promise.isCancelled = true;
if (canceller !== undefined) { if (canceller !== undefined) {
try { try {
canceller(); canceller();
...@@ -532,7 +570,7 @@ define("rsvp/promise", ...@@ -532,7 +570,7 @@ define("rsvp/promise",
}; };
try { try {
resolver(resolvePromise, rejectPromise, notifyPromise); resolver(resolvePromise, rejectPromise);
} catch(e) { } catch(e) {
rejectPromise(e); rejectPromise(e);
} }
...@@ -550,6 +588,7 @@ define("rsvp/promise", ...@@ -550,6 +588,7 @@ define("rsvp/promise",
if (promise.isFulfilled) { return; } if (promise.isFulfilled) { return; }
if (promise.isRejected) { return; } if (promise.isRejected) { return; }
if (promise.isCancelled) { return; }
if (hasCallback) { if (hasCallback) {
try { try {
...@@ -577,31 +616,16 @@ define("rsvp/promise", ...@@ -577,31 +616,16 @@ define("rsvp/promise",
} }
}; };
var invokeNotifyCallback = function(promise, callback, event) {
var value;
if (typeof callback === 'function') {
try {
value = callback(event.detail);
} catch (e) {
// stop propagating
return;
}
notify(promise, value);
} else {
notify(promise, event.detail);
}
};
Promise.prototype = { Promise.prototype = {
constructor: Promise, constructor: Promise,
isCancelled: undefined,
isRejected: undefined, isRejected: undefined,
isFulfilled: undefined, isFulfilled: undefined,
rejectedReason: undefined, rejectedReason: undefined,
fulfillmentValue: undefined, fulfillmentValue: undefined,
then: function(done, fail, progress) { then: function(done, fail) {
this.off('error', onerror); this.off('error', onerror);
var thenPromise = new this.constructor(function() {}, var thenPromise = new this.constructor(function() {},
...@@ -629,10 +653,6 @@ define("rsvp/promise", ...@@ -629,10 +653,6 @@ define("rsvp/promise",
invokeCallback('reject', thenPromise, fail, event); invokeCallback('reject', thenPromise, fail, event);
}); });
this.on('promise:notified', function (event) {
invokeNotifyCallback(thenPromise, progress, event);
});
return thenPromise; return thenPromise;
}, },
...@@ -720,21 +740,15 @@ define("rsvp/promise", ...@@ -720,21 +740,15 @@ define("rsvp/promise",
}); });
} }
function notify(promise, value) {
config.async(function() {
promise.trigger('promise:notified', { detail: value });
});
}
__exports__.Promise = Promise; __exports__.Promise = Promise;
}); });
define("rsvp/queue", define("rsvp/queue",
["rsvp/promise","rsvp/timeout","exports"], ["rsvp/promise","rsvp/resolve","exports"],
function(__dependency1__, __dependency2__, __exports__) { function(__dependency1__, __dependency2__, __exports__) {
"use strict"; "use strict";
var Promise = __dependency1__.Promise; var Promise = __dependency1__.Promise;
var delay = __dependency2__.delay; var resolve = __dependency2__.resolve;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
function ResolvedQueueError(message) { function ResolvedQueueError(message) {
...@@ -747,7 +761,7 @@ define("rsvp/queue", ...@@ -747,7 +761,7 @@ define("rsvp/queue",
ResolvedQueueError.prototype = new Error(); ResolvedQueueError.prototype = new Error();
ResolvedQueueError.prototype.constructor = ResolvedQueueError; ResolvedQueueError.prototype.constructor = ResolvedQueueError;
var Queue = function() { var Queue = function(thenable) {
var queue = this, var queue = this,
promise_list = [], promise_list = [],
promise, promise,
...@@ -760,11 +774,31 @@ define("rsvp/queue", ...@@ -760,11 +774,31 @@ define("rsvp/queue",
} }
function canceller() { function canceller() {
for (var i = 0; i < 2; i++) { for (var i = promise_list.length; i > 0; i--) {
promise_list[i].cancel(); promise_list[i - 1].cancel();
} }
} }
function checkPromise(next_promise) {
promise_list.push(next_promise);
// Handle pop
promise_list.push(next_promise.then(function (fulfillmentValue) {
promise_list.splice(0, 2);
if (promise_list.length === 0) {
fulfill(fulfillmentValue);
} else {
return fulfillmentValue;
}
}, function (rejectedReason) {
promise_list.splice(0, 2);
if (promise_list.length === 0) {
reject(rejectedReason);
} else {
throw rejectedReason;
}
}));
}
promise = new Promise(function(done, fail) { promise = new Promise(function(done, fail) {
fulfill = function (fulfillmentValue) { fulfill = function (fulfillmentValue) {
if (resolved) {return;} if (resolved) {return;}
...@@ -782,13 +816,7 @@ define("rsvp/queue", ...@@ -782,13 +816,7 @@ define("rsvp/queue",
}; };
}, canceller); }, canceller);
promise_list.push(delay()); checkPromise(resolve(thenable));
promise_list.push(promise_list[0].then(function () {
promise_list.splice(0, 2);
if (promise_list.length === 0) {
fulfill();
}
}));
queue.cancel = function () { queue.cancel = function () {
if (resolved) {return;} if (resolved) {return;}
...@@ -811,25 +839,9 @@ define("rsvp/queue", ...@@ -811,25 +839,9 @@ define("rsvp/queue",
throw new ResolvedQueueError(); throw new ResolvedQueueError();
} }
next_promise = last_promise.then(done, fail);
promise_list.push(next_promise);
// Handle pop // Handle pop
promise_list.push(next_promise.then(function (fulfillmentValue) { checkPromise(last_promise.then(done, fail));
promise_list.splice(0, 2);
if (promise_list.length === 0) {
fulfill(fulfillmentValue);
} else {
return fulfillmentValue;
}
}, function (rejectedReason) {
promise_list.splice(0, 2);
if (promise_list.length === 0) {
reject(rejectedReason);
} else {
throw rejectedReason;
}
}));
return this; return this;
}; };
...@@ -985,4 +997,4 @@ define("rsvp", ...@@ -985,4 +997,4 @@ define("rsvp",
__exports__.reject = reject; __exports__.reject = reject;
}); });
window.RSVP = requireModule("rsvp"); window.RSVP = requireModule("rsvp");
})(window); })(window);
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment