diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 096c876f7b2956..855fca6d179ff6 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -12,9 +12,9 @@ Last update: - console: https://github.com/web-platform-tests/wpt/tree/3b1f72e99a/console - encoding: https://github.com/web-platform-tests/wpt/tree/1821fb5f77/encoding -- url: https://github.com/web-platform-tests/wpt/tree/54c6d64be0/url -- resources: https://github.com/web-platform-tests/wpt/tree/1d14e821b9/resources -- interfaces: https://github.com/web-platform-tests/wpt/tree/15e47f779c/interfaces +- url: https://github.com/web-platform-tests/wpt/tree/09d8830be1/url +- resources: https://github.com/web-platform-tests/wpt/tree/001e50de41/resources +- interfaces: https://github.com/web-platform-tests/wpt/tree/8719553b2d/interfaces - html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing - html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/264f12bc7b/html/webappapis/timers - hr-time: https://github.com/web-platform-tests/wpt/tree/a5d1774ecf/hr-time diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index e92bb8690dddd3..bf8da3733ec26f 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -436,7 +436,6 @@ interface HTMLIFrameElement : HTMLElement { [SameObject, PutForwards=value] readonly attribute DOMTokenList sandbox; [CEReactions] attribute DOMString allow; [CEReactions] attribute boolean allowFullscreen; - [CEReactions] attribute boolean allowPaymentRequest; [CEReactions] attribute DOMString width; [CEReactions] attribute DOMString height; [CEReactions] attribute DOMString referrerPolicy; @@ -1564,16 +1563,18 @@ dictionary ElementDefinitionOptions { [Exposed=Window] interface ElementInternals { - // Form-associated custom elements + // Shadow root access + readonly attribute ShadowRoot? shadowRoot; + // Form-associated custom elements undefined setFormValue((File or USVString or FormData)? value, - optional (File or USVString or FormData)? state); + optional (File or USVString or FormData)? state); readonly attribute HTMLFormElement? form; undefined setValidity(optional ValidityStateFlags flags = {}, - optional DOMString message, - optional HTMLElement anchor); + optional DOMString message, + optional HTMLElement anchor); readonly attribute boolean willValidate; readonly attribute ValidityState validity; readonly attribute DOMString validationMessage; @@ -1583,6 +1584,9 @@ interface ElementInternals { readonly attribute NodeList labels; }; +// Accessibility semantics +ElementInternals includes ARIAMixin; + dictionary ValidityStateFlags { boolean valueMissing = false; boolean typeMismatch = false; @@ -2353,6 +2357,18 @@ interface WorkerLocation { readonly attribute USVString hash; }; +[Exposed=Worklet, SecureContext] +interface WorkletGlobalScope {}; + +[Exposed=Window, SecureContext] +interface Worklet { + [NewObject] Promise addModule(USVString moduleURL, optional WorkletOptions options = {}); +}; + +dictionary WorkletOptions { + RequestCredentials credentials = "same-origin"; +}; + [Exposed=Window] interface Storage { readonly attribute unsigned long length; diff --git a/test/fixtures/wpt/resources/test-only-api.m.js b/test/fixtures/wpt/resources/test-only-api.m.js new file mode 100644 index 00000000000000..984f635abac002 --- /dev/null +++ b/test/fixtures/wpt/resources/test-only-api.m.js @@ -0,0 +1,5 @@ +/* Whether the browser is Chromium-based with MojoJS enabled */ +export const isChromiumBased = 'MojoInterfaceInterceptor' in self; + +/* Whether the browser is WebKit-based with internal test-only API enabled */ +export const isWebKitBased = !isChromiumBased && 'internals' in self; diff --git a/test/fixtures/wpt/resources/test-only-api.m.js.headers b/test/fixtures/wpt/resources/test-only-api.m.js.headers new file mode 100644 index 00000000000000..5e8f640c6659d1 --- /dev/null +++ b/test/fixtures/wpt/resources/test-only-api.m.js.headers @@ -0,0 +1,2 @@ +Content-Type: text/javascript; charset=utf-8 +Cache-Control: max-age=3600 diff --git a/test/fixtures/wpt/resources/testdriver-actions.js b/test/fixtures/wpt/resources/testdriver-actions.js index 870a2e8e266780..f3e6388e8acd19 100644 --- a/test/fixtures/wpt/resources/testdriver-actions.js +++ b/test/fixtures/wpt/resources/testdriver-actions.js @@ -9,6 +9,7 @@ function Actions(defaultTickDuration=16) { this.sourceTypes = new Map([["key", KeySource], ["pointer", PointerSource], + ["wheel", WheelSource], ["none", GeneralSource]]); this.sources = new Map(); this.sourceOrder = []; @@ -22,6 +23,7 @@ this.createSource("none"); this.tickIdx = 0; this.defaultTickDuration = defaultTickDuration; + this.context = null; } Actions.prototype = { @@ -65,7 +67,17 @@ } catch(e) { return Promise.reject(e); } - return test_driver.action_sequence(actions); + return test_driver.action_sequence(actions, this.context); + }, + + /** + * Set the context for the actions + * + * @param {WindowProxy} context - Context in which to run the action sequence + */ + setContext: function(context) { + this.context = context; + return this; }, /** @@ -73,7 +85,7 @@ * If no name is passed, a new source with the given type is * created. * - * @param {String} type - Source type ('none', 'key', or 'pointer') + * @param {String} type - Source type ('none', 'key', 'pointer', or 'wheel') * @param {String?} name - Name of the source * @returns {Source} Source object for that source. */ @@ -154,6 +166,32 @@ return this; }, + /** + * Add a new wheel input source with the given name + * + * @param {String} type - Name of the wheel source + * @param {Bool} set - Set source as the default wheel source + * @returns {Actions} + */ + addWheel: function(name, set=true) { + this.createSource("wheel", name); + if (set) { + this.setWheel(name); + } + return this; + }, + + /** + * Set the current default wheel source + * + * @param {String} name - Name of the wheel source + * @returns {Actions} + */ + setWheel: function(name) { + this.setSource("wheel", name); + return this; + }, + createSource: function(type, name, parameters={}) { if (!this.sources.has(type)) { throw new Error(`${type} is not a valid action type`); @@ -196,8 +234,9 @@ * * @param {Number?} duration - Minimum length of the tick in ms. * @param {String} sourceType - source type - * @param {String?} sourceName - Named key or pointer source to use or null for the default - * key or pointer source + * @param {String?} sourceName - Named key, pointer or wheel source to use + * or null for the default key, pointer or + * wheel source * @returns {Actions} */ pause: function(duration=0, sourceType="none", {sourceName=null}={}) { @@ -280,6 +319,27 @@ source.pointerMove(this, x, y, duration, origin); return this; }, + + /** + * Create a scroll event for the current default wheel source + * + * @param {Number} x - mouse cursor x coordinate + * @param {Number} y - mouse cursor y coordinate + * @param {Number} deltaX - scroll delta value along the x-axis in pixels + * @param {Number} deltaY - scroll delta value along the y-axis in pixels + * @param {String|Element} origin - Origin of the coordinate system. + * Either "viewport" or an Element + * @param {Number?} duration - Time in ms for the scroll + * @param {String?} sourceName - Named wheel source to use or null for the + * default wheel source + * @returns {Actions} + */ + scroll: function(x, y, deltaX, deltaY, + {origin="viewport", duration, sourceName=null}={}) { + let source = this.getSource("wheel", sourceName); + source.scroll(this, x, y, deltaX, deltaY, duration, origin); + return this; + }, }; function GeneralSource() { @@ -417,5 +477,46 @@ }, }; + function WheelSource() { + this.actions = new Map(); + } + + WheelSource.prototype = { + serialize: function(tickCount) { + if (!this.actions.size) { + return undefined; + } + let actions = []; + let data = {"type": "wheel", "actions": actions}; + for (let i=0; i button.addEventListener("click", resolve)); - test_driver.click(button).catch(reject); - }).then(function() { + return test_driver.click(button) + .then(wait_click) + .then(function() { button.remove(); if (typeof action === "function") { return action(); } + return null; }); }, @@ -95,14 +127,6 @@ * the cases the WebDriver command errors */ click: function(element) { - if (window.top !== window) { - return Promise.reject(new Error("can only click in top-level window")); - } - - if (!window.document.contains(element)) { - return Promise.reject(new Error("element in different document or shadow tree")); - } - if (!inView(element)) { element.scrollIntoView({behavior: "instant", block: "end", @@ -135,14 +159,6 @@ * the cases the WebDriver command errors */ send_keys: function(element, keys) { - if (window.top !== window) { - return Promise.reject(new Error("can only send keys in top-level window")); - } - - if (!window.document.contains(element)) { - return Promise.reject(new Error("element in different document or shadow tree")); - } - if (!inView(element)) { element.scrollIntoView({behavior: "instant", block: "end", @@ -166,34 +182,42 @@ * https://github.com/WICG/page-lifecycle/blob/master/README.md|Lifecycle API * for Web Pages} * + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * * @returns {Promise} fulfilled after the freeze request is sent, or rejected * in case the WebDriver command errors */ - freeze: function() { + freeze: function(context=null) { return window.test_driver_internal.freeze(); }, /** * Send a sequence of actions * - * This function sends a sequence of actions to the top level window + * This function sends a sequence of actions * to perform. It is modeled after the behaviour of {@link * https://w3c.github.io/webdriver/#actions|WebDriver Actions Command} * * @param {Array} actions - an array of actions. The format is the same as the actions - property of the WebDriver command {@link - https://w3c.github.io/webdriver/#perform-actions|Perform - Actions} command. Each element is an object representing an - input source and each input source itself has an actions - property detailing the behaviour of that source at each timestep - (or tick). Authors are not expected to construct the actions - sequence by hand, but to use the builder api provided in - testdriver-actions.js + * property of the WebDriver command {@link + * https://w3c.github.io/webdriver/#perform-actions|Perform + * Actions} command. Each element is an object representing an + * input source and each input source itself has an actions + * property detailing the behaviour of that source at each timestep + * (or tick). Authors are not expected to construct the actions + * sequence by hand, but to use the builder api provided in + * testdriver-actions.js + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * * @returns {Promise} fufiled after the actions are performed, or rejected in * the cases the WebDriver command errors */ - action_sequence: function(actions) { - return window.test_driver_internal.action_sequence(actions); + action_sequence: function(actions, context=null) { + return window.test_driver_internal.action_sequence(actions, context); }, /** @@ -203,11 +227,15 @@ * by ReportingObserver) for testing purposes, as described in * {@link https://w3c.github.io/reporting/#generate-test-report-command} * + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * * @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); + generate_test_report: function(message, context=null) { + return window.test_driver_internal.generate_test_report(message, context); }, /** @@ -221,6 +249,9 @@ * object * @param {String} state - the state of the permission * @param {boolean} one_realm - Optional. Whether the permission applies to only one realm + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. * * The above params are used to create a [PermissionSetParameters]{@link * https://w3c.github.io/permissions/#dictdef-permissionsetparameters} object @@ -228,13 +259,13 @@ * @returns {Promise} fulfilled after the permission is set, or rejected if setting the * permission fails */ - set_permission: function(descriptor, state, one_realm) { + set_permission: function(descriptor, state, one_realm, context=null) { let permission_params = { descriptor, state, oneRealm: one_realm, }; - return window.test_driver_internal.set_permission(permission_params); + return window.test_driver_internal.set_permission(permission_params, context); }, /** @@ -247,12 +278,16 @@ * @param {Object} config - an [Authenticator Configuration]{@link * https://w3c.github.io/webauthn/#authenticator-configuration} * object + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * * @returns {Promise} fulfilled after the authenticator is added, or * rejected in the cases the WebDriver command * errors. Returns the ID of the authenticator */ - add_virtual_authenticator: function(config) { - return window.test_driver_internal.add_virtual_authenticator(config); + add_virtual_authenticator: function(config, context=null) { + return window.test_driver_internal.add_virtual_authenticator(config, context); }, /** @@ -264,13 +299,16 @@ * * @param {String} authenticator_id - the ID of the authenticator to be * removed. + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. * * @returns {Promise} fulfilled after the authenticator is removed, or * rejected in the cases the WebDriver command * errors */ - remove_virtual_authenticator: function(authenticator_id) { - return window.test_driver_internal.remove_virtual_authenticator(authenticator_id); + remove_virtual_authenticator: function(authenticator_id, context=null) { + return window.test_driver_internal.remove_virtual_authenticator(authenticator_id, context); }, /** @@ -282,13 +320,16 @@ * @param {Object} credential - A [Credential Parameters]{@link * https://w3c.github.io/webauthn/#credential-parameters} * object + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. * * @returns {Promise} fulfilled after the credential is added, or * rejected in the cases the WebDriver command * errors */ - add_credential: function(authenticator_id, credential) { - return window.test_driver_internal.add_credential(authenticator_id, credential); + add_credential: function(authenticator_id, credential, context=null) { + return window.test_driver_internal.add_credential(authenticator_id, credential, context); }, /** @@ -300,6 +341,9 @@ * https://w3c.github.io/webauthn/#sctn-automation-get-credentials * * @param {String} authenticator_id - the ID of the authenticator + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. * * @returns {Promise} fulfilled after the credentials are returned, or * rejected in the cases the WebDriver command @@ -307,8 +351,8 @@ * Parameters]{@link * https://w3c.github.io/webauthn/#credential-parameters} */ - get_credentials: function(authenticator_id) { - return window.test_driver_internal.get_credentials(authenticator_id); + get_credentials: function(authenticator_id, context=null) { + return window.test_driver_internal.get_credentials(authenticator_id, context=null); }, /** @@ -318,13 +362,16 @@ * * @param {String} authenticator_id - the ID of the authenticator * @param {String} credential_id - the ID of the credential + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. * * @returns {Promise} fulfilled after the credential is removed, or * rejected in the cases the WebDriver command * errors. */ - remove_credential: function(authenticator_id, credential_id) { - return window.test_driver_internal.remove_credential(authenticator_id, credential_id); + remove_credential: function(authenticator_id, credential_id, context=null) { + return window.test_driver_internal.remove_credential(authenticator_id, credential_id, context); }, /** @@ -333,13 +380,16 @@ * https://w3c.github.io/webauthn/#sctn-automation-remove-all-credentials * * @param {String} authenticator_id - the ID of the authenticator + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. * * @returns {Promise} fulfilled after the credentials are removed, or * rejected in the cases the WebDriver command * errors. */ - remove_all_credentials: function(authenticator_id) { - return window.test_driver_internal.remove_all_credentials(authenticator_id); + remove_all_credentials: function(authenticator_id, context=null) { + return window.test_driver_internal.remove_all_credentials(authenticator_id, context); }, /** @@ -351,9 +401,12 @@ * * @param {String} authenticator_id - the ID of the authenticator * @param {boolean} uv - the User Verified flag + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. */ - set_user_verified: function(authenticator_id, uv) { - return window.test_driver_internal.set_user_verified(authenticator_id, uv); + set_user_verified: function(authenticator_id, uv, context=null) { + return window.test_driver_internal.set_user_verified(authenticator_id, uv, context); }, /** @@ -370,16 +423,19 @@ * May be "*" to indicate all origins. * @param {String} state - The storage access setting. * Must be either "allowed" or "blocked". + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. * * @returns {Promise} Fulfilled after the storage access rule has been * set, or rejected if setting the rule fails. */ - set_storage_access: function(origin, embedding_origin, state) { + set_storage_access: function(origin, embedding_origin, state, context=null) { if (state !== "allowed" && state !== "blocked") { throw new Error("storage access status must be 'allowed' or 'blocked'"); } const blocked = state === "blocked"; - return window.test_driver_internal.set_storage_access(origin, embedding_origin, blocked); + return window.test_driver_internal.set_storage_access(origin, embedding_origin, blocked, context); }, }; @@ -392,13 +448,6 @@ */ 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 - */ click: function(element, coords) { if (this.in_automation) { return Promise.reject(new Error('Not implemented')); @@ -409,14 +458,6 @@ }); }, - /** - * Waits for an element to receive a series of key presses - * - * @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) { if (this.in_automation) { return Promise.reject(new Error('Not implemented')); @@ -449,158 +490,52 @@ }); }, - /** - * Freeze the current page - * - * @returns {Promise} fulfilled after freeze request is sent, otherwise - * it gets rejected - */ - freeze: function() { + freeze: function(context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Send a sequence of pointer actions - * - * @returns {Promise} fufilled after actions are sent, rejected if any actions - * fail - */ - action_sequence: function(actions) { + action_sequence: function(actions, context=null) { 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) { + generate_test_report: function(message, context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Sets the state of a permission - * - * This function simulates a user setting a permission into a particular state as described - * in {@link https://w3c.github.io/permissions/#set-permission-command} - * - * @param {Object} permission_params - a [PermissionSetParameters]{@lint - * https://w3c.github.io/permissions/#dictdef-permissionsetparameters} - * object - * @returns {Promise} fulfilled after the permission is set, or rejected if setting the - * permission fails - */ - set_permission: function(permission_params) { + set_permission: function(permission_params, context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Creates a virtual authenticator - * - * @param {Object} config - the authenticator configuration - * @returns {Promise} fulfilled after the authenticator is added, or - * rejected in the cases the WebDriver command - * errors. - */ - add_virtual_authenticator: function(config) { + add_virtual_authenticator: function(config, context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Removes a virtual authenticator - * - * @param {String} authenticator_id - the ID of the authenticator to be - * removed. - * - * @returns {Promise} fulfilled after the authenticator is removed, or - * rejected in the cases the WebDriver command - * errors - */ - remove_virtual_authenticator: function(authenticator_id) { + remove_virtual_authenticator: function(authenticator_id, context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Adds a credential to a virtual authenticator - * - * @param {String} authenticator_id - the ID of the authenticator - * @param {Object} credential - A [Credential Parameters]{@link - * https://w3c.github.io/webauthn/#credential-parameters} - * object - * - * @returns {Promise} fulfilled after the credential is added, or - * rejected in the cases the WebDriver command - * errors - * - */ - add_credential: function(authenticator_id, credential) { + add_credential: function(authenticator_id, credential, context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Gets all the credentials stored in an authenticator - * - * @param {String} authenticator_id - the ID of the authenticator - * - * @returns {Promise} fulfilled after the credentials are returned, or - * rejected in the cases the WebDriver command - * errors. Returns an array of [Credential - * Parameters]{@link - * https://w3c.github.io/webauthn/#credential-parameters} - * - */ - get_credentials: function(authenticator_id) { + get_credentials: function(authenticator_id, context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Remove a credential stored in an authenticator - * - * @param {String} authenticator_id - the ID of the authenticator - * @param {String} credential_id - the ID of the credential - * - * @returns {Promise} fulfilled after the credential is removed, or - * rejected in the cases the WebDriver command - * errors. - * - */ - remove_credential: function(authenticator_id, credential_id) { + remove_credential: function(authenticator_id, credential_id, context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Removes all the credentials stored in a virtual authenticator - * - * @param {String} authenticator_id - the ID of the authenticator - * - * @returns {Promise} fulfilled after the credentials are removed, or - * rejected in the cases the WebDriver command - * errors. - * - */ - remove_all_credentials: function(authenticator_id) { + remove_all_credentials: function(authenticator_id, context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Sets the User Verified flag on an authenticator - * - * @param {String} authenticator_id - the ID of the authenticator - * @param {boolean} uv - the User Verified flag - * - */ - set_user_verified: function(authenticator_id, uv) { + set_user_verified: function(authenticator_id, uv, context=null) { return Promise.reject(new Error("unimplemented")); }, - /** - * Sets the storage access policy for a third-party origin when loaded - * in the current first party context - */ - set_storage_access: function(origin, embedding_origin, blocked) { + set_storage_access: function(origin, embedding_origin, blocked, context=null) { return Promise.reject(new Error("unimplemented")); }, }; diff --git a/test/fixtures/wpt/resources/testharness.js b/test/fixtures/wpt/resources/testharness.js index 61eadfeb6541a9..d50e094117df56 100644 --- a/test/fixtures/wpt/resources/testharness.js +++ b/test/fixtures/wpt/resources/testharness.js @@ -520,6 +520,43 @@ policies and contribution forms [3]. Object.prototype.toString.call(worker) == '[object ServiceWorker]'; } + var seen_func_name = Object.create(null); + + function get_test_name(func, name) + { + if (name) { + return name; + } + + if (func) { + var func_code = func.toString(); + + // Try and match with brackets, but fallback to matching without + var arrow = func_code.match(/^\(\)\s*=>\s*(?:{(.*)}\s*|(.*))$/); + + // Check for JS line separators + if (arrow !== null && !/[\u000A\u000D\u2028\u2029]/.test(func_code)) { + var trimmed = (arrow[1] !== undefined ? arrow[1] : arrow[2]).trim(); + // drop trailing ; if there's no earlier ones + trimmed = trimmed.replace(/^([^;]*)(;\s*)+$/, "$1"); + + if (trimmed) { + let name = trimmed; + if (seen_func_name[trimmed]) { + // This subtest name already exists, so add a suffix. + name += " " + seen_func_name[trimmed]; + } else { + seen_func_name[trimmed] = 0; + } + seen_func_name[trimmed] += 1; + return name; + } + } + } + + return test_environment.next_default_test_name(); + } + /* * API functions */ @@ -530,17 +567,18 @@ policies and contribution forms [3]. tests.status.message = '`test` invoked after `promise_setup`'; tests.complete(); } - var test_name = name ? name : test_environment.next_default_test_name(); + var test_name = get_test_name(func, name); var test_obj = new Test(test_name, properties); var value = test_obj.step(func, test_obj, test_obj); if (value !== undefined) { - var msg = "Test named \"" + test_name + - "\" inappropriately returned a value"; + var msg = 'Test named "' + test_name + + '" passed a function to `test` that returned a value.'; try { - if (value && value.hasOwnProperty("then")) { - msg += ", consider using `promise_test` instead"; + if (value && typeof value.then === 'function') { + msg += ' Consider using `promise_test` instead when ' + + 'using Promises or async/await.'; } } catch (err) {} @@ -565,10 +603,33 @@ policies and contribution forms [3]. name = func; func = null; } - var test_name = name ? name : test_environment.next_default_test_name(); + var test_name = get_test_name(func, name); var test_obj = new Test(test_name, properties); if (func) { - test_obj.step(func, test_obj, test_obj); + var value = test_obj.step(func, test_obj, test_obj); + + // Test authors sometimes return values to async_test, expecting us + // to handle the value somehow. Make doing so a harness error to be + // clear this is invalid, and point authors to promise_test if it + // may be appropriate. + // + // Note that we only perform this check on the initial function + // passed to async_test, not on any later steps - we haven't seen a + // consistent problem with those (and it's harder to check). + if (value !== undefined) { + var msg = 'Test named "' + test_name + + '" passed a function to `async_test` that returned a value.'; + + try { + if (value && typeof value.then === 'function') { + msg += ' Consider using `promise_test` instead when ' + + 'using Promises or async/await.'; + } + } catch (err) {} + + tests.set_status(tests.status.ERROR, msg); + tests.complete(); + } } return test_obj; } @@ -579,7 +640,7 @@ policies and contribution forms [3]. name = func; func = null; } - var test_name = name ? name : test_environment.next_default_test_name(); + var test_name = get_test_name(func, name); var test = new Test(test_name, properties); test._is_promise_test = true; @@ -1323,10 +1384,16 @@ policies and contribution forms [3]. "expected a number but got a ${type_actual}", {type_actual:typeof actual}); - assert(Math.abs(actual - expected) <= epsilon, - "assert_approx_equals", description, - "expected ${expected} +/- ${epsilon} but got ${actual}", - {expected:expected, actual:actual, epsilon:epsilon}); + // The epsilon math below does not place nice with NaN and Infinity + // But in this case Infinity = Infinity and NaN = NaN + if (isFinite(actual) || isFinite(expected)) { + assert(Math.abs(actual - expected) <= epsilon, + "assert_approx_equals", description, + "expected ${expected} +/- ${epsilon} but got ${actual}", + {expected:expected, actual:actual, epsilon:epsilon}); + } else { + assert_equals(actual, expected); + } } expose(assert_approx_equals, "assert_approx_equals"); @@ -3773,7 +3840,7 @@ policies and contribution forms [3]. function get_title() { if ('document' in global_scope) { - //Don't use document.title to work around an Opera bug in XHTML documents + //Don't use document.title to work around an Opera/Presto bug in XHTML documents var title = document.getElementsByTagName("title")[0]; if (title && title.firstChild && title.firstChild.data) { return title.firstChild.data; diff --git a/test/fixtures/wpt/url/README.md b/test/fixtures/wpt/url/README.md index 823a8eec022282..50a71bb482df9e 100644 --- a/test/fixtures/wpt/url/README.md +++ b/test/fixtures/wpt/url/README.md @@ -44,6 +44,10 @@ expected to fail. Tests in `/encoding` and `/html/infrastructure/urls/resolving-urls/query-encoding/` cover the encoding argument to the URL parser. +There's also limited coverage in `resources/percent-encoding.json` for percent-encode after encoding +with _percentEncodeSet_ set to special-query percent-encode set and _spaceAsPlus_ set to false. +(Improvements to expand coverage here are welcome.) + ## Specification The tests in this directory assert conformance with [the URL Standard][URL]. diff --git a/test/fixtures/wpt/url/percent-encoding.window.js b/test/fixtures/wpt/url/percent-encoding.window.js new file mode 100644 index 00000000000000..dcb5c1e55b21b7 --- /dev/null +++ b/test/fixtures/wpt/url/percent-encoding.window.js @@ -0,0 +1,33 @@ +promise_test(() => fetch("resources/percent-encoding.json").then(res => res.json()).then(runTests), "Loading data…"); + +function runTests(testUnits) { + for (const testUnit of testUnits) { + // Ignore comments + if (typeof testUnit === "string") { + continue; + } + for (const encoding of Object.keys(testUnit.output)) { + async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func_done(() => { + const output = frame.contentDocument.querySelector("a"); + // Test that the fragment is always UTF-8 encoded + assert_equals(output.hash, `#${testUnit.output["utf-8"]}`, "fragment"); + assert_equals(output.search, `?${testUnit.output[encoding]}`, "query"); + }); + frame.src = `resources/percent-encoding.py?encoding=${encoding}&value=${toBase64(testUnit.input)}`; + }, `Input ${testUnit.input} with encoding ${encoding}`); + } + } +} + +// Use base64 to avoid relying on the URL parser to get UTF-8 percent-encoding correctly. This does +// not use btoa directly as that only works with code points in the range U+0000 to U+00FF, +// inclusive. +function toBase64(input) { + const bytes = new TextEncoder().encode(input); + const byteString = Array.from(bytes, byte => String.fromCharCode(byte)).join(""); + const encoded = self.btoa(byteString); + return encoded; +} diff --git a/test/fixtures/wpt/url/resources/percent-encoding.json b/test/fixtures/wpt/url/resources/percent-encoding.json new file mode 100644 index 00000000000000..eccd1db62fe601 --- /dev/null +++ b/test/fixtures/wpt/url/resources/percent-encoding.json @@ -0,0 +1,48 @@ +[ + "Tests for percent-encoding.", + { + "input": "\u2020", + "output": { + "big5": "%26%238224%3B", + "euc-kr": "%A2%D3", + "utf-8": "%E2%80%A0", + "windows-1252": "%86" + } + }, + "This uses a trailing A to prevent the URL parser from trimming the C0 control.", + { + "input": "\u000EA", + "output": { + "big5": "%0EA", + "iso-2022-jp": "%26%2365533%3BA", + "utf-8": "%0EA" + } + }, + { + "input": "\u203E\u005C", + "output": { + "iso-2022-jp": "%1B(J~%1B(B\\", + "utf-8": "%E2%80%BE\\" + } + }, + { + "input": "\uE5E5", + "output": { + "gb18030": "%26%2358853%3B", + "utf-8": "%EE%97%A5" + } + }, + { + "input": "\u2212", + "output": { + "shift_jis": "%81|", + "utf-8": "%E2%88%92" + } + }, + { + "input": "á|", + "output": { + "utf-8": "%C3%A1|" + } + } +] diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index e18a5e0213fb3e..f1e9b8304d5fda 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -8,15 +8,15 @@ "path": "encoding" }, "url": { - "commit": "54c6d64be071c60baaad8c4da0365b962ffbe77c", + "commit": "09d8830be15da7e3a44f32a934609c25357d6ef3", "path": "url" }, "resources": { - "commit": "1d14e821b9586f250e6a31d550504e3d16a05ae7", + "commit": "001e50de41dc35820774b27e31f77a165f4c0b9b", "path": "resources" }, "interfaces": { - "commit": "15e47f779cf61555669b0f67e2c49b9c830b9019", + "commit": "8719553b2dd8f0f39d38253ccac2ee9ab4d6c87b", "path": "interfaces" }, "html/webappapis/microtask-queuing": { @@ -39,4 +39,4 @@ "commit": "7caa3de7471cf19b78ee9efa313c7341a462b5e3", "path": "dom/abort" } -} +} \ No newline at end of file diff --git a/test/wpt/status/url.json b/test/wpt/status/url.json index 9038f6df80703e..89601af0ca5814 100644 --- a/test/wpt/status/url.json +++ b/test/wpt/status/url.json @@ -3,6 +3,10 @@ "requires": ["small-icu"], "skip": "TODO: port from .window.js" }, + "percent-encoding.window.js": { + "requires": ["small-icu"], + "skip": "TODO: port from .window.js" + }, "historical.any.js": { "requires": ["small-icu"] },