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

RenderJS: prepares communication with parent gadget only if communication channel is ready

It removes communication timeout with parent iFrame and solve the issue of timeout in case of a high load due to heavy JS processing
/reviewed-on nexedi/renderjs!2
parent 0ab5cb9e
...@@ -1020,7 +1020,11 @@ ...@@ -1020,7 +1020,11 @@
notifyDeclareMethod, notifyDeclareMethod,
gadget_ready = false, gadget_ready = false,
iframe_top_gadget, iframe_top_gadget,
last_acquisition_gadget; last_acquisition_gadget,
declare_method_list_waiting = [],
gadget_failed = false,
gadget_error,
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_dict.hasOwnProperty(url)) {
...@@ -1079,28 +1083,45 @@ ...@@ -1079,28 +1083,45 @@
setAqParent(root_gadget, last_acquisition_gadget); setAqParent(root_gadget, last_acquisition_gadget);
} else { } else {
// Create the communication channel
embedded_channel = Channel.build({
window: window.parent,
origin: "*",
scope: "renderJS"
});
// Create the root gadget instance and put it in the loading stack // Create the root gadget instance and put it in the loading stack
tmp_constructor = RenderJSEmbeddedGadget; tmp_constructor = RenderJSEmbeddedGadget;
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice(); 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.prototype.__path = url; tmp_constructor.prototype.__path = url;
root_gadget = new RenderJSEmbeddedGadget(); root_gadget = new RenderJSEmbeddedGadget();
setAqParent(root_gadget, last_acquisition_gadget);
// Create the communication channel
// Notify parent about gadget instanciation embedded_channel = Channel.build({
notifyReady = function () { window: window.parent,
if ((declare_method_count === 0) && (gadget_ready === true)) { origin: "*",
embedded_channel.notify({method: "ready"}); scope: "renderJS",
} onReady: function () {
var k;
iframe_top_gadget = false;
//Default: Define __aq_parent to inform parent window
root_gadget.__aq_parent =
tmp_constructor.prototype.__aq_parent = function (method_name,
argument_list, time_out) {
return new RSVP.Promise(function (resolve, reject) {
embedded_channel.call({
method: "acquire",
params: [
method_name,
argument_list
],
success: function (s) {
resolve(s);
},
error: function (e) {
reject(e);
},
timeout: time_out
});
});
}; };
// Inform parent gadget about declareMethod calls here. // Channel is ready, so now declare Function
notifyDeclareMethod = function (name) { notifyDeclareMethod = function (name) {
declare_method_count += 1; declare_method_count += 1;
embedded_channel.call({ embedded_channel.call({
...@@ -1115,6 +1136,56 @@ ...@@ -1115,6 +1136,56 @@
} }
}); });
}; };
for (k = 0; k < declare_method_list_waiting.length; k += 1) {
notifyDeclareMethod(declare_method_list_waiting[k]);
}
declare_method_list_waiting = [];
// If Gadget Failed Notify Parent
if (gadget_failed) {
embedded_channel.notify({
method: "failed",
params: gadget_error
});
return;
}
// Get Top URL
return tmp_constructor.prototype.__aq_parent('getTopURL', [])
.then(function (topURL) {
var base = document.createElement('base');
base.href = topURL;
base.target = "_top";
document.head.appendChild(base);
connection_ready = true;
notifyReady();
//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) {
throw error;
});
}
});
// 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.
notifyDeclareMethod = function (name) {
declare_method_list_waiting.push(name);
};
notifyDeclareMethod("getInterfaceList"); notifyDeclareMethod("getInterfaceList");
notifyDeclareMethod("getRequiredCSSList"); notifyDeclareMethod("getRequiredCSSList");
...@@ -1139,26 +1210,7 @@ ...@@ -1139,26 +1210,7 @@
tmp_constructor.allowPublicAcquisition = tmp_constructor.allowPublicAcquisition =
RenderJSGadget.allowPublicAcquisition; RenderJSGadget.allowPublicAcquisition;
//Default: Define __aq_parent to inform parent window iframe_top_gadget = true;
tmp_constructor.prototype.__aq_parent = function (method_name,
argument_list, time_out) {
return new RSVP.Promise(function (resolve, reject) {
embedded_channel.call({
method: "acquire",
params: [
method_name,
argument_list
],
success: function (s) {
resolve(s);
},
error: function (e) {
reject(e);
},
timeout: time_out
});
});
};
} }
tmp_constructor.prototype.__acquired_method_dict = {}; tmp_constructor.prototype.__acquired_method_dict = {};
...@@ -1283,45 +1335,6 @@ ...@@ -1283,45 +1335,6 @@
return root_gadget; return root_gadget;
} }
if (window.top !== window.self) {
//checking channel should be done before sub gadget's declaration
//__ready_list:
//0: clearGadgetInternalParameters
//1: loadSubGadgetDOMDeclaration
//.....
tmp_constructor.__ready_list.splice(1, 0, function () {
return root_gadget.__aq_parent('getTopURL', [], 100)
.then(function (topURL) {
var base = document.createElement('base');
base.href = topURL;
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) { tmp_constructor.ready(function (g) {
return startService(g); return startService(g);
}); });
...@@ -1346,11 +1359,15 @@ ...@@ -1346,11 +1359,15 @@
loading_gadget_promise loading_gadget_promise
.then(function () { .then(function () {
gadget_ready = true; gadget_ready = true;
if (connection_ready) {
notifyReady(); notifyReady();
}
}) })
.fail(function (e) { .fail(function (e) {
//top gadget in iframe //top gadget in iframe
if (iframe_top_gadget) { if (iframe_top_gadget) {
gadget_failed = true;
gadget_error = e.toString();
letsCrash(e); letsCrash(e);
} else { } else {
embedded_channel.notify({method: "failed", params: e.toString()}); embedded_channel.notify({method: "failed", params: e.toString()});
......
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Embedded page for renderJS test</title>
<meta name="viewport" content="width=device-width, height=device-height"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="../node_modules/rsvp/dist/rsvp-2.0.4.js" type="text/javascript"></script>
<script src="../dist/renderjs-latest.js" type="text/javascript"></script>
<script src="./embedded_heavy.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -3579,6 +3579,345 @@ ...@@ -3579,6 +3579,345 @@
}); });
}); });
test('checking working delayed communication iframe gadget', function () {
// Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(),
acquire_called = false,
url = "./embedded.html",
topURL = "http://example.org/topGadget";
function readyMessageDelay(e) {
var now,
then,
i = 0;
if (e.data.indexOf('{"method":"renderJS::__ready"') === 0) {
now = Date.now();
then = now + 150;
while (Date.now() < then) {
i += 1;
}
}
return i;
}
window.addEventListener('message', readyMessageDelay, false);
gadget.__aq_parent = function (method_name, argument_list) {
acquire_called = true;
equal(this, gadget, "Context should be kept");
if (method_name === "acquireMethodRequested") {
equal(method_name, "acquireMethodRequested",
"Method name should be kept");
deepEqual(argument_list, ["param1", "param2"],
"Argument list should be kept"
);
return "result correctly fetched from parent";
}
throw new renderJS.AcquisitionError("Can not handle " + method_name);
};
gadget.__sub_gadget_dict = {};
gadget.__acquired_method_dict = {
getTopURL: function () {return topURL; }
};
stop();
gadget.declareGadget(url, {
sandbox: 'iframe',
element: document.getElementById('qunit-fixture')
})
.then(function (new_gadget) {
return new RSVP.Queue()
// Method returns an RSVP.Queue
.push(function () {
var result = new_gadget.wasReadyCalled();
ok(
result instanceof RSVP.Queue,
"iframe method should return Queue"
);
})
// Check that ready function are called
.push(function () {
return new_gadget.wasReadyCalled();
})
.push(function (result) {
equal(result, true);
})
// Check that service are started
.push(function () {
return new_gadget.wasServiceStarted();
})
.push(function (result) {
equal(result, true);
})
// Check that service error can be reported
.push(function () {
return new_gadget.canReportServiceError();
})
.push(function (result) {
equal(result, true);
})
// Custom method accept parameter
// and return value
.push(function () {
return new_gadget.setContent("foobar");
})
.push(function (result) {
return new_gadget.getContent();
})
.push(function (result) {
equal(result, "foobar");
})
// Method are propagated
.push(function () {
return new_gadget.triggerError();
})
.push(function () {
ok(false, "triggerError should fail");
}, function (e) {
equal(e, "Error: Manually triggered embedded error");
})
// sub_gadget_dict private property is created
.push(function () {
return new_gadget.isSubGadgetDictInitialize();
})
.push(function (result) {
equal(result, true);
})
// acquired_method_dict is created on prototype
.push(function () {
return new_gadget.isAcquisitionDictInitialize();
})
.push(function (result) {
equal(result, true);
})
// service_list is created on prototype
.push(function () {
return new_gadget.isServiceListInitialize();
})
.push(function (result) {
equal(result, true);
})
// acquire check correctly returns result
.push(function () {
return new_gadget.callOKAcquire("param1", "param2");
})
.push(function (result) {
ok(acquire_called);
equal(result, "result correctly fetched from parent");
})
// acquire correctly returns error
.push(function () {
return new_gadget.callErrorAcquire(
"acquireMethodRequestedWithAcquisitionError",
["param1", "param2"]
);
})
.push(function (result) {
ok(false, result);
})
.push(undefined, function (error) {
equal(
error,
"AcquisitionError: Can not handle " +
"acquireMethodRequestedWithAcquisitionError",
error
);
})
.push(function () {
return new_gadget.getBaseHref();
})
.push(function (href) {
equal(href, topURL);
})
.push(function () {
return new_gadget.getBaseTarget();
})
.push(function (target) {
equal(target, "_top");
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
window.removeEventListener("message", readyMessageDelay);
});
});
test('checking working heavy iframe gadget', function () {
// Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(),
acquire_called = false,
url = "./embedded_heavy.html",
topURL = "http://example.org/topGadget";
gadget.__aq_parent = function (method_name, argument_list) {
acquire_called = true;
equal(this, gadget, "Context should be kept");
if (method_name === "acquireMethodRequested") {
equal(method_name, "acquireMethodRequested",
"Method name should be kept");
deepEqual(argument_list, ["param1", "param2"],
"Argument list should be kept"
);
return "result correctly fetched from parent";
}
throw new renderJS.AcquisitionError("Can not handle " + method_name);
};
gadget.__sub_gadget_dict = {};
gadget.__acquired_method_dict = {
getTopURL: function () {return topURL; }
};
stop();
gadget.declareGadget(url, {
sandbox: 'iframe',
element: document.getElementById('qunit-fixture')
})
.then(function (new_gadget) {
return new RSVP.Queue()
// Method returns an RSVP.Queue
.push(function () {
var result = new_gadget.wasReadyCalled();
ok(
result instanceof RSVP.Queue,
"iframe method should return Queue"
);
})
// Check that ready function are called
.push(function () {
return new_gadget.wasReadyCalled();
})
.push(function (result) {
equal(result, true);
})
// Check that service are started
.push(function () {
return new_gadget.wasServiceStarted();
})
.push(function (result) {
equal(result, true);
})
// Check that service error can be reported
.push(function () {
return new_gadget.canReportServiceError();
})
.push(function (result) {
equal(result, true);
})
// Custom method accept parameter
// and return value
.push(function () {
return new_gadget.setContent("foobar");
})
.push(function (result) {
return new_gadget.getContent();
})
.push(function (result) {
equal(result, "foobar");
})
// Method are propagated
.push(function () {
return new_gadget.triggerError();
})
.push(function () {
ok(false, "triggerError should fail");
}, function (e) {
equal(e, "Error: Manually triggered embedded error");
})
// sub_gadget_dict private property is created
.push(function () {
return new_gadget.isSubGadgetDictInitialize();
})
.push(function (result) {
equal(result, true);
})
// acquired_method_dict is created on prototype
.push(function () {
return new_gadget.isAcquisitionDictInitialize();
})
.push(function (result) {
equal(result, true);
})
// service_list is created on prototype
.push(function () {
return new_gadget.isServiceListInitialize();
})
.push(function (result) {
equal(result, true);
})
// acquire check correctly returns result
.push(function () {
return new_gadget.callOKAcquire("param1", "param2");
})
.push(function (result) {
ok(acquire_called);
equal(result, "result correctly fetched from parent");
})
// acquire correctly returns error
.push(function () {
return new_gadget.callErrorAcquire(
"acquireMethodRequestedWithAcquisitionError",
["param1", "param2"]
);
})
.push(function (result) {
ok(false, result);
})
.push(undefined, function (error) {
equal(
error,
"AcquisitionError: Can not handle " +
"acquireMethodRequestedWithAcquisitionError",
error
);
})
.push(function () {
return new_gadget.getBaseHref();
})
.push(function (href) {
equal(href, topURL);
})
.push(function () {
return new_gadget.getBaseTarget();
})
.push(function (target) {
equal(target, "_top");
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test('checking failing iframe gadget', function () { test('checking failing 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(),
......
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