Index: LayoutTests/http/tests/resources/testharness.js |
diff --git a/LayoutTests/http/tests/resources/testharness.js b/LayoutTests/http/tests/resources/testharness.js |
index 67b0f406c08fd9f634892eec50fb1f918d17e57c..9ad4f9aa5c1acdfd6be2c6903f53d7013e92fee2 100644 |
--- a/LayoutTests/http/tests/resources/testharness.js |
+++ b/LayoutTests/http/tests/resources/testharness.js |
@@ -22,7 +22,8 @@ policies and contribution forms [3]. |
"normal":10000, |
"long":60000 |
}, |
- test_timeout:null |
+ test_timeout:null, |
+ message_events: ["start", "test_state", "result", "completion"] |
}; |
var xhtml_ns = "http://www.w3.org/1999/xhtml"; |
@@ -64,6 +65,40 @@ policies and contribution forms [3]. |
this.output_handler = null; |
this.all_loaded = false; |
var this_obj = this; |
+ this.message_events = []; |
+ |
+ this.message_functions = { |
+ start: [add_start_callback, remove_start_callback, |
+ function (properties) { |
+ this_obj._dispatch("start_callback", [properties], |
+ {type: "start", properties: properties}); |
+ }], |
+ |
+ test_state: [add_test_state_callback, remove_test_state_callback, |
+ function(test) { |
+ this_obj._dispatch("test_state_callback", [test], |
+ {type: "test_state", |
+ test: test.structured_clone()}); |
+ }], |
+ result: [add_result_callback, remove_result_callback, |
+ function (test) { |
+ this_obj.output_handler.show_status(); |
+ this_obj._dispatch("result_callback", [test], |
+ {type: "result", |
+ test: test.structured_clone()}); |
+ }], |
+ completion: [add_completion_callback, remove_completion_callback, |
+ function (tests, harness_status) { |
+ var cloned_tests = map(tests, function(test) { |
+ return test.structured_clone(); |
+ }); |
+ this_obj._dispatch("completion_callback", [tests, harness_status], |
+ {type: "complete", |
+ tests: cloned_tests, |
+ status: harness_status.structured_clone()}); |
+ }] |
+ } |
+ |
on_event(window, 'load', function() { |
this_obj.all_loaded = true; |
}); |
@@ -150,30 +185,39 @@ policies and contribution forms [3]. |
this.output_handler = output; |
var this_obj = this; |
+ |
add_start_callback(function (properties) { |
this_obj.output_handler.init(properties); |
- this_obj._dispatch("start_callback", [properties], |
- { type: "start", properties: properties }); |
}); |
+ |
add_test_state_callback(function(test) { |
this_obj.output_handler.show_status(); |
- this_obj._dispatch("test_state_callback", [test], |
- { type: "test_state", test: test.structured_clone() }); |
}); |
+ |
add_result_callback(function (test) { |
this_obj.output_handler.show_status(); |
- this_obj._dispatch("result_callback", [test], |
- { type: "result", test: test.structured_clone() }); |
}); |
+ |
add_completion_callback(function (tests, harness_status) { |
this_obj.output_handler.show_results(tests, harness_status); |
- var cloned_tests = map(tests, function(test) { return test.structured_clone(); }); |
- this_obj._dispatch("completion_callback", [tests, harness_status], |
- { type: "complete", tests: cloned_tests, |
- status: harness_status.structured_clone() }); |
}); |
+ this.setup_messages(settings.message_events); |
}; |
+ WindowTestEnvironment.prototype.setup_messages = function(new_events) { |
+ var this_obj = this; |
+ forEach(settings.message_events, function(x) { |
+ var current_dispatch = this_obj.message_events.indexOf(x) !== -1; |
+ var new_dispatch = new_events.indexOf(x) !== -1; |
+ if (!current_dispatch && new_dispatch) { |
+ this_obj.message_functions[x][0](this_obj.message_functions[x][2]); |
+ } else if (current_dispatch && !new_dispatch) { |
+ this_obj.message_functions[x][1](this_obj.message_functions[x][2]); |
+ } |
+ }); |
+ this.message_events = new_events; |
+ } |
+ |
WindowTestEnvironment.prototype.next_default_test_name = function() { |
//Don't use document.title to work around an Opera bug in XHTML documents |
var title = document.getElementsByTagName("title")[0]; |
@@ -185,6 +229,9 @@ policies and contribution forms [3]. |
WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) { |
this.output_handler.setup(properties); |
+ if (properties.hasOwnProperty("message_events")) { |
+ this.setup_messages(properties.message_events); |
+ } |
}; |
WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) { |
@@ -368,8 +415,20 @@ policies and contribution forms [3]. |
self.addEventListener("message", |
function(event) { |
if (event.data.type && event.data.type === "connect") { |
- this_obj._add_message_port(event.ports[0]); |
- event.ports[0].start(); |
+ if (event.ports && event.ports[0]) { |
+ // If a MessageChannel was passed, then use it to |
+ // send results back to the main window. This |
+ // allows the tests to work even if the browser |
+ // does not fully support MessageEvent.source in |
+ // ServiceWorkers yet. |
+ this_obj._add_message_port(event.ports[0]); |
+ event.ports[0].start(); |
+ } else { |
+ // If there is no MessageChannel, then attempt to |
+ // use the MessageEvent.source to send results |
+ // back to the main window. |
+ this_obj._add_message_port(event.source); |
+ } |
} |
}); |
@@ -458,19 +517,27 @@ policies and contribution forms [3]. |
function promise_test(func, name, properties) { |
var test = async_test(name, properties); |
- Promise.resolve(test.step(func, test, test)) |
- .then( |
- function() { |
- test.done(); |
- }) |
- .catch(test.step_func( |
- function(value) { |
- if (value instanceof AssertionError) { |
- throw value; |
- } |
- assert(false, "promise_test", null, |
- "Unhandled rejection with value: ${value}", {value:value}); |
- })); |
+ // If there is no promise tests queue make one. |
+ test.step(function() { |
+ if (!tests.promise_tests) { |
+ tests.promise_tests = Promise.resolve(); |
+ } |
+ }); |
+ tests.promise_tests = tests.promise_tests.then(function() { |
+ return Promise.resolve(test.step(func, test, test)) |
+ .then( |
+ function() { |
+ test.done(); |
+ }) |
+ .catch(test.step_func( |
+ function(value) { |
+ if (value instanceof AssertionError) { |
+ throw value; |
+ } |
+ assert(false, "promise_test", null, |
+ "Unhandled rejection with value: ${value}", {value:value}); |
+ })); |
+ }); |
} |
function promise_rejects(test, expected, promise) { |
@@ -1436,13 +1503,13 @@ policies and contribution forms [3]. |
RemoteTest.prototype.structured_clone = function() { |
var clone = {}; |
Object.keys(this).forEach( |
- function(key) { |
+ (function(key) { |
if (typeof(this[key]) === "object") { |
clone[key] = merge({}, this[key]); |
} else { |
clone[key] = this[key]; |
} |
- }); |
+ }).bind(this)); |
clone.phases = merge({}, this.phases); |
return clone; |
}; |
@@ -1476,15 +1543,24 @@ policies and contribution forms [3]. |
var message_port; |
if (is_service_worker(worker)) { |
- // The ServiceWorker's implicit MessagePort is currently not |
- // reliably accessible from the ServiceWorkerGlobalScope due to |
- // Blink setting MessageEvent.source to null for messages sent via |
- // ServiceWorker.postMessage(). Until that's resolved, create an |
- // explicit MessageChannel and pass one end to the worker. |
- var message_channel = new MessageChannel(); |
- message_port = message_channel.port1; |
- message_port.start(); |
- worker.postMessage({type: "connect"}, [message_channel.port2]); |
+ if (window.MessageChannel) { |
+ // The ServiceWorker's implicit MessagePort is currently not |
+ // reliably accessible from the ServiceWorkerGlobalScope due to |
+ // Blink setting MessageEvent.source to null for messages sent |
+ // via ServiceWorker.postMessage(). Until that's resolved, |
+ // create an explicit MessageChannel and pass one end to the |
+ // worker. |
+ var message_channel = new MessageChannel(); |
+ message_port = message_channel.port1; |
+ message_port.start(); |
+ worker.postMessage({type: "connect"}, [message_channel.port2]); |
+ } else { |
+ // If MessageChannel is not available, then try the |
+ // ServiceWorker.postMessage() approach using MessageEvent.source |
+ // on the other end. |
+ message_port = navigator.serviceWorker; |
+ worker.postMessage({type: "connect"}); |
+ } |
} else if (is_shared_worker(worker)) { |
message_port = worker.port; |
} else { |
@@ -1837,14 +1913,12 @@ policies and contribution forms [3]. |
tests.test_state_callbacks.push(callback); |
} |
- function add_result_callback(callback) |
- { |
+ function add_result_callback(callback) { |
tests.test_done_callbacks.push(callback); |
} |
- function add_completion_callback(callback) |
- { |
- tests.all_done_callbacks.push(callback); |
+ function add_completion_callback(callback) { |
+ tests.all_done_callbacks.push(callback); |
} |
expose(add_start_callback, 'add_start_callback'); |
@@ -1852,6 +1926,29 @@ policies and contribution forms [3]. |
expose(add_result_callback, 'add_result_callback'); |
expose(add_completion_callback, 'add_completion_callback'); |
+ function remove(array, item) { |
+ var index = array.indexOf(item); |
+ if (index > -1) { |
+ array.splice(index, 1); |
+ } |
+ } |
+ |
+ function remove_start_callback(callback) { |
+ remove(tests.start_callbacks, callback); |
+ } |
+ |
+ function remove_test_state_callback(callback) { |
+ remove(tests.test_state_callbacks, callback); |
+ } |
+ |
+ function remove_result_callback(callback) { |
+ remove(tests.test_done_callbacks, callback); |
+ } |
+ |
+ function remove_completion_callback(callback) { |
+ remove(tests.all_done_callbacks, callback); |
+ } |
+ |
/* |
* Output listener |
*/ |
@@ -1959,28 +2056,11 @@ policies and contribution forms [3]. |
log.removeChild(log.lastChild); |
} |
- var script_prefix = null; |
- var scripts = document.getElementsByTagName("script"); |
- for (var i = 0; i < scripts.length; i++) { |
- var src; |
- if (scripts[i].src) { |
- src = scripts[i].src; |
- } else if (scripts[i].href) { |
- //SVG case |
- src = scripts[i].href.baseVal; |
- } |
- |
- var matches = src && src.match(/^(.*\/|)testharness\.js$/); |
- if (matches) { |
- script_prefix = matches[1]; |
- break; |
- } |
- } |
- |
- if (script_prefix !== null) { |
+ var harness_url = get_harness_url(); |
+ if (harness_url !== null) { |
var stylesheet = output_document.createElementNS(xhtml_ns, "link"); |
stylesheet.setAttribute("rel", "stylesheet"); |
- stylesheet.setAttribute("href", script_prefix + "testharness.css"); |
+ stylesheet.setAttribute("href", harness_url + "testharness.css"); |
var heads = output_document.getElementsByTagName("head"); |
if (heads.length) { |
heads[0].appendChild(stylesheet); |
@@ -2335,18 +2415,39 @@ policies and contribution forms [3]. |
AssertionError.prototype = Object.create(Error.prototype); |
AssertionError.prototype.get_stack = function() { |
- var lines = new Error().stack.split("\n"); |
- var rv = []; |
- var re = /\/resources\/testharness\.js/; |
+ var stack = new Error().stack; |
+ // IE11 does not initialize 'Error.stack' until the object is thrown. |
+ if (!stack) { |
+ try { |
+ throw new Error(); |
+ } catch (e) { |
+ stack = e.stack; |
+ } |
+ } |
+ |
+ var lines = stack.split("\n"); |
+ |
+ // Create a pattern to match stack frames originating within testharness.js. These include the |
+ // script URL, followed by the line/col (e.g., '/resources/testharness.js:120:21'). |
+ var re = new RegExp((get_script_url() || "\\btestharness.js") + ":\\d+:\\d+"); |
+ |
+ // Some browsers include a preamble that specifies the type of the error object. Skip this by |
+ // advancing until we find the first stack frame originating from testharness.js. |
var i = 0; |
- // Fire remove any preamble that doesn't match the regexp |
- while (!re.test(lines[i])) { |
- i++ |
+ while (!re.test(lines[i]) && i < lines.length) { |
+ i++; |
} |
- // Then remove top frames in testharness.js itself |
- while (re.test(lines[i])) { |
- i++ |
+ |
+ // Then skip the top frames originating from testharness.js to begin the stack at the test code. |
+ while (re.test(lines[i]) && i < lines.length) { |
+ i++; |
} |
+ |
+ // Paranoid check that we didn't skip all frames. If so, return the original stack unmodified. |
+ if (i >= lines.length) { |
+ return stack; |
+ } |
+ |
return lines.slice(i).join("\n"); |
} |
@@ -2394,7 +2495,7 @@ policies and contribution forms [3]. |
Array.prototype.push.apply(array, items); |
} |
- function forEach (array, callback, thisObj) |
+ function forEach(array, callback, thisObj) |
{ |
for (var i = 0; i < array.length; i++) { |
if (array.hasOwnProperty(i)) { |
@@ -2438,11 +2539,46 @@ policies and contribution forms [3]. |
} |
} |
+ /** Returns the 'src' URL of the first <script> tag in the page to include the file 'testharness.js'. */ |
+ function get_script_url() |
+ { |
+ if (!('document' in self)) { |
+ return undefined; |
+ } |
+ |
+ var scripts = document.getElementsByTagName("script"); |
+ for (var i = 0; i < scripts.length; i++) { |
+ var src; |
+ if (scripts[i].src) { |
+ src = scripts[i].src; |
+ } else if (scripts[i].href) { |
+ //SVG case |
+ src = scripts[i].href.baseVal; |
+ } |
+ |
+ var matches = src && src.match(/^(.*\/|)testharness\.js$/); |
+ if (matches) { |
+ return src; |
+ } |
+ } |
+ return undefined; |
+ } |
+ |
+ /** Returns the URL path at which the files for testharness.js are assumed to reside (e.g., '/resources/'). |
+ The path is derived from inspecting the 'src' of the <script> tag that included 'testharness.js'. */ |
+ function get_harness_url() |
+ { |
+ var script_url = get_script_url(); |
+ |
+ // Exclude the 'testharness.js' file from the returned path, but '+ 1' to include the trailing slash. |
+ return script_url ? script_url.slice(0, script_url.lastIndexOf('/') + 1) : undefined; |
+ } |
+ |
function supports_post_message(w) |
{ |
var supports; |
var type; |
- // Given IE implements postMessage across nested iframes but not across |
+ // 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. |
// |