diff --git a/test/common/wpt.js b/test/common/wpt.js index 60be82564bf229..8401d101dc881f 100644 --- a/test/common/wpt.js +++ b/test/common/wpt.js @@ -336,7 +336,6 @@ class WPTRunner { fetch(file) { return resource.fetch(file); }, - location: {}, GLOBAL: { isWindow() { return false; } }, @@ -375,8 +374,6 @@ class WPTRunner { // TODO(joyeecheung): we are not a window - work with the upstream to // add a new scope for us. - const { Worker } = require('worker_threads'); - sandbox.DedicatedWorker = Worker; // Pretend we are a Worker return context; } diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 51450f918bd1b8..1865d6698c9faa 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -12,8 +12,8 @@ Last update: - console: https://github.com/web-platform-tests/wpt/tree/9786a4b131/console - encoding: https://github.com/web-platform-tests/wpt/tree/a093a659ed/encoding -- url: https://github.com/web-platform-tests/wpt/tree/75b0f336c5/url -- resources: https://github.com/web-platform-tests/wpt/tree/679a364421/resources +- url: https://github.com/web-platform-tests/wpt/tree/418f7fabeb/url +- resources: https://github.com/web-platform-tests/wpt/tree/e1fddfbf80/resources - interfaces: https://github.com/web-platform-tests/wpt/tree/712c9f275e/interfaces - html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/0c3bed38df/html/webappapis/microtask-queuing - html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/ddfe9c089b/html/webappapis/timers diff --git a/test/fixtures/wpt/resources/idlharness.js b/test/fixtures/wpt/resources/idlharness.js index f1a39ae9f2692f..c7a040996b9994 100644 --- a/test/fixtures/wpt/resources/idlharness.js +++ b/test/fixtures/wpt/resources/idlharness.js @@ -298,7 +298,9 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) }.bind(this)); deps.forEach(function(name) { - new_options.only.push(name); + if (!new_options.only.includes(name)) { + new_options.only.push(name); + } const follow_up = new Set(); for (const dep_type of ["inheritance", "implements", "includes"]) { @@ -306,13 +308,19 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) const inheriting = parsed[dep_type]; const inheritor = parsed.name || parsed.target; const deps = [inheriting]; - // For A includes B, we can ignore A unless B is being tested. + // For A includes B, we can ignore A, unless B (or some of its + // members) is being tested. if (dep_type !== "includes" - || (inheriting in this.members && !this.members[inheriting].untested)) { + || inheriting in this.members && !this.members[inheriting].untested + || this.partials.some(function(p) { + return p.name === inheriting; + })) { deps.push(inheritor); } for (const dep of deps) { - new_options.only.push(dep); + if (!new_options.only.includes(dep)) { + new_options.only.push(dep); + } all_deps.add(dep); follow_up.add(dep); } @@ -718,9 +726,6 @@ function exposed_in(globals) { self instanceof ServiceWorkerGlobalScope) { return globals.has("ServiceWorker"); } - if ('NodeScope' in self) { - return true; - } throw new IdlHarnessError("Unexpected global object"); } @@ -1669,7 +1674,122 @@ IdlInterface.prototype.test_self = function() }.bind(this), this.name + " interface: legacy window alias"); } - // TODO: Test named constructors if I find any interfaces that have them. + + if (this.has_extended_attribute("NamedConstructor")) { + var constructors = this.extAttrs + .filter(function(attr) { return attr.name == "NamedConstructor"; }); + if (constructors.length !== 1) { + throw new IdlHarnessError("Internal error: missing support for multiple NamedConstructor extended attributes"); + } + var constructor = constructors[0]; + var min_length = minOverloadLength([constructor]); + + subsetTestByKey(this.name, test, function() + { + // This function tests WebIDL as of 2019-01-14. + + // "for every [NamedConstructor] extended attribute on an exposed + // interface, a corresponding property must exist on the ECMAScript + // global object. The name of the property is the + // [NamedConstructor]'s identifier, and its value is an object + // called a named constructor, ... . The property has the attributes + // { [[Writable]]: true, [[Enumerable]]: false, + // [[Configurable]]: true }." + var name = constructor.rhs.value; + assert_own_property(self, name); + var desc = Object.getOwnPropertyDescriptor(self, name); + assert_equals(desc.value, self[name], "wrong value in " + name + " property descriptor"); + assert_true(desc.writable, name + " should be writable"); + assert_false(desc.enumerable, name + " should not be enumerable"); + assert_true(desc.configurable, name + " should be configurable"); + assert_false("get" in desc, name + " should not have a getter"); + assert_false("set" in desc, name + " should not have a setter"); + }.bind(this), this.name + " interface: named constructor"); + + subsetTestByKey(this.name, test, function() + { + // This function tests WebIDL as of 2019-01-14. + + // "2. Let F be ! CreateBuiltinFunction(realm, steps, + // realm.[[Intrinsics]].[[%FunctionPrototype%]])." + var name = constructor.rhs.value; + var value = self[name]; + assert_equals(typeof value, "function", "type of value in " + name + " property descriptor"); + assert_not_equals(value, this.get_interface_object(), "wrong value in " + name + " property descriptor"); + assert_equals(Object.getPrototypeOf(value), Function.prototype, "wrong value for " + name + "'s prototype"); + }.bind(this), this.name + " interface: named constructor object"); + + subsetTestByKey(this.name, test, function() + { + // This function tests WebIDL as of 2019-01-14. + + // "7. Let proto be the interface prototype object of interface I + // in realm. + // "8. Perform ! DefinePropertyOrThrow(F, "prototype", + // PropertyDescriptor{ + // [[Value]]: proto, [[Writable]]: false, + // [[Enumerable]]: false, [[Configurable]]: false + // })." + var name = constructor.rhs.value; + var expected = this.get_interface_object().prototype; + var desc = Object.getOwnPropertyDescriptor(self[name], "prototype"); + assert_equals(desc.value, expected, "wrong value for " + name + ".prototype"); + assert_false(desc.writable, "prototype should not be writable"); + assert_false(desc.enumerable, "prototype should not be enumerable"); + assert_false(desc.configurable, "prototype should not be configurable"); + assert_false("get" in desc, "prototype should not have a getter"); + assert_false("set" in desc, "prototype should not have a setter"); + }.bind(this), this.name + " interface: named constructor prototype property"); + + subsetTestByKey(this.name, test, function() + { + // This function tests WebIDL as of 2019-01-14. + + // "3. Perform ! SetFunctionName(F, id)." + var name = constructor.rhs.value; + var desc = Object.getOwnPropertyDescriptor(self[name], "name"); + assert_equals(desc.value, name, "wrong value for " + name + ".name"); + assert_false(desc.writable, "name should not be writable"); + assert_false(desc.enumerable, "name should not be enumerable"); + assert_true(desc.configurable, "name should be configurable"); + assert_false("get" in desc, "name should not have a getter"); + assert_false("set" in desc, "name should not have a setter"); + }.bind(this), this.name + " interface: named constructor name"); + + subsetTestByKey(this.name, test, function() + { + // This function tests WebIDL as of 2019-01-14. + + // "4. Initialize S to the effective overload set for constructors + // with identifier id on interface I and with argument count 0. + // "5. Let length be the length of the shortest argument list of + // the entries in S. + // "6. Perform ! SetFunctionLength(F, length)." + var name = constructor.rhs.value; + var desc = Object.getOwnPropertyDescriptor(self[name], "length"); + assert_equals(desc.value, min_length, "wrong value for " + name + ".length"); + assert_false(desc.writable, "length should not be writable"); + assert_false(desc.enumerable, "length should not be enumerable"); + assert_true(desc.configurable, "length should be configurable"); + assert_false("get" in desc, "length should not have a getter"); + assert_false("set" in desc, "length should not have a setter"); + }.bind(this), this.name + " interface: named constructor length"); + + subsetTestByKey(this.name, test, function() + { + // This function tests WebIDL as of 2019-01-14. + + // "1. Let steps be the following steps: + // " 1. If NewTarget is undefined, then throw a TypeError." + var name = constructor.rhs.value; + var args = constructor.arguments.map(function(arg) { + return create_suitable_object(arg.idlType); + }); + assert_throws(new TypeError(), function() { + self[name](...args); + }.bind(this)); + }.bind(this), this.name + " interface: named constructor without 'new'"); + } subsetTestByKey(this.name, test, function() { @@ -2304,22 +2424,7 @@ IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject } } -IdlInterface.prototype.add_iterable_members = function(member) -{ - this.members.push(new IdlInterfaceMember( - { type: "operation", name: "entries", idlType: "iterator", arguments: []})); - this.members.push(new IdlInterfaceMember( - { type: "operation", name: "keys", idlType: "iterator", arguments: []})); - this.members.push(new IdlInterfaceMember( - { type: "operation", name: "values", idlType: "iterator", arguments: []})); - this.members.push(new IdlInterfaceMember( - { type: "operation", name: "forEach", idlType: "void", - arguments: - [{ name: "callback", idlType: {idlType: "function"}}, - { name: "thisValue", idlType: {idlType: "any"}, optional: true}]})); -}; - -IdlInterface.prototype.test_to_json_operation = function(memberHolderObject, member) { +IdlInterface.prototype.test_to_json_operation = function(desc, memberHolderObject, member) { var instanceName = memberHolderObject && memberHolderObject.constructor.name || member.name + " object"; if (member.has_extended_attribute("Default")) { @@ -2335,38 +2440,40 @@ IdlInterface.prototype.test_to_json_operation = function(memberHolderObject, mem this.array.assert_type_is(json[k], type); delete json[k]; }, this); - }.bind(this), "Test default toJSON operation of " + instanceName); + }.bind(this), this.name + " interface: default toJSON operation on " + desc); } else { subsetTestByKey(this.name, test, function() { assert_true(this.array.is_json_type(member.idlType), JSON.stringify(member.idlType) + " is not an appropriate return value for the toJSON operation of " + instanceName); this.array.assert_type_is(memberHolderObject.toJSON(), member.idlType); - }.bind(this), "Test toJSON operation of " + instanceName); + }.bind(this), this.name + " interface: toJSON operation on " + desc); } }; IdlInterface.prototype.test_member_iterable = function(member) { - var isPairIterator = member.idlType.length === 2; subsetTestByKey(this.name, test, function() { - var descriptor = Object.getOwnPropertyDescriptor(this.get_interface_object().prototype, Symbol.iterator); - assert_true(descriptor.writable, "property should be writable"); - assert_true(descriptor.configurable, "property should be configurable"); - assert_false(descriptor.enumerable, "property should not be enumerable"); - assert_equals(this.get_interface_object().prototype[Symbol.iterator].name, isPairIterator ? "entries" : "values", "@@iterator function does not have the right name"); - }.bind(this), "Testing Symbol.iterator property of iterable interface " + this.name); - - if (isPairIterator) { - subsetTestByKey(this.name, test, function() { - assert_equals(this.get_interface_object().prototype[Symbol.iterator], this.get_interface_object().prototype["entries"], "entries method is not the same as @@iterator"); - }.bind(this), "Testing pair iterable interface " + this.name); - } else { - subsetTestByKey(this.name, test, function() { - ["entries", "keys", "values", "forEach", Symbol.Iterator].forEach(function(property) { - assert_equals(this.get_interface_object().prototype[property], Array.prototype[property], property + " function is not the same as Array one"); + var isPairIterator = member.idlType.length === 2; + var proto = this.get_interface_object().prototype; + var descriptor = Object.getOwnPropertyDescriptor(proto, Symbol.iterator); + + assert_true(descriptor.writable, "@@iterator property should be writable"); + assert_true(descriptor.configurable, "@@iterator property should be configurable"); + assert_false(descriptor.enumerable, "@@iterator property should not be enumerable"); + assert_equals(typeof descriptor.value, "function", "@@iterator property should be a function"); + assert_equals(descriptor.value.length, 0, "@@iterator function object length should be 0"); + assert_equals(descriptor.value.name, isPairIterator ? "entries" : "values", "@@iterator function object should have the right name"); + + if (isPairIterator) { + assert_equals(proto["entries"], proto[Symbol.iterator], "entries method should be the same as @@iterator method"); + } else { + assert_equals(proto[Symbol.iterator], Array.prototype[Symbol.iterator], "@@iterator method should be the same as Array prototype's"); + ["entries", "keys", "values", "forEach", Symbol.iterator].forEach(function(property) { + var propertyName = property === Symbol.iterator ? "@@iterator" : property; + assert_equals(proto[property], Array.prototype[property], propertyName + " method should be the same as Array prototype's"); }.bind(this)); - }.bind(this), "Testing value iterable interface " + this.name); - } + } + }.bind(this), this.name + " interface: iterable<" + member.idlType.map(function(t) { return t.idlType; }).join(", ") + ">"); }; IdlInterface.prototype.test_member_stringifier = function(member) @@ -2432,19 +2539,6 @@ IdlInterface.prototype.test_member_stringifier = function(member) IdlInterface.prototype.test_members = function() { - for (var i = 0; i < this.members.length; i++) - { - var member = this.members[i]; - switch (member.type) { - case "iterable": - this.add_iterable_members(member); - break; - // TODO: add setlike and maplike handling. - default: - break; - } - } - for (var i = 0; i < this.members.length; i++) { var member = this.members[i]; @@ -2743,7 +2837,7 @@ IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expect } if (member.is_to_json_regular_operation()) { - this.test_to_json_operation(obj, member); + this.test_to_json_operation(desc, obj, member); } } }; diff --git a/test/fixtures/wpt/resources/sriharness.js b/test/fixtures/wpt/resources/sriharness.js index b36d29223585ec..9d7fa76a7d65f6 100644 --- a/test/fixtures/wpt/resources/sriharness.js +++ b/test/fixtures/wpt/resources/sriharness.js @@ -97,3 +97,4 @@ SRIStyleTest.prototype.execute = function() { container.appendChild(e); this.customCallback(e, container); }; + diff --git a/test/fixtures/wpt/resources/testdriver-actions.js b/test/fixtures/wpt/resources/testdriver-actions.js index 46c68858e45746..43d8b1df00ae4c 100644 --- a/test/fixtures/wpt/resources/testdriver-actions.js +++ b/test/fixtures/wpt/resources/testdriver-actions.js @@ -7,7 +7,7 @@ function Actions() { this.sourceTypes = new Map([["key", KeySource], ["pointer", PointerSource], - ["general", GeneralSource]]); + ["none", GeneralSource]]); this.sources = new Map(); this.sourceOrder = []; for (let sourceType of this.sourceTypes.keys()) { @@ -17,11 +17,19 @@ for (let sourceType of this.sourceTypes.keys()) { this.currentSources.set(sourceType, null); } - this.createSource("general"); + this.createSource("none"); this.tickIdx = 0; } Actions.prototype = { + ButtonType: { + LEFT: 0, + MIDDLE: 1, + RIGHT: 2, + BACK: 3, + FORWARD: 4, + }, + /** * Generate the action sequence suitable for passing to * test_driver.action_sequence @@ -62,7 +70,7 @@ * If no name is passed, a new source with the given type is * created. * - * @param {String} type - Source type ('general', 'key', or 'pointer') + * @param {String} type - Source type ('none', 'key', or 'pointer') * @param {String?} name - Name of the source * @returns {Source} Source object for that source. */ @@ -98,7 +106,7 @@ * @returns {Actions} */ addKeyboard: function(name, set=true) { - this.createSource("key", name, true); + this.createSource("key", name); if (set) { this.setKeyboard(name); } @@ -125,7 +133,7 @@ * @returns {Actions} */ addPointer: function(name, pointerType="mouse", set=true) { - this.createSource("pointer", name, true, {pointerType: pointerType}); + this.createSource("pointer", name, {pointerType: pointerType}); if (set) { this.setPointer(name); } @@ -187,7 +195,7 @@ * @returns {Actions} */ pause: function(duration) { - this.getSource("general").addPause(this, duration); + this.getSource("none").addPause(this, duration); return this; }, @@ -225,7 +233,7 @@ * pointer source * @returns {Actions} */ - pointerDown: function({button=0, sourceName=null}={}) { + pointerDown: function({button=this.ButtonType.LEFT, sourceName=null}={}) { let source = this.getSource("pointer", sourceName); source.pointerDown(this, button); return this; @@ -239,7 +247,7 @@ * source * @returns {Actions} */ - pointerUp: function({button=0, sourceName=null}={}) { + pointerUp: function({button=this.ButtonType.LEFT, sourceName=null}={}) { let source = this.getSource("pointer", sourceName); source.pointerUp(this, button); return this; diff --git a/test/fixtures/wpt/resources/testdriver.js b/test/fixtures/wpt/resources/testdriver.js index e0741e8d61d4d6..031be1b7e55016 100644 --- a/test/fixtures/wpt/resources/testdriver.js +++ b/test/fixtures/wpt/resources/testdriver.js @@ -192,32 +192,89 @@ * @returns {Promise} fufiled after the actions are performed, or rejected in * the cases the WebDriver command errors */ - action_sequence(actions) { + action_sequence: function(actions) { return window.test_driver_internal.action_sequence(actions); + }, + + /** + * Generates a test report on the current page + * + * The generate_test_report function generates a report (to be observed + * by ReportingObserver) for testing purposes, as described in + * {@link https://w3c.github.io/reporting/#generate-test-report-command} + * + * @returns {Promise} fulfilled after the report is generated, or + * rejected if the report generation fails + */ + generate_test_report: function(message) { + return window.test_driver_internal.generate_test_report(message); } }; window.test_driver_internal = { /** - * Triggers a user-initiated click + * This flag should be set to `true` by any code which implements the + * internal methods defined below for automation purposes. Doing so + * allows the library to signal failure immediately when an automated + * implementation of one of the methods is not available. + */ + in_automation: false, + + /** + * Waits for a user-initiated click * * @param {Element} element - element to be clicked * @param {{x: number, y: number} coords - viewport coordinates to click at - * @returns {Promise} fulfilled after click occurs or rejected if click fails + * @returns {Promise} fulfilled after click occurs */ click: function(element, coords) { - return Promise.reject(new Error("unimplemented")); + if (this.in_automation) { + return Promise.reject(new Error('Not implemented')); + } + + return new Promise(function(resolve, reject) { + element.addEventListener("click", resolve); + }); }, /** - * Triggers a user-initiated click + * Waits for an element to receive a series of key presses * - * @param {Element} element - element to be clicked - * @param {String} keys - keys to send to the element - * @returns {Promise} fulfilled after keys are sent or rejected if click fails + * @param {Element} element - element which should receve key presses + * @param {String} keys - keys to expect + * @returns {Promise} fulfilled after keys are received or rejected if + * an incorrect key sequence is received */ send_keys: function(element, keys) { - return Promise.reject(new Error("unimplemented")); + if (this.in_automation) { + return Promise.reject(new Error('Not implemented')); + } + + return new Promise(function(resolve, reject) { + var seen = ""; + + function remove() { + element.removeEventListener("keydown", onKeyDown); + } + + function onKeyDown(event) { + if (event.key.length > 1) { + return; + } + + seen += event.key; + + if (keys.indexOf(seen) !== 0) { + reject(new Error("Unexpected key sequence: " + seen)); + remove(); + } else if (seen === keys) { + resolve(); + remove(); + } + } + + element.addEventListener("keydown", onKeyDown); + }); }, /** @@ -238,6 +295,17 @@ */ action_sequence: function(actions) { return Promise.reject(new Error("unimplemented")); + }, + + /** + * Generates a test report on the current page + * + * @param {String} message - the message to be contained in the report + * @returns {Promise} fulfilled after the report is generated, or + * rejected if the report generation fails + */ + generate_test_report: function(message) { + return Promise.reject(new Error("unimplemented")); } }; })(); diff --git a/test/fixtures/wpt/resources/testharness.js b/test/fixtures/wpt/resources/testharness.js index 18a6f70beab26b..bffdf022b33255 100644 --- a/test/fixtures/wpt/resources/testharness.js +++ b/test/fixtures/wpt/resources/testharness.js @@ -513,7 +513,7 @@ policies and contribution forms [3]. return new DedicatedWorkerTestEnvironment(); } - if (!('self' in global_scope)) { + if (!('location' in global_scope)) { return new ShellTestEnvironment(); } @@ -635,7 +635,7 @@ policies and contribution forms [3]. * which can make it a lot easier to test a very specific series of events, * including ensuring that unexpected events are not fired at any point. */ - function EventWatcher(test, watchedNode, eventTypes) + function EventWatcher(test, watchedNode, eventTypes, timeoutPromise) { if (typeof eventTypes == 'string') { eventTypes = [eventTypes]; @@ -712,6 +712,27 @@ policies and contribution forms [3]. recordedEvents = []; } return new Promise(function(resolve, reject) { + var timeout = test.step_func(function() { + // If the timeout fires after the events have been received + // or during a subsequent call to wait_for, ignore it. + if (!waitingFor || waitingFor.resolve !== resolve) + return; + + // This should always fail, otherwise we should have + // resolved the promise. + assert_true(waitingFor.types.length == 0, + 'Timed out waiting for ' + waitingFor.types.join(', ')); + var result = recordedEvents; + recordedEvents = null; + var resolveFunc = waitingFor.resolve; + waitingFor = null; + resolveFunc(result); + }); + + if (timeoutPromise) { + timeoutPromise().then(timeout); + } + waitingFor = { types: types, resolve: resolve, @@ -1111,7 +1132,7 @@ policies and contribution forms [3]. assert(Math.abs(actual[i] - expected[i]) <= epsilon, "assert_array_approx_equals", description, "property ${i}, expected ${expected} +/- ${epsilon}, expected ${expected} but got ${actual}", - {i:i, expected:expected[i], actual:actual[i]}); + {i:i, expected:expected[i], actual:actual[i], epsilon:epsilon}); } } expose(assert_array_approx_equals, "assert_array_approx_equals"); @@ -1478,7 +1499,7 @@ policies and contribution forms [3]. } this.name = name; - this.phase = tests.is_aborted ? + this.phase = (tests.is_aborted || tests.phase === tests.phases.COMPLETE) ? this.phases.COMPLETE : this.phases.INITIAL; this.status = this.NOTRUN; @@ -1486,11 +1507,9 @@ policies and contribution forms [3]. this.index = null; this.properties = properties; - var timeout = properties.timeout ? properties.timeout : settings.test_timeout; - if (timeout !== null) { - this.timeout_length = timeout * tests.timeout_multiplier; - } else { - this.timeout_length = null; + this.timeout_length = settings.test_timeout; + if (this.timeout_length !== null) { + this.timeout_length *= tests.timeout_multiplier; } this.message = null; @@ -1503,6 +1522,13 @@ policies and contribution forms [3]. this._user_defined_cleanup_count = 0; this._done_callbacks = []; + // Tests declared following harness completion are likely an indication + // of a programming error, but they cannot be reported + // deterministically. + if (tests.phase === tests.phases.COMPLETE) { + return; + } + tests.push(this); } @@ -1904,7 +1930,9 @@ policies and contribution forms [3]. */ function RemoteContext(remote, message_target, message_filter) { this.running = true; + this.started = false; this.tests = new Array(); + this.early_exception = null; var this_obj = this; // If remote context is cross origin assigning to onerror is not @@ -1943,6 +1971,21 @@ policies and contribution forms [3]. } RemoteContext.prototype.remote_error = function(error) { + if (error.preventDefault) { + error.preventDefault(); + } + + // Defer interpretation of errors until the testing protocol has + // started and the remote test's `allow_uncaught_exception` property + // is available. + if (!this.started) { + this.early_exception = error; + } else if (!this.allow_uncaught_exception) { + this.report_uncaught(error); + } + }; + + RemoteContext.prototype.report_uncaught = function(error) { var message = error.message || String(error); var filename = (error.filename ? " " + error.filename: ""); // FIXME: Display remote error states separately from main document @@ -1950,9 +1993,14 @@ policies and contribution forms [3]. tests.set_status(tests.status.ERROR, "Error in remote" + filename + ": " + message, error.stack); + }; - if (error.preventDefault) { - error.preventDefault(); + RemoteContext.prototype.start = function(data) { + this.started = true; + this.allow_uncaught_exception = data.properties.allow_uncaught_exception; + + if (this.early_exception && !this.allow_uncaught_exception) { + this.report_uncaught(this.early_exception); } }; @@ -2002,6 +2050,7 @@ policies and contribution forms [3]. }; RemoteContext.prototype.message_handlers = { + start: RemoteContext.prototype.start, test_state: RemoteContext.prototype.test_state, result: RemoteContext.prototype.test_done, complete: RemoteContext.prototype.remote_done @@ -2114,6 +2163,9 @@ policies and contribution forms [3]. } } else if (p == "timeout_multiplier") { this.timeout_multiplier = value; + if (this.timeout_length) { + this.timeout_length *= this.timeout_multiplier; + } } } } @@ -2338,6 +2390,42 @@ policies and contribution forms [3]. return duplicates; }; + function code_unit_str(char) { + return 'U+' + char.charCodeAt(0).toString(16); + } + + function sanitize_unpaired_surrogates(str) { + return str.replace(/([\ud800-\udbff])(?![\udc00-\udfff])/g, + function(_, unpaired) + { + return code_unit_str(unpaired); + }) + // This replacement is intentionally implemented without an + // ES2018 negative lookbehind assertion to support runtimes + // which do not yet implement that language feature. + .replace(/(^|[^\ud800-\udbff])([\udc00-\udfff])/g, + function(_, previous, unpaired) { + if (/[\udc00-\udfff]/.test(previous)) { + previous = code_unit_str(previous); + } + + return previous + code_unit_str(unpaired); + }); + } + + function sanitize_all_unpaired_surrogates(tests) { + forEach (tests, + function (test) + { + var sanitized = sanitize_unpaired_surrogates(test.name); + + if (test.name !== sanitized) { + test.name = sanitized; + delete test._structured_clone; + } + }); + } + Tests.prototype.notify_complete = function() { var this_obj = this; var duplicates; @@ -2345,6 +2433,11 @@ policies and contribution forms [3]. if (this.status.status === null) { duplicates = this.find_duplicates(); + // Some transports adhere to UTF-8's restriction on unpaired + // surrogates. Sanitize the titles so that the results can be + // consistently sent via all transports. + sanitize_all_unpaired_surrogates(this.tests); + // Test names are presumed to be unique within test files--this // allows consumers to use them for identification purposes. // Duplicated names violate this expectation and should therefore @@ -2536,6 +2629,9 @@ policies and contribution forms [3]. Output.prototype.resolve_log = function() { var output_document; + if (this.output_node) { + return; + } if (typeof this.output_document === "function") { output_document = this.output_document.apply(undefined); } else { @@ -2546,7 +2642,7 @@ policies and contribution forms [3]. } var node = output_document.getElementById("log"); if (!node) { - if (!document.readyState == "loading") { + if (output_document.readyState === "loading") { return; } node = output_document.createElementNS("http://www.w3.org/1999/xhtml", "div"); @@ -2583,11 +2679,11 @@ policies and contribution forms [3]. if (this.phase < this.STARTED) { this.init(); } - if (!this.enabled) { + if (!this.enabled || this.phase === this.COMPLETE) { return; } + this.resolve_log(); if (this.phase < this.HAVE_RESULTS) { - this.resolve_log(); this.phase = this.HAVE_RESULTS; } var done_count = tests.tests.length - tests.num_pending; diff --git a/test/fixtures/wpt/url/META.yml b/test/fixtures/wpt/url/META.yml index 3a789a0d513f0a..094b266b64b61b 100644 --- a/test/fixtures/wpt/url/META.yml +++ b/test/fixtures/wpt/url/META.yml @@ -2,7 +2,6 @@ spec: https://url.spec.whatwg.org/ suggested_reviewers: - mikewest - domenic - - Sebmaster - annevk - GPHemsley - TimothyGu diff --git a/test/fixtures/wpt/url/historical.any.js b/test/fixtures/wpt/url/historical.any.js index c3797ad263850c..407e118f3a05f9 100644 --- a/test/fixtures/wpt/url/historical.any.js +++ b/test/fixtures/wpt/url/historical.any.js @@ -1,7 +1,9 @@ -test(function() { - assert_false("searchParams" in self.location, - "location object should not have a searchParams attribute"); -}, "searchParams on location object"); +if (self.location) { + test(function() { + assert_false("searchParams" in self.location, + "location object should not have a searchParams attribute"); + }, "searchParams on location object"); +} if(self.GLOBAL.isWindow()) { test(() => { diff --git a/test/fixtures/wpt/url/resources/urltestdata.json b/test/fixtures/wpt/url/resources/urltestdata.json index 26b8ea2e0bc9a1..bf4e2a7833d17f 100644 --- a/test/fixtures/wpt/url/resources/urltestdata.json +++ b/test/fixtures/wpt/url/resources/urltestdata.json @@ -4633,6 +4633,22 @@ "search": "", "hash": "" }, + "# unknown scheme with non-URL characters in the path", + { + "input": "wow:\uFFFF", + "base": "about:blank", + "href": "wow:%EF%BF%BF", + "origin": "null", + "protocol": "wow:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "%EF%BF%BF", + "search": "", + "hash": "" + }, "# Hosts and percent-encoding", { "input": "ftp://example.com%80/", diff --git a/test/fixtures/wpt/url/urlsearchparams-constructor.any.js b/test/fixtures/wpt/url/urlsearchparams-constructor.any.js index 6fff03f00fdddd..398021dde2f35f 100644 --- a/test/fixtures/wpt/url/urlsearchparams-constructor.any.js +++ b/test/fixtures/wpt/url/urlsearchparams-constructor.any.js @@ -14,6 +14,11 @@ test(function() { assert_equals(params.toString(), "") }, "URLSearchParams constructor, no arguments") +test(function () { + var params = new URLSearchParams("?a=b") + assert_equals(params.toString(), "a=b") +}, 'URLSearchParams constructor, remove leading "?"') + test(() => { var params = new URLSearchParams(DOMException); assert_equals(params.toString(), "INDEX_SIZE_ERR=1&DOMSTRING_SIZE_ERR=2&HIERARCHY_REQUEST_ERR=3&WRONG_DOCUMENT_ERR=4&INVALID_CHARACTER_ERR=5&NO_DATA_ALLOWED_ERR=6&NO_MODIFICATION_ALLOWED_ERR=7&NOT_FOUND_ERR=8&NOT_SUPPORTED_ERR=9&INUSE_ATTRIBUTE_ERR=10&INVALID_STATE_ERR=11&SYNTAX_ERR=12&INVALID_MODIFICATION_ERR=13&NAMESPACE_ERR=14&INVALID_ACCESS_ERR=15&VALIDATION_ERR=16&TYPE_MISMATCH_ERR=17&SECURITY_ERR=18&NETWORK_ERR=19&ABORT_ERR=20&URL_MISMATCH_ERR=21"A_EXCEEDED_ERR=22&TIMEOUT_ERR=23&INVALID_NODE_TYPE_ERR=24&DATA_CLONE_ERR=25") diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 7bd65d90237370..96909391b1ee93 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -8,11 +8,11 @@ "path": "encoding" }, "url": { - "commit": "75b0f336c50105c6fea47ad253d57219dfa744d3", + "commit": "418f7fabebeeb642e79e05b48ffde1a601c7e058", "path": "url" }, "resources": { - "commit": "679a364421ce3704289df21e1ff985c14b360981", + "commit": "e1fddfbf801e7cce9cac5645e992194e4059ef56", "path": "resources" }, "interfaces": {