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 @@
ScopeError.prototype = new Error();
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) {
var result;
try {
......@@ -208,6 +221,7 @@
isAbsoluteOrDataURL = new RegExp('^(?:[a-z]+:)?//|data:', 'i'),
is_page_unloaded = false,
error_list = [],
unhandled_error_type = 0,
all_dependency_loaded_deferred;
window.addEventListener('error', function handleGlobalError(error) {
......@@ -257,6 +271,40 @@
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) {
var i,
body,
......@@ -968,6 +1016,7 @@
old_element) {
var gadget_instance,
iframe,
transaction_dict = {},
iframe_loading_deferred = RSVP.defer();
if (old_element === undefined) {
throw new Error("DOM element is required to create Iframe Gadget " +
......@@ -1025,13 +1074,17 @@
channel_call_id,
wait_promise = new RSVP.Promise(
function handleChannelCall(resolve, reject) {
function errorWrap(value) {
return rejectErrorType(value, reject);
}
channel_call_id = gadget_instance.__chan.call({
method: "methodCall",
params: [
method_name,
Array.prototype.slice.call(argument_list, 0)],
success: resolve,
error: reject
error: errorWrap
});
},
function cancelChannelCall(msg) {
......@@ -1063,15 +1116,45 @@
iframe_loading_deferred.reject(params);
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",
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()
.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) {
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);
});
......@@ -1595,6 +1678,7 @@
/////////////////////////////////////////////////////////////////
renderJS.Mutex = Mutex;
renderJS.ScopeError = ScopeError;
renderJS.IframeSerializationError = IframeSerializationError;
renderJS.loopEventListener = loopEventListener;
window.rJS = window.renderJS = renderJS;
window.__RenderJSGadget = RenderJSGadget;
......@@ -1899,18 +1983,32 @@
TmpConstructor.prototype.__aq_parent = function aq_parent(method_name,
argument_list,
time_out) {
var channel_call_id;
return new RSVP.Promise(
function waitForChannelAcquire(resolve, reject) {
embedded_channel.call({
function errorWrap(value) {
return rejectErrorType(value, reject);
}
channel_call_id = embedded_channel.call({
method: "acquire",
params: [
method_name,
argument_list
Array.prototype.slice.call(argument_list, 0)
],
success: resolve,
error: reject,
error: errorWrap,
timeout: time_out
});
},
function cancelChannelCall(msg) {
embedded_channel.notify({
method: "cancelAcquiredMethodCall",
params: [
channel_call_id,
msg
]
});
}
);
};
......@@ -1925,9 +2023,23 @@
delete local_transaction_dict[transaction_id];
trans.complete.apply(trans, arguments);
}, 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
delete local_transaction_dict[transaction_id];
trans.error(e.toString());
trans.error({
type: error_type,
msg: message
});
});
trans.delayReturn(true);
});
......
......@@ -17,8 +17,9 @@
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
(function (window, rJS) {
(function (window, rJS, RSVP) {
"use strict";
var gk = rJS(window),
......@@ -27,6 +28,7 @@
job_started = false,
event_started = false,
method_cancel_called = false,
acquired_method_cancel_called = false,
state_change_callback_called = false,
state_change_count = 0,
init_state = {bar: 'foo'},
......@@ -97,6 +99,9 @@
.declareMethod('triggerError', function (value) {
throw new Error("Manually triggered embedded error");
})
.declareMethod('triggerStringError', function (value) {
throw "Manually triggered embedded error as string";
})
.declareMethod('setContent', function (value) {
this.embedded_property = value;
})
......@@ -110,7 +115,15 @@
.declareAcquiredMethod('plugErrorAcquire',
'acquireMethodRequestedWithAcquisitionError')
.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 () {
return new RSVP.Promise(function () {
......@@ -121,6 +134,40 @@
})
.declareMethod('wasMethodCancelCalled', function () {
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 @@
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
(function (document, renderJS, QUnit, sinon, URI, URL, Event,
MutationObserver) {
MutationObserver, RSVP) {
"use strict";
var test = QUnit.test,
stop = QUnit.stop,
......@@ -5646,6 +5647,7 @@
test('checking working iframe gadget', function () {
// Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(),
acquired_method_cancel_called = false,
acquire_called = false,
url = "./embedded.html",
new_gadget;
......@@ -5661,13 +5663,29 @@
);
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);
};
gadget.__sub_gadget_dict = {};
stop();
expect(25);
expect(42);
gadget.declareGadget(url, {
sandbox: 'iframe',
element: document.getElementById('qunit-fixture'),
......@@ -5793,7 +5811,25 @@
.push(function () {
ok(false, "triggerError should fail");
}, 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
......@@ -5840,31 +5876,91 @@
ok(false, result);
})
.push(undefined, function (error) {
ok(
error instanceof renderJS.AcquisitionError,
JSON.stringify(error)
);
equal(
error,
error.toString(),
"AcquisitionError: Can not handle " +
"acquireMethodRequestedWithAcquisitionError",
error
);
})
// cancel call is correctly propagated by declareMethod
// cancel is correctly propagated by declareMethod
.push(function () {
var method_to_cancel = new_gadget.triggerMethodToCancel();
return new RSVP.Queue(RSVP.delay(400))
.push(function () {
return RSVP.all([
method_to_cancel,
method_to_cancel.cancel()
method_to_cancel.cancel("cancel from triggerMethodToCancel")
]);
});
})
.push(undefined, function (error) {
ok(error instanceof RSVP.CancellationError, error);
equal(
error.toString(),
"cancel: cancel from triggerMethodToCancel"
);
return new_gadget.wasMethodCancelCalled();
})
.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) {
......@@ -5914,7 +6010,7 @@
gadget.__sub_gadget_dict = {};
stop();
expect(16);
expect(19);
gadget.declareGadget(url, {
sandbox: 'iframe',
element: document.getElementById('qunit-fixture')
......@@ -5974,7 +6070,12 @@
.push(function () {
ok(false, "triggerError should fail");
}, 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
......@@ -6021,12 +6122,18 @@
ok(false, result);
})
.push(undefined, function (error) {
ok(error instanceof renderJS.AcquisitionError);
equal(
error,
error.toString(),
"AcquisitionError: Can not handle " +
"acquireMethodRequestedWithAcquisitionError",
error
);
equal(
error.name,
"AcquisitionError",
error
);
});
})
.fail(function (error) {
......@@ -6061,7 +6168,7 @@
gadget.__sub_gadget_dict = {};
stop();
expect(16);
expect(19);
gadget.declareGadget(url, {
sandbox: 'iframe',
element: document.getElementById('qunit-fixture')
......@@ -6121,7 +6228,12 @@
.push(function () {
ok(false, "triggerError should fail");
}, 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
......@@ -6168,12 +6280,18 @@
ok(false, result);
})
.push(undefined, function (error) {
ok(error instanceof renderJS.AcquisitionError, error);
equal(
error,
error.toString(),
"AcquisitionError: Can not handle " +
"acquireMethodRequestedWithAcquisitionError",
error
);
equal(
error.name,
"AcquisitionError",
error
);
});
})
.fail(function (error) {
......@@ -6815,5 +6933,5 @@
});
}(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