Commit be266052 authored by Gabriel Monnerat's avatar Gabriel Monnerat

Propapate error between parent and iframe

improve code to serialize and unserialize errors(AcquisitionError and CancelationError) through jschannel

Also, add IframeSerializationError to handle unhandled type errors
parent 42306bb0
...@@ -61,6 +61,19 @@ ...@@ -61,6 +61,19 @@
ScopeError.prototype = new Error(); ScopeError.prototype = new Error();
ScopeError.prototype.constructor = ScopeError; ScopeError.prototype.constructor = ScopeError;
/////////////////////////////////////////////////////////////////
// renderJS.IframeSerializationError
/////////////////////////////////////////////////////////////////
function IframeSerializationError(message) {
this.name = "IframeSerializationError";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Parameter serialization failed";
}
IframeSerializationError.prototype = new Error();
IframeSerializationError.prototype.constructor = IframeSerializationError;
function ensurePushableQueue(callback, argument_list, context) { function ensurePushableQueue(callback, argument_list, context) {
var result; var result;
try { try {
...@@ -208,6 +221,7 @@ ...@@ -208,6 +221,7 @@
isAbsoluteOrDataURL = new RegExp('^(?:[a-z]+:)?//|data:', 'i'), isAbsoluteOrDataURL = new RegExp('^(?:[a-z]+:)?//|data:', 'i'),
is_page_unloaded = false, is_page_unloaded = false,
error_list = [], error_list = [],
unhandled_error_type = 0,
all_dependency_loaded_deferred; all_dependency_loaded_deferred;
window.addEventListener('error', function handleGlobalError(error) { window.addEventListener('error', function handleGlobalError(error) {
...@@ -257,6 +271,40 @@ ...@@ -257,6 +271,40 @@
return url; return url;
} }
function getErrorTypeMapping() {
var error_type_mapping = {
1: renderJS.AcquisitionError,
2: RSVP.CancellationError
};
// set the unhandle error type to be used as default
error_type_mapping[unhandled_error_type] = IframeSerializationError;
return error_type_mapping;
}
function convertObjectToErrorType(error) {
var error_type,
error_type_mapping = getErrorTypeMapping();
for (error_type in error_type_mapping) {
if (error_type_mapping.hasOwnProperty(error_type) &&
error instanceof error_type_mapping[error_type]) {
return error_type;
}
}
return unhandled_error_type;
}
function rejectErrorType(value, reject) {
var error_type_mapping = getErrorTypeMapping();
if (value.hasOwnProperty("type") &&
error_type_mapping.hasOwnProperty(value.type)) {
value = new error_type_mapping[value.type](
value.msg
);
}
return reject(value);
}
function letsCrash(e) { function letsCrash(e) {
var i, var i,
body, body,
...@@ -968,6 +1016,7 @@ ...@@ -968,6 +1016,7 @@
old_element) { old_element) {
var gadget_instance, var gadget_instance,
iframe, iframe,
transaction_dict = {},
iframe_loading_deferred = RSVP.defer(); iframe_loading_deferred = RSVP.defer();
if (old_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 " +
...@@ -1025,13 +1074,17 @@ ...@@ -1025,13 +1074,17 @@
channel_call_id, channel_call_id,
wait_promise = new RSVP.Promise( wait_promise = new RSVP.Promise(
function handleChannelCall(resolve, reject) { function handleChannelCall(resolve, reject) {
function errorWrap(value) {
return rejectErrorType(value, reject);
}
channel_call_id = gadget_instance.__chan.call({ channel_call_id = gadget_instance.__chan.call({
method: "methodCall", method: "methodCall",
params: [ params: [
method_name, method_name,
Array.prototype.slice.call(argument_list, 0)], Array.prototype.slice.call(argument_list, 0)],
success: resolve, success: resolve,
error: reject error: errorWrap
}); });
}, },
function cancelChannelCall(msg) { function cancelChannelCall(msg) {
...@@ -1063,15 +1116,45 @@ ...@@ -1063,15 +1116,45 @@
iframe_loading_deferred.reject(params); iframe_loading_deferred.reject(params);
return "OK"; return "OK";
}); });
gadget_instance.__chan.bind("cancelAcquiredMethodCall",
function handleChannelCancel(trans,
params) {
var transaction_id = params[0],
msg = params[1];
if (transaction_dict.hasOwnProperty(transaction_id)) {
transaction_dict[transaction_id].cancel(msg);
delete transaction_dict[transaction_id];
}
return "OK";
});
gadget_instance.__chan.bind("acquire", gadget_instance.__chan.bind("acquire",
function handleChannelAcquire(trans, params) { function handleChannelAcquire(trans, params,
transaction_id) {
function cleanUpTransactionDict(transaction_id) {
if (transaction_dict.hasOwnProperty(transaction_id)) {
delete transaction_dict[transaction_id];
}
}
new RSVP.Queue() new RSVP.Queue()
.push(function () { .push(function () {
return gadget_instance.__aq_parent.apply(gadget_instance, params); var promise = gadget_instance.__aq_parent.apply(
gadget_instance,
params
);
transaction_dict[transaction_id] = promise;
return promise;
})
.then(function () {
cleanUpTransactionDict(transaction_id);
trans.complete.apply(trans, arguments);
}) })
.then(trans.complete)
.fail(function handleChannelAcquireError(e) { .fail(function handleChannelAcquireError(e) {
trans.error(e.toString()); var message = e instanceof Error ? e.message : e;
trans.error({
type: convertObjectToErrorType(e),
msg: message
});
return cleanUpTransactionDict(transaction_id);
}); });
trans.delayReturn(true); trans.delayReturn(true);
}); });
...@@ -1595,6 +1678,7 @@ ...@@ -1595,6 +1678,7 @@
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
renderJS.Mutex = Mutex; renderJS.Mutex = Mutex;
renderJS.ScopeError = ScopeError; renderJS.ScopeError = ScopeError;
renderJS.IframeSerializationError = IframeSerializationError;
renderJS.loopEventListener = loopEventListener; renderJS.loopEventListener = loopEventListener;
window.rJS = window.renderJS = renderJS; window.rJS = window.renderJS = renderJS;
window.__RenderJSGadget = RenderJSGadget; window.__RenderJSGadget = RenderJSGadget;
...@@ -1899,18 +1983,32 @@ ...@@ -1899,18 +1983,32 @@
TmpConstructor.prototype.__aq_parent = function aq_parent(method_name, TmpConstructor.prototype.__aq_parent = function aq_parent(method_name,
argument_list, argument_list,
time_out) { time_out) {
var channel_call_id;
return new RSVP.Promise( return new RSVP.Promise(
function waitForChannelAcquire(resolve, reject) { function waitForChannelAcquire(resolve, reject) {
embedded_channel.call({ function errorWrap(value) {
return rejectErrorType(value, reject);
}
channel_call_id = embedded_channel.call({
method: "acquire", method: "acquire",
params: [ params: [
method_name, method_name,
argument_list Array.prototype.slice.call(argument_list, 0)
], ],
success: resolve, success: resolve,
error: reject, error: errorWrap,
timeout: time_out timeout: time_out
}); });
},
function cancelChannelCall(msg) {
embedded_channel.notify({
method: "cancelAcquiredMethodCall",
params: [
channel_call_id,
msg
]
});
} }
); );
}; };
...@@ -1925,9 +2023,23 @@ ...@@ -1925,9 +2023,23 @@
delete local_transaction_dict[transaction_id]; delete local_transaction_dict[transaction_id];
trans.complete.apply(trans, arguments); trans.complete.apply(trans, arguments);
}, function handleMethodCallError(e) { }, function handleMethodCallError(e) {
var error_type = convertObjectToErrorType(e),
message;
if (e instanceof Error) {
if (error_type !== unhandled_error_type) {
message = e.message;
} else {
message = e.toString();
}
} else {
message = e;
}
// drop the promise reference, to allow garbage collection // drop the promise reference, to allow garbage collection
delete local_transaction_dict[transaction_id]; delete local_transaction_dict[transaction_id];
trans.error(e.toString()); trans.error({
type: error_type,
msg: message
});
}); });
trans.delayReturn(true); trans.delayReturn(true);
}); });
......
...@@ -17,8 +17,9 @@ ...@@ -17,8 +17,9 @@
* See COPYING file for full licensing terms. * See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options. * See https://www.nexedi.com/licensing for rationale and options.
*/ */
/*jslint nomen: true*/ /*jslint nomen: true*/
(function (window, rJS) { (function (window, rJS, RSVP) {
"use strict"; "use strict";
var gk = rJS(window), var gk = rJS(window),
...@@ -27,6 +28,7 @@ ...@@ -27,6 +28,7 @@
job_started = false, job_started = false,
event_started = false, event_started = false,
method_cancel_called = false, method_cancel_called = false,
acquired_method_cancel_called = false,
state_change_callback_called = false, state_change_callback_called = false,
state_change_count = 0, state_change_count = 0,
init_state = {bar: 'foo'}, init_state = {bar: 'foo'},
...@@ -97,6 +99,9 @@ ...@@ -97,6 +99,9 @@
.declareMethod('triggerError', function (value) { .declareMethod('triggerError', function (value) {
throw new Error("Manually triggered embedded error"); throw new Error("Manually triggered embedded error");
}) })
.declareMethod('triggerStringError', function (value) {
throw "Manually triggered embedded error as string";
})
.declareMethod('setContent', function (value) { .declareMethod('setContent', function (value) {
this.embedded_property = value; this.embedded_property = value;
}) })
...@@ -110,7 +115,15 @@ ...@@ -110,7 +115,15 @@
.declareAcquiredMethod('plugErrorAcquire', .declareAcquiredMethod('plugErrorAcquire',
'acquireMethodRequestedWithAcquisitionError') 'acquireMethodRequestedWithAcquisitionError')
.declareMethod('callErrorAcquire', function (param1, param2) { .declareMethod('callErrorAcquire', function (param1, param2) {
return this.plugErrorAcquire(param1, param2); return this.plugErrorAcquire(param1, param2)
.push(undefined, function (error) {
if (error instanceof renderJS.AcquisitionError) {
throw error;
}
throw new Error(
'Expected AcquisitionError: ' + JSON.stringify(error)
);
});
}) })
.declareMethod('triggerMethodToCancel', function () { .declareMethod('triggerMethodToCancel', function () {
return new RSVP.Promise(function () { return new RSVP.Promise(function () {
...@@ -121,6 +134,40 @@ ...@@ -121,6 +134,40 @@
}) })
.declareMethod('wasMethodCancelCalled', function () { .declareMethod('wasMethodCancelCalled', function () {
return method_cancel_called; return method_cancel_called;
})
.declareAcquiredMethod('acquireCancellationError',
'acquireCancellationError')
.declareMethod('triggerAcquiredMethodToCancel', function () {
return this.acquireCancellationError()
.push(undefined, function (error) {
if (error instanceof RSVP.CancellationError) {
acquired_method_cancel_called = true;
throw error;
}
throw new Error(
'Expected CancellationError: ' + JSON.stringify(error)
);
});
})
.declareMethod('wasAcquiredMethodCancelCalled', function () {
return acquired_method_cancel_called;
})
.declareAcquiredMethod('acquiredStringError',
'acquiredStringError')
.declareMethod('triggerAcquiredStringError',
function () {
return this.acquiredStringError();
})
.declareAcquiredMethod("acquiredManualCancellationError",
"acquiredManualCancellationError")
.declareMethod('acquirePromiseToCancel',
function () {
return this.acquiredManualCancellationError();
})
.declareAcquiredMethod("isAcquiredMethodCancelCalled",
"isAcquiredMethodCancelCalled")
.declareMethod('wasAcquiredMethodCancelCalledFromParent', function () {
return this.isAcquiredMethodCancelCalled();
}); });
}(window, rJS)); }(window, rJS, RSVP));
...@@ -17,9 +17,10 @@ ...@@ -17,9 +17,10 @@
* See COPYING file for full licensing terms. * See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options. * See https://www.nexedi.com/licensing for rationale and options.
*/ */
/*jslint nomen: true*/ /*jslint nomen: true*/
(function (document, renderJS, QUnit, sinon, URI, URL, Event, (function (document, renderJS, QUnit, sinon, URI, URL, Event,
MutationObserver) { MutationObserver, RSVP) {
"use strict"; "use strict";
var test = QUnit.test, var test = QUnit.test,
stop = QUnit.stop, stop = QUnit.stop,
...@@ -5646,6 +5647,7 @@ ...@@ -5646,6 +5647,7 @@
test('checking working iframe gadget', function () { test('checking working iframe gadget', function () {
// Check that declare gadget returns the gadget // Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(), var gadget = new RenderJSGadget(),
acquired_method_cancel_called = false,
acquire_called = false, acquire_called = false,
url = "./embedded.html", url = "./embedded.html",
new_gadget; new_gadget;
...@@ -5661,13 +5663,29 @@ ...@@ -5661,13 +5663,29 @@
); );
return "result correctly fetched from parent"; return "result correctly fetched from parent";
} }
if (method_name === "acquireCancellationError") {
throw new RSVP.CancellationError('Explicit cancellation');
}
if (method_name === "acquiredStringError") {
throw "String Error";
}
if (method_name === "acquiredManualCancellationError") {
return new RSVP.Promise(function () {
return;
}, function () {
acquired_method_cancel_called = true;
});
}
if (method_name === "isAcquiredMethodCancelCalled") {
return acquired_method_cancel_called;
}
throw new renderJS.AcquisitionError("Can not handle " + method_name); throw new renderJS.AcquisitionError("Can not handle " + method_name);
}; };
gadget.__sub_gadget_dict = {}; gadget.__sub_gadget_dict = {};
stop(); stop();
expect(25); expect(42);
gadget.declareGadget(url, { gadget.declareGadget(url, {
sandbox: 'iframe', sandbox: 'iframe',
element: document.getElementById('qunit-fixture'), element: document.getElementById('qunit-fixture'),
...@@ -5793,7 +5811,25 @@ ...@@ -5793,7 +5811,25 @@
.push(function () { .push(function () {
ok(false, "triggerError should fail"); ok(false, "triggerError should fail");
}, function (e) { }, function (e) {
equal(e, "Error: Manually triggered embedded error"); ok(e instanceof renderJS.IframeSerializationError);
equal(
e.toString(),
"IframeSerializationError: Error: " +
"Manually triggered embedded error"
);
})
.push(function () {
return new_gadget.triggerStringError();
})
.push(function () {
ok(false, "triggerStringError should fail");
}, function (e) {
ok(e instanceof renderJS.IframeSerializationError);
equal(
e.toString(),
"IframeSerializationError: " +
"Manually triggered embedded error as string"
);
}) })
// sub_gadget_dict private property is created // sub_gadget_dict private property is created
...@@ -5840,31 +5876,91 @@ ...@@ -5840,31 +5876,91 @@
ok(false, result); ok(false, result);
}) })
.push(undefined, function (error) { .push(undefined, function (error) {
ok(
error instanceof renderJS.AcquisitionError,
JSON.stringify(error)
);
equal( equal(
error, error.toString(),
"AcquisitionError: Can not handle " + "AcquisitionError: Can not handle " +
"acquireMethodRequestedWithAcquisitionError", "acquireMethodRequestedWithAcquisitionError",
error error
); );
}) })
// cancel call is correctly propagated by declareMethod // cancel is correctly propagated by declareMethod
.push(function () { .push(function () {
var method_to_cancel = new_gadget.triggerMethodToCancel(); var method_to_cancel = new_gadget.triggerMethodToCancel();
return new RSVP.Queue(RSVP.delay(400)) return new RSVP.Queue(RSVP.delay(400))
.push(function () { .push(function () {
return RSVP.all([ return RSVP.all([
method_to_cancel, method_to_cancel,
method_to_cancel.cancel() method_to_cancel.cancel("cancel from triggerMethodToCancel")
]); ]);
}); });
}) })
.push(undefined, function (error) { .push(undefined, function (error) {
ok(error instanceof RSVP.CancellationError, error); ok(error instanceof RSVP.CancellationError, error);
equal(
error.toString(),
"cancel: cancel from triggerMethodToCancel"
);
return new_gadget.wasMethodCancelCalled(); return new_gadget.wasMethodCancelCalled();
}) })
.push(function (result) { .push(function (result) {
ok(result, 'Embedded method not cancelled'); ok(result, 'Embedded method not cancelled ' + result);
})
// cancel is correctly propagated by acquiredMethod
.push(function () {
return new_gadget.triggerAcquiredMethodToCancel();
})
.push(undefined, function (error) {
ok(error instanceof RSVP.CancellationError, JSON.stringify(error));
equal(
error.toString(),
"cancel: Explicit cancellation"
);
return new_gadget.wasAcquiredMethodCancelCalled();
})
.push(function (result) {
ok(result, 'Embedded acquired method not cancelled ' + result);
})
// cancellation of a acquiredMethod call
.push(function () {
var method_to_cancel =
new_gadget.acquirePromiseToCancel();
return new RSVP.Queue(RSVP.delay(400))
.push(function () {
return RSVP.all([
method_to_cancel,
method_to_cancel.cancel("cancel from acquirePromiseToCancel")
]);
});
})
.push(undefined, function (error) {
ok(error instanceof RSVP.CancellationError, JSON.stringify(error));
equal(
error.toString(),
"cancel: cancel from acquirePromiseToCancel"
);
return new_gadget.wasAcquiredMethodCancelCalledFromParent();
})
.push(function (result) {
ok(result, 'Embedded acquired method not cancelled ' + result);
})
.push(function () {
return new_gadget.triggerAcquiredStringError();
})
.push(undefined, function (error) {
ok(
error instanceof renderJS.IframeSerializationError,
JSON.stringify(error)
);
equal(
error.toString(),
"IframeSerializationError: String Error"
);
}); });
}) })
.fail(function (error) { .fail(function (error) {
...@@ -5914,7 +6010,7 @@ ...@@ -5914,7 +6010,7 @@
gadget.__sub_gadget_dict = {}; gadget.__sub_gadget_dict = {};
stop(); stop();
expect(16); expect(19);
gadget.declareGadget(url, { gadget.declareGadget(url, {
sandbox: 'iframe', sandbox: 'iframe',
element: document.getElementById('qunit-fixture') element: document.getElementById('qunit-fixture')
...@@ -5974,7 +6070,12 @@ ...@@ -5974,7 +6070,12 @@
.push(function () { .push(function () {
ok(false, "triggerError should fail"); ok(false, "triggerError should fail");
}, function (e) { }, function (e) {
equal(e, "Error: Manually triggered embedded error"); ok(e instanceof renderJS.IframeSerializationError);
equal(
e.toString(),
"IframeSerializationError: Error: " +
"Manually triggered embedded error"
);
}) })
// sub_gadget_dict private property is created // sub_gadget_dict private property is created
...@@ -6021,12 +6122,18 @@ ...@@ -6021,12 +6122,18 @@
ok(false, result); ok(false, result);
}) })
.push(undefined, function (error) { .push(undefined, function (error) {
ok(error instanceof renderJS.AcquisitionError);
equal( equal(
error, error.toString(),
"AcquisitionError: Can not handle " + "AcquisitionError: Can not handle " +
"acquireMethodRequestedWithAcquisitionError", "acquireMethodRequestedWithAcquisitionError",
error error
); );
equal(
error.name,
"AcquisitionError",
error
);
}); });
}) })
.fail(function (error) { .fail(function (error) {
...@@ -6061,7 +6168,7 @@ ...@@ -6061,7 +6168,7 @@
gadget.__sub_gadget_dict = {}; gadget.__sub_gadget_dict = {};
stop(); stop();
expect(16); expect(19);
gadget.declareGadget(url, { gadget.declareGadget(url, {
sandbox: 'iframe', sandbox: 'iframe',
element: document.getElementById('qunit-fixture') element: document.getElementById('qunit-fixture')
...@@ -6121,7 +6228,12 @@ ...@@ -6121,7 +6228,12 @@
.push(function () { .push(function () {
ok(false, "triggerError should fail"); ok(false, "triggerError should fail");
}, function (e) { }, function (e) {
equal(e, "Error: Manually triggered embedded error"); ok(e instanceof renderJS.IframeSerializationError);
equal(
e.toString(),
"IframeSerializationError: Error: " +
"Manually triggered embedded error"
);
}) })
// sub_gadget_dict private property is created // sub_gadget_dict private property is created
...@@ -6168,12 +6280,18 @@ ...@@ -6168,12 +6280,18 @@
ok(false, result); ok(false, result);
}) })
.push(undefined, function (error) { .push(undefined, function (error) {
ok(error instanceof renderJS.AcquisitionError, error);
equal( equal(
error, error.toString(),
"AcquisitionError: Can not handle " + "AcquisitionError: Can not handle " +
"acquireMethodRequestedWithAcquisitionError", "acquireMethodRequestedWithAcquisitionError",
error error
); );
equal(
error.name,
"AcquisitionError",
error
);
}); });
}) })
.fail(function (error) { .fail(function (error) {
...@@ -6815,5 +6933,5 @@ ...@@ -6815,5 +6933,5 @@
}); });
}(document, renderJS, QUnit, sinon, URI, URL, Event, }(document, renderJS, QUnit, sinon, URI, URL, Event,
MutationObserver)); MutationObserver, RSVP));
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