Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for posting results using cross-origin communication. #3

Closed
wants to merge 11 commits into from
215 changes: 188 additions & 27 deletions testharness.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,22 @@ policies and contribution forms [3].
* These are given the same arguments as the corresponding internal callbacks
* described above.
*
* == External API through cross-document messaging ==
*
* Where supported, the test harness will also send messages using
* cross-document messaging to each ancestor browsing context. Since it
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this should be ancestor and opener

* uses the wildcard keyword (*), cross-origin communication is enabled and
* script on different origins can collect the results.
*
* This API follows similar conventions as those described above only slightly
* modified to accommodate message event API. Each message is sent by the harness
* is passed a single vanilla object, available as the `data` property of the
* event object. These objects are structures as follows:
*
* start - { type: "start" }
* result - { type: "result", test: Test }
* complete - { type: "complete", tests: [Test, ...], status: TestsStatus }
*
* == List of assertions ==
*
* assert_true(actual, description)
Expand Down Expand Up @@ -888,13 +904,29 @@ policies and contribution forms [3].
tests.push(this);
}

Test.prototype = {
Test.statuses = {
PASS:0,
FAIL:1,
TIMEOUT:2,
NOTRUN:3
};

Test.prototype = merge({}, Test.statuses);

Test.prototype.structured_clone = function()
{
if(!this._structured_clone)
{
var msg = this.message;
msg = msg ? String(msg) : msg;
this._structured_clone = merge({
name:String(this.name),
status:this.status,
message:msg
}, Test.statuses);
}
return this._structured_clone;
};

Test.prototype.step = function(func, this_obj)
{
Expand Down Expand Up @@ -1002,12 +1034,29 @@ policies and contribution forms [3].
this.status = null;
this.message = null;
}
TestsStatus.prototype = {

TestsStatus.statuses = {
OK:0,
ERROR:1,
TIMEOUT:2
};

TestsStatus.prototype = merge({}, TestsStatus.statuses);

TestsStatus.prototype.structured_clone = function()
{
if(!this._structured_clone)
{
var msg = this.message;
msg = msg ? String(msg) : msg;
this._structured_clone = merge({
status:this.status,
message:msg
}, TestsStatus.statuses);
}
return this._structured_clone;
};

function Tests()
{
this.tests = [];
Expand Down Expand Up @@ -1149,10 +1198,10 @@ policies and contribution forms [3].
{
callback(this_obj.properties);
});
forEach(ancestor_windows(),
function(w)
forEach_windows(
function(w, is_same_origin)
{
if(w.start_callback)
if(is_same_origin && w.start_callback)
{
try
{
Expand All @@ -1166,6 +1215,13 @@ policies and contribution forms [3].
}
}
}
if (supports_post_message(w))
{
w.postMessage({
type: "start",
properties: this_obj.properties
}, "*");
}
});
};

Expand All @@ -1189,10 +1245,10 @@ policies and contribution forms [3].
callback(test, this_obj);
});

forEach(ancestor_windows(),
function(w)
forEach_windows(
function(w, is_same_origin)
{
if(w.result_callback)
if(is_same_origin && w.result_callback)
{
try
{
Expand All @@ -1205,6 +1261,13 @@ policies and contribution forms [3].
}
}
}
if (supports_post_message(w))
{
w.postMessage({
type: "result",
test: test.structured_clone()
}, "*");
}
});
this.processing_callbacks = false;
if (this_obj.all_done())
Expand All @@ -1225,6 +1288,11 @@ policies and contribution forms [3].
{
clearTimeout(this.timeout_id);
var this_obj = this;
var tests = map(this_obj.tests,
function(test)
{
return test.structured_clone();
});
if (this.status.status === null)
{
this.status.status = this.status.OK;
Expand All @@ -1236,10 +1304,10 @@ policies and contribution forms [3].
callback(this_obj.tests, this_obj.status);
});

forEach(ancestor_windows(),
function(w)
forEach_windows(
function(w, is_same_origin)
{
if(w.completion_callback)
if(is_same_origin && w.completion_callback)
{
try
{
Expand All @@ -1253,6 +1321,14 @@ policies and contribution forms [3].
}
}
}
if (supports_post_message(w))
{
w.postMessage({
type: "complete",
tests: tests,
status: this_obj.status.structured_clone()
}, "*");
}
});
};

Expand Down Expand Up @@ -1802,22 +1878,107 @@ policies and contribution forms [3].
target[components[components.length - 1]] = object;
}

function ancestor_windows() {
//Get the windows [self ... top] as an array
if ("result_cache" in ancestor_windows)
{
return ancestor_windows.result_cache;
}
var rv = [self];
var w = self;
while (w != w.parent)
{
w = w.parent;
rv.push(w);
}
ancestor_windows.result_cache = rv;
return rv;
}
function forEach_windows(callback) {
// Iterate of the the windows [self ... top, opener]. The callback is passed
// two objects, the first one is the windows object itself, the second one
// is a boolean indicating whether or not its on the same origin as the
// current window.
var cache = forEach_windows.result_cache;
if (!cache) {
cache = [[self, true]];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is all kind of ugly, but I'm not sure I have a better suggestion…

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Spent some time trying to find a decent solution. That's the best I could come up with given the constraints (and the WebKit ugliness). Open to suggestions.

var w = self;
var i = 0;
var so;
var origins = location.ancestorOrigins;
while (w != w.parent)
{
w = w.parent;
// In WebKit, calls to parent windows' properties that aren't on the same
// origin cause an error message to be displayed in the error console but
// don't throw an exception. This is a deviation from the current HTML5
// spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
// The problem with WebKit's behavior is that it pollutes the error console
// with error messages that can't be caught.
//
// This issue can be mitigated by relying on the (for now) proprietary
// `location.ancestorOrigins` property which returns an ordered list of
// the origins of enclosing windows. See:
// http://trac.webkit.org/changeset/113945.
if(origins) {
so = (location.origin == origins[i]);
}
else
{
so = is_same_origin(w);
}
cache.push([w, so]);
i++;
}
w = window.opener;
if(w)
{
// window.opener isn't included in the `location.ancestorOrigins` prop.
// We'll just have to deal with a simple check and an error msg on WebKit
// browsers in this case.
cache.push([w, is_same_origin(w)]);
}
forEach_windows.result_cache = cache;
}

forEach(cache,
function(a)
{
callback.apply(null, a);
});
}

function is_same_origin(w) {
try {
'random_prop' in w;
return true;
} catch(e) {
return false;
}
}

function supports_post_message(w)
{
var supports;
var type;
// Given IE implements postMessage across nested iframes but not across
// windows or tabs, you can't infer cross-origin communication from the presence
// of postMessage on the current window object only.
//
// Touching the postMessage prop on a window can throw if the window is
// not from the same origin AND post message is not supported in that
// browser. So just doing an existence test here won't do, you also need
// to wrap it in a try..cacth block.
try
{
type = typeof w.postMessage;
if (type === "function")
{
supports = true;
}
// IE8 supports postMessage, but implements it as a host object which
// returns "object" as its `typeof`.
else if (type === "object")
{
supports = true;
}
// This is the case where postMessage isn't supported AND accessing a
// window property across origins does NOT throw (e.g. old Safari browser).
else
{
supports = false;
}
}
catch(e) {
// This is the case where postMessage isn't supported AND accessing a
// window property across origins throws (e.g. old Firefox browser).
supports = false;
}
return supports;
}
})();
// vim: set expandtab shiftwidth=4 tabstop=4: