From f9a5a759903d40057755363091851357f3e30840 Mon Sep 17 00:00:00 2001 From: Uzlopak <5059100+Uzlopak@users.noreply.github.com> Date: Mon, 25 Nov 2024 00:19:00 +0000 Subject: [PATCH] chore: update WPT --- test/fixtures/wpt/eventsource/META.yml | 1 + .../quota.tentative.https.window.js | 52 ++++--- .../interfaces/attribution-reporting-api.idl | 1 + test/fixtures/wpt/interfaces/fedcm.idl | 16 +- test/fixtures/wpt/interfaces/fenced-frame.idl | 1 + test/fixtures/wpt/interfaces/html.idl | 12 +- test/fixtures/wpt/interfaces/paint-timing.idl | 7 + .../wpt/interfaces/shared-storage.idl | 6 +- .../wpt/interfaces/web-animations-2.idl | 2 +- test/fixtures/wpt/interfaces/webauthn.idl | 1 - test/fixtures/wpt/interfaces/webgpu.idl | 4 +- test/fixtures/wpt/interfaces/webrtc-stats.idl | 3 + .../wpt/resources/idlharness-shadowrealm.js | 47 +++--- test/fixtures/wpt/resources/testdriver.js | 140 +++++++++++++++--- ...rness-shadowrealm-audioworkletprocessor.js | 52 +++++++ .../testharness-shadowrealm-inner.js | 27 ++++ .../testharness-shadowrealm-outer.js | 127 ++++++++++++++++ 17 files changed, 417 insertions(+), 82 deletions(-) create mode 100644 test/fixtures/wpt/resources/testharness-shadowrealm-audioworkletprocessor.js create mode 100644 test/fixtures/wpt/resources/testharness-shadowrealm-inner.js create mode 100644 test/fixtures/wpt/resources/testharness-shadowrealm-outer.js diff --git a/test/fixtures/wpt/eventsource/META.yml b/test/fixtures/wpt/eventsource/META.yml index 437da600931..c0756cf85af 100644 --- a/test/fixtures/wpt/eventsource/META.yml +++ b/test/fixtures/wpt/eventsource/META.yml @@ -3,3 +3,4 @@ suggested_reviewers: - odinho - Yaffle - annevk + - rexxars diff --git a/test/fixtures/wpt/fetch/fetch-later/quota.tentative.https.window.js b/test/fixtures/wpt/fetch/fetch-later/quota.tentative.https.window.js index 9d0ae4287df..400be405095 100644 --- a/test/fixtures/wpt/fetch/fetch-later/quota.tentative.https.window.js +++ b/test/fixtures/wpt/fetch/fetch-later/quota.tentative.https.window.js @@ -4,8 +4,9 @@ 'use strict'; -const kQuotaPerOrigin = 64 * 1024; // 64 kilobytes per spec. +const QUOTA_PER_ORIGIN = 64 * 1024; // 64 kilobytes per spec. const {ORIGIN, HTTPS_NOTSAMESITE_ORIGIN} = get_host_info(); +const TEST_ENDPOINT = '/fetch-later'; // Runs a test case that cover a single fetchLater() call with `body` in its // request payload. The call is not expected to throw any errors. @@ -13,8 +14,7 @@ function fetchLaterPostTest(body, description) { test(() => { const controller = new AbortController(); const result = fetchLater( - '/fetch-later', - {method: 'POST', signal: controller.signal, body: body}); + TEST_ENDPOINT, {method: 'POST', signal: controller.signal, body: body}); assert_false(result.activated); // Release quota taken by the pending request for subsequent tests. controller.abort(); @@ -30,19 +30,25 @@ for (const [dataType, skipCharset] of Object.entries( } // Test various size of payloads for the same origin. -for (const dataType in BeaconDataType) { - if (dataType !== BeaconDataType.FormData && - dataType !== BeaconDataType.URLSearchParams) { - // Skips FormData & URLSearchParams, as browser adds extra bytes to them - // in addition to the user-provided content. It is difficult to test a - // request right at the quota limit. - fetchLaterPostTest( - // Generates data that is exactly 64 kilobytes. - makeBeaconData(generatePayload(kQuotaPerOrigin), dataType), - `A single fetchLater() call takes up the per-origin quota for its ` + - `body of ${dataType}.`); - } -} + +// Test max possible size of payload. +// Length of absolute URL to the endpoint. +const POST_TEST_REQUEST_URL_SIZE = (ORIGIN + TEST_ENDPOINT).length; +// Total size of the request header. +const POST_TEST_REQUEST_HEADER_SIZE = 36; +// Runs this test only for String type beacon, as browser adds extra bytes to +// body for some other types (FormData & URLSearchParams), and the request +// header sizes varies for every other types. It is difficult to test a request +// right at the quota limit. +fetchLaterPostTest( + // Generates data that is exactly 64 kilobytes. + makeBeaconData( + generatePayload( + QUOTA_PER_ORIGIN - POST_TEST_REQUEST_URL_SIZE - + POST_TEST_REQUEST_HEADER_SIZE), + BeaconDataType.String), + `A single fetchLater() call takes up the per-origin quota for its ` + + `body of String.`); // Test empty payload. for (const dataType in BeaconDataType) { @@ -64,8 +70,8 @@ for (const dataType in BeaconDataType) { () => fetchLater('/fetch-later', { method: 'POST', // Generates data that exceeds 64 kilobytes. - body: - makeBeaconData(generatePayload(kQuotaPerOrigin + 1), dataType) + body: makeBeaconData( + generatePayload(QUOTA_PER_ORIGIN + 1), dataType) })); }, `A single fetchLater() call is not allowed to exceed per-origin quota ` + @@ -81,7 +87,7 @@ for (const dataType in BeaconDataType) { fetchLater('/fetch-later', { method: 'POST', signal: controller.signal, - body: makeBeaconData(generatePayload(kQuotaPerOrigin / 2), dataType) + body: makeBeaconData(generatePayload(QUOTA_PER_ORIGIN / 2), dataType) }); // Makes the 2nd call that sends half+1 of allowed quota. @@ -90,7 +96,7 @@ for (const dataType in BeaconDataType) { method: 'POST', signal: controller.signal, body: makeBeaconData( - generatePayload(kQuotaPerOrigin / 2 + 1), dataType) + generatePayload(QUOTA_PER_ORIGIN / 2 + 1), dataType) }); }); // Release quota taken by the pending requests for subsequent tests. @@ -109,7 +115,7 @@ for (const dataType in BeaconDataType) { fetchLater('/fetch-later', { method: 'POST', signal: controller.signal, - body: makeBeaconData(generatePayload(kQuotaPerOrigin / 2), dataType) + body: makeBeaconData(generatePayload(QUOTA_PER_ORIGIN / 2), dataType) }); // Makes the 2nd call that sends half+1 of allowed quota, but to a @@ -117,8 +123,8 @@ for (const dataType in BeaconDataType) { fetchLater(`${HTTPS_NOTSAMESITE_ORIGIN}/fetch-later`, { method: 'POST', signal: controller.signal, - body: - makeBeaconData(generatePayload(kQuotaPerOrigin / 2 + 1), dataType) + body: makeBeaconData( + generatePayload(QUOTA_PER_ORIGIN / 2 + 1), dataType) }); // Release quota taken by the pending requests for subsequent tests. controller.abort(); diff --git a/test/fixtures/wpt/interfaces/attribution-reporting-api.idl b/test/fixtures/wpt/interfaces/attribution-reporting-api.idl index ed4497b56ff..3fe24bd34a0 100644 --- a/test/fixtures/wpt/interfaces/attribution-reporting-api.idl +++ b/test/fixtures/wpt/interfaces/attribution-reporting-api.idl @@ -8,6 +8,7 @@ interface mixin HTMLAttributionSrcElementUtils { }; HTMLAnchorElement includes HTMLAttributionSrcElementUtils; +HTMLAreaElement includes HTMLAttributionSrcElementUtils; HTMLImageElement includes HTMLAttributionSrcElementUtils; HTMLScriptElement includes HTMLAttributionSrcElementUtils; diff --git a/test/fixtures/wpt/interfaces/fedcm.idl b/test/fixtures/wpt/interfaces/fedcm.idl index 443d3311a24..c308ee273b9 100644 --- a/test/fixtures/wpt/interfaces/fedcm.idl +++ b/test/fixtures/wpt/interfaces/fedcm.idl @@ -29,9 +29,15 @@ enum IdentityCredentialRequestOptionsContext { "continue" }; +enum IdentityCredentialRequestOptionsMode { + "active", + "passive" +}; + dictionary IdentityCredentialRequestOptions { required sequence providers; IdentityCredentialRequestOptionsContext context = "signin"; + IdentityCredentialRequestOptionsMode mode = "passive"; }; dictionary IdentityProviderConfig { @@ -85,8 +91,9 @@ dictionary IdentityProviderAccountList { sequence accounts; }; -dictionary IdentityProviderToken { - required USVString token; +dictionary IdentityAssertionResponse { + USVString token; + USVString continue_on; }; dictionary IdentityProviderClientMetadata { @@ -101,7 +108,12 @@ dictionary IdentityUserInfo { USVString picture; }; +dictionary IdentityResolveOptions { + USVString accountId; +}; + [Exposed=Window, SecureContext] interface IdentityProvider { static undefined close(); + static undefined resolve(DOMString token, optional IdentityResolveOptions options = {}); static Promise> getUserInfo(IdentityProviderConfig config); }; diff --git a/test/fixtures/wpt/interfaces/fenced-frame.idl b/test/fixtures/wpt/interfaces/fenced-frame.idl index cdc35d04ca4..b21ba8f99d2 100644 --- a/test/fixtures/wpt/interfaces/fenced-frame.idl +++ b/test/fixtures/wpt/interfaces/fenced-frame.idl @@ -74,6 +74,7 @@ interface Fence { undefined reportEvent(optional ReportEventType event = {}); undefined setReportEventDataForAutomaticBeacons(optional FenceEvent event = {}); sequence getNestedConfigs(); + Promise disableUntrustedNetwork(); undefined notifyEvent(Event event); }; diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index c295941fcb2..aefa95dbd82 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -133,12 +133,20 @@ interface HTMLElement : Element { ElementInternals attachInternals(); // The popover API - undefined showPopover(); + undefined showPopover(optional ShowPopoverOptions options = {}); undefined hidePopover(); - boolean togglePopover(optional boolean force); + boolean togglePopover(optional (TogglePopoverOptions or boolean) options = {}); [CEReactions] attribute DOMString? popover; }; +dictionary ShowPopoverOptions { + HTMLElement source; +}; + +dictionary TogglePopoverOptions : ShowPopoverOptions { + boolean force; +}; + HTMLElement includes GlobalEventHandlers; HTMLElement includes ElementContentEditable; HTMLElement includes HTMLOrSVGElement; diff --git a/test/fixtures/wpt/interfaces/paint-timing.idl b/test/fixtures/wpt/interfaces/paint-timing.idl index 396f461e94c..fbf91858571 100644 --- a/test/fixtures/wpt/interfaces/paint-timing.idl +++ b/test/fixtures/wpt/interfaces/paint-timing.idl @@ -3,5 +3,12 @@ // (https://github.com/w3c/webref) // Source: Paint Timing (https://w3c.github.io/paint-timing/) +[Exposed=Window] +interface mixin PaintTimingMixin { + readonly attribute DOMHighResTimeStamp paintTime; + readonly attribute DOMHighResTimeStamp? presentationTime; +}; + [Exposed=Window] interface PerformancePaintTiming : PerformanceEntry {}; +PerformancePaintTiming includes PaintTimingMixin; diff --git a/test/fixtures/wpt/interfaces/shared-storage.idl b/test/fixtures/wpt/interfaces/shared-storage.idl index 85906bedbcf..741b86a31c8 100644 --- a/test/fixtures/wpt/interfaces/shared-storage.idl +++ b/test/fixtures/wpt/interfaces/shared-storage.idl @@ -5,8 +5,6 @@ typedef (USVString or FencedFrameConfig) SharedStorageResponse; -enum SharedStorageDataOrigin { "context-origin", "script-origin" }; - [Exposed=(Window)] interface SharedStorageWorklet : Worklet { Promise selectURL(DOMString name, @@ -25,6 +23,8 @@ interface SharedStorageWorkletGlobalScope : WorkletGlobalScope { readonly attribute SharedStorage sharedStorage; readonly attribute PrivateAggregation privateAggregation; + + Promise> interestGroups(); }; dictionary SharedStorageUrlWithMetadata { @@ -93,7 +93,7 @@ dictionary SharedStorageRunOperationMethodOptions { }; dictionary SharedStorageWorkletOptions : WorkletOptions { - SharedStorageDataOrigin dataOrigin = "context-origin"; + USVString dataOrigin = "context-origin"; }; interface mixin HTMLSharedStorageWritableElementUtils { diff --git a/test/fixtures/wpt/interfaces/web-animations-2.idl b/test/fixtures/wpt/interfaces/web-animations-2.idl index 4c3af535149..97a0d3f6c6b 100644 --- a/test/fixtures/wpt/interfaces/web-animations-2.idl +++ b/test/fixtures/wpt/interfaces/web-animations-2.idl @@ -14,7 +14,7 @@ partial interface AnimationTimeline { partial interface Animation { attribute CSSNumberish? startTime; attribute CSSNumberish? currentTime; - readonly attribute double? progress; + readonly attribute double? overallProgress; }; [Exposed=Window] diff --git a/test/fixtures/wpt/interfaces/webauthn.idl b/test/fixtures/wpt/interfaces/webauthn.idl index 2b855144650..46e2418281e 100644 --- a/test/fixtures/wpt/interfaces/webauthn.idl +++ b/test/fixtures/wpt/interfaces/webauthn.idl @@ -330,7 +330,6 @@ partial dictionary AuthenticationExtensionsClientInputs { dictionary CredentialPropertiesOutput { boolean rk; - DOMString authenticatorDisplayName; }; partial dictionary AuthenticationExtensionsClientOutputs { diff --git a/test/fixtures/wpt/interfaces/webgpu.idl b/test/fixtures/wpt/interfaces/webgpu.idl index ebd3b4ab67e..401d2ec69b5 100644 --- a/test/fixtures/wpt/interfaces/webgpu.idl +++ b/test/fixtures/wpt/interfaces/webgpu.idl @@ -78,9 +78,10 @@ interface GPU { }; dictionary GPURequestAdapterOptions { - DOMString featureLevel; + DOMString featureLevel = "core"; GPUPowerPreference powerPreference; boolean forceFallbackAdapter = false; + boolean xrCompatible = false; }; enum GPUPowerPreference { @@ -128,6 +129,7 @@ enum GPUFeatureName { interface GPUDevice : EventTarget { [SameObject] readonly attribute GPUSupportedFeatures features; [SameObject] readonly attribute GPUSupportedLimits limits; + [SameObject] readonly attribute GPUAdapterInfo adapterInfo; [SameObject] readonly attribute GPUQueue queue; diff --git a/test/fixtures/wpt/interfaces/webrtc-stats.idl b/test/fixtures/wpt/interfaces/webrtc-stats.idl index 0b2e956a207..ac820c7c9dc 100644 --- a/test/fixtures/wpt/interfaces/webrtc-stats.idl +++ b/test/fixtures/wpt/interfaces/webrtc-stats.idl @@ -96,6 +96,9 @@ dictionary RTCInboundRtpStreamStats : RTCReceivedRtpStreamStats { unsigned long long retransmittedBytesReceived; unsigned long rtxSsrc; unsigned long fecSsrc; + double totalCorruptionProbability; + double totalSquaredCorruptionProbability; + unsigned long long corruptionMeasurements; }; dictionary RTCRemoteInboundRtpStreamStats : RTCReceivedRtpStreamStats { diff --git a/test/fixtures/wpt/resources/idlharness-shadowrealm.js b/test/fixtures/wpt/resources/idlharness-shadowrealm.js index 9484ca6f512..b959ca00e83 100644 --- a/test/fixtures/wpt/resources/idlharness-shadowrealm.js +++ b/test/fixtures/wpt/resources/idlharness-shadowrealm.js @@ -1,3 +1,7 @@ +/* global shadowRealmEvalAsync */ + +// requires /resources/idlharness-shadowrealm-outer.js + // TODO: it would be nice to support `idl_array.add_objects` function fetch_text(url) { return fetch(url).then(function (r) { @@ -23,38 +27,25 @@ function fetch_text(url) { function idl_test_shadowrealm(srcs, deps) { promise_setup(async t => { const realm = new ShadowRealm(); - // https://github.com/web-platform-tests/wpt/issues/31996 - realm.evaluate("globalThis.self = globalThis; undefined;"); - - realm.evaluate(` - globalThis.self.GLOBAL = { - isWindow: function() { return false; }, - isWorker: function() { return false; }, - isShadowRealm: function() { return true; }, - }; undefined; - `); const specs = await Promise.all(srcs.concat(deps).map(spec => { return fetch_text("/interfaces/" + spec + ".idl"); })); const idls = JSON.stringify(specs); - await new Promise( - realm.evaluate(`(resolve,reject) => { - (async () => { - await import("/resources/testharness.js"); - await import("/resources/WebIDLParser.js"); - await import("/resources/idlharness.js"); - const idls = ${idls}; - const idl_array = new IdlArray(); - for (let i = 0; i < ${srcs.length}; i++) { - idl_array.add_idls(idls[i]); - } - for (let i = ${srcs.length}; i < ${srcs.length + deps.length}; i++) { - idl_array.add_dependency_idls(idls[i]); - } - idl_array.test(); - })().then(resolve, (e) => reject(e.toString())); - }`) - ); + await shadowRealmEvalAsync(realm, ` + await import("/resources/testharness-shadowrealm-inner.js"); + await import("/resources/testharness.js"); + await import("/resources/WebIDLParser.js"); + await import("/resources/idlharness.js"); + const idls = ${idls}; + const idl_array = new IdlArray(); + for (let i = 0; i < ${srcs.length}; i++) { + idl_array.add_idls(idls[i]); + } + for (let i = ${srcs.length}; i < ${srcs.length + deps.length}; i++) { + idl_array.add_dependency_idls(idls[i]); + } + idl_array.test(); + `); await fetch_tests_from_shadow_realm(realm); }); } diff --git a/test/fixtures/wpt/resources/testdriver.js b/test/fixtures/wpt/resources/testdriver.js index d05be1d7df9..bdd18cc0e7d 100644 --- a/test/fixtures/wpt/resources/testdriver.js +++ b/test/fixtures/wpt/resources/testdriver.js @@ -54,50 +54,136 @@ */ bidi: { /** - * `log `_ module. + * @typedef {(String|WindowProxy)} Context A browsing context. Can + * be specified by its ID (a string) or using a `WindowProxy` + * object. */ - log: { + /** + * `bluetooth `_ module. + */ + bluetooth: { /** - * `log.entryAdded `_ event. + * Creates a simulated bluetooth adapter with the given params. Matches the + * `bluetooth.simulateAdapter `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulate_adapter({ + * state: "powered-on" + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.state The state of the simulated bluetooth adapter. + * Matches the + * `bluetooth.SimulateAdapterParameters:state `_ + * value. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the simulated bluetooth adapter should be set. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the simulated bluetooth adapter is created + * and set, or rejected if the operation fails. */ + simulate_adapter: function (params) { + return window.test_driver_internal.bidi.bluetooth.simulate_adapter(params); + } + }, + /** + * `log `_ module. + */ + log: { entry_added: { /** - * Subscribe to the `log.entryAdded` event. This does not - * add actual listeners. To listen to the event, use the - * `on` or `once` methods. - * @param {{contexts?: null | (string | Window)[]}} params - Parameters for the subscription. - * * `contexts`: an array of window proxies or browsing - * context ids to listen to the event. If not provided, the - * event subscription is done for the current window's - * browsing context. `null` for the global subscription. - * @return {Promise} + * @typedef {object} LogEntryAdded `log.entryAdded `_ event. + */ + + /** + * Subscribes to the event. Events will be emitted only if + * there is a subscription for the event. This method does + * not add actual listeners. To listen to the event, use the + * `on` or `once` methods. The buffered events will be + * emitted before the command promise is resolved. + * + * @param {object} [params] Parameters for the subscription. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing + * contexts to subscribe to the event on. It should be + * either an array of Context objects, or null. If null, the + * event will be subscribed to globally. If omitted, the + * event will be subscribed to on the current browsing + * context. + * @returns {Promise} Resolves when the subscription + * is successfully done. */ subscribe: async function (params = {}) { return window.test_driver_internal.bidi.log.entry_added.subscribe(params); }, /** - * Add an event listener for the `log.entryAdded - * `_ event. Make sure `subscribe` is - * called before using this method. + * Adds an event listener for the event. * - * @param callback {function(event): void} - The callback - * to be called when the event is fired. - * @returns {function(): void} - A function to call to - * remove the event listener. + * @param {function(LogEntryAdded): void} callback The + * callback to be called when the event is emitted. The + * callback is called with the event object as a parameter. + * @returns {function(): void} A function that removes the + * added event listener when called. */ on: function (callback) { return window.test_driver_internal.bidi.log.entry_added.on(callback); }, + /** + * Adds an event listener for the event that is only called + * once and removed afterward. + * + * @return {Promise} The promise which is resolved + * with the event object when the event is emitted. + */ once: function () { return new Promise(resolve => { const remove_handler = window.test_driver_internal.bidi.log.entry_added.on( - data => { - resolve(data); + event => { + resolve(event); remove_handler(); }); }); }, } + }, + /** + * `permissions `_ module. + */ + permissions: { + /** + * Sets the state of a permission + * + * This function causes permission requests and queries for the status + * of a certain permission type (e.g. "push", or "background-fetch") to + * always return ``state`` for the specific origin. + * + * Matches the `permissions.setPermission `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.permissions.set_permission({ + * {name: "geolocation"}, + * state: "granted", + * }); + * + * @param {object} params - Parameters for the command. + * @param {PermissionDescriptor} params.descriptor - a `PermissionDescriptor + * `_ + * or derived object. + * @param {PermissionState} params.state - a `PermissionState + * `_ + * value. + * @param {string} [params.origin] - an optional `origin` string to set the + * permission for. If omitted, the permission is set for the + * current window's origin. + * @returns {Promise} fulfilled after the permission is set, or rejected if setting + * the permission fails. + */ + set_permission: function (params) { + return window.test_driver_internal.bidi.permissions.set_permission( + params); + } } }, @@ -1229,6 +1315,12 @@ in_automation: false, bidi: { + bluetooth: { + simulate_adapter: function () { + throw new Error( + "bidi.bluetooth.simulate_adapter is not implemented by testdriver-vendor.js"); + } + }, log: { entry_added: { async subscribe() { @@ -1240,6 +1332,12 @@ "bidi.log.entry_added.on is not implemented by testdriver-vendor.js"); } } + }, + permissions: { + async set_permission() { + throw new Error( + "bidi.permissions.set_permission() is not implemented by testdriver-vendor.js"); + } } }, diff --git a/test/fixtures/wpt/resources/testharness-shadowrealm-audioworkletprocessor.js b/test/fixtures/wpt/resources/testharness-shadowrealm-audioworkletprocessor.js new file mode 100644 index 00000000000..a87d9130908 --- /dev/null +++ b/test/fixtures/wpt/resources/testharness-shadowrealm-audioworkletprocessor.js @@ -0,0 +1,52 @@ +/** + * AudioWorkletProcessor intended for hosting a ShadowRealm and running a test + * inside of that ShadowRealm. + */ +globalThis.TestRunner = class TestRunner extends AudioWorkletProcessor { + constructor() { + super(); + this.createShadowRealmAndStartTests(); + } + + /** + * Fetch adaptor function intended as a drop-in replacement for fetchAdaptor() + * (see testharness-shadowrealm-outer.js), but it does not assume fetch() is + * present in the realm. Instead, it relies on setupFakeFetchOverMessagePort() + * having been called on the port on the other side of this.port's channel. + */ + fetchOverPortExecutor(resource) { + return (resolve, reject) => { + const listener = (event) => { + if (typeof event.data !== "string" || !event.data.startsWith("fetchResult::")) { + return; + } + + const result = event.data.slice("fetchResult::".length); + if (result.startsWith("success::")) { + resolve(result.slice("success::".length)); + } else { + reject(result.slice("fail::".length)); + } + + this.port.removeEventListener("message", listener); + } + this.port.addEventListener("message", listener); + this.port.start(); + this.port.postMessage(`fetchRequest::${resource}`); + } + } + + /** + * Async method, which is patched over in + * (test).any.audioworklet-shadowrealm.js; see serve.py + */ + async createShadowRealmAndStartTests() { + throw new Error("Forgot to overwrite this method!"); + } + + /** Overrides AudioWorkletProcessor.prototype.process() */ + process() { + return false; + } +}; +registerProcessor("test-runner", TestRunner); diff --git a/test/fixtures/wpt/resources/testharness-shadowrealm-inner.js b/test/fixtures/wpt/resources/testharness-shadowrealm-inner.js new file mode 100644 index 00000000000..98df77b0b6c --- /dev/null +++ b/test/fixtures/wpt/resources/testharness-shadowrealm-inner.js @@ -0,0 +1,27 @@ +// testharness file with ShadowRealm utilities to be imported inside ShadowRealm + +/** + * Set up all properties on the ShadowRealm's global object that tests will + * expect to be present. + * + * @param {string} queryString - string to use as value for location.search, + * used for subsetting some tests + * @param {function} fetchAdaptor - a function that takes a resource URI and + * returns a function which itself takes a (resolve, reject) pair from the + * hosting realm, and calls resolve with the text result of fetching the + * resource, or reject with a string indicating the error that occurred + */ +globalThis.setShadowRealmGlobalProperties = function (queryString, fetchAdaptor) { + globalThis.fetch_json = (resource) => { + const executor = fetchAdaptor(resource); + return new Promise(executor).then((s) => JSON.parse(s)); + }; + + globalThis.location = { search: queryString }; +}; + +globalThis.GLOBAL = { + isWindow: function() { return false; }, + isWorker: function() { return false; }, + isShadowRealm: function() { return true; }, +}; diff --git a/test/fixtures/wpt/resources/testharness-shadowrealm-outer.js b/test/fixtures/wpt/resources/testharness-shadowrealm-outer.js new file mode 100644 index 00000000000..9665ff2656f --- /dev/null +++ b/test/fixtures/wpt/resources/testharness-shadowrealm-outer.js @@ -0,0 +1,127 @@ +// testharness file with ShadowRealm utilities to be imported in the realm +// hosting the ShadowRealm + +/** + * Convenience function for evaluating some async code in the ShadowRealm and + * waiting for the result. + * + * @param {ShadowRealm} realm - the ShadowRealm to evaluate the code in + * @param {string} asyncBody - the code to evaluate; will be put in the body of + * an async function, and must return a value explicitly if a value is to be + * returned to the hosting realm. + */ +globalThis.shadowRealmEvalAsync = function (realm, asyncBody) { + return new Promise(realm.evaluate(` + (resolve, reject) => { + (async () => { + ${asyncBody} + })().then(resolve, (e) => reject(e.toString())); + } + `)); +}; + +/** + * Convenience adaptor function for fetch() that can be passed to + * setShadowRealmGlobalProperties() (see testharness-shadowrealm-inner.js). + * Used to adapt the hosting realm's fetch(), if present, to fetch a resource + * and pass its text through the callable boundary to the ShadowRealm. + */ +globalThis.fetchAdaptor = (resource) => (resolve, reject) => { + fetch(resource) + .then(res => res.text()) + .then(resolve, (e) => reject(e.toString())); +}; + +let sharedWorkerMessagePortPromise; +/** + * Used when the hosting realm is a worker. This value is a Promise that + * resolves to a function that posts a message to the worker's message port, + * just like postMessage(). The message port is only available asynchronously in + * SharedWorkers and ServiceWorkers. + */ +globalThis.getPostMessageFunc = async function () { + if (typeof postMessage === "function") { + return postMessage; // postMessage available directly in dedicated worker + } + + if (typeof clients === "object") { + // Messages from the ShadowRealm are not in response to any message received + // from the ServiceWorker's client, so broadcast them to all clients + const allClients = await clients.matchAll({ includeUncontrolled: true }); + return function broadcast(msg) { + allClients.map(client => client.postMessage(msg)); + } + } + + if (sharedWorkerMessagePortPromise) { + return await sharedWorkerMessagePortPromise; + } + + throw new Error("getPostMessageFunc is intended for Worker scopes"); +} + +// Port available asynchronously in shared worker, but not via an async func +let savedResolver; +if (globalThis.constructor.name === "SharedWorkerGlobalScope") { + sharedWorkerMessagePortPromise = new Promise((resolve) => { + savedResolver = resolve; + }); + addEventListener("connect", function (event) { + const port = event.ports[0]; + savedResolver(port.postMessage.bind(port)); + }); +} + +/** + * Used when the hosting realm does not permit dynamic import, e.g. in + * ServiceWorkers or AudioWorklets. Requires an adaptor function such as + * fetchAdaptor() above, or an equivalent if fetch() is not present in the + * hosting realm. + * + * @param {ShadowRealm} realm - the ShadowRealm in which to setup a + * fakeDynamicImport() global function. + * @param {function} adaptor - an adaptor function that does what fetchAdaptor() + * does. + */ +globalThis.setupFakeDynamicImportInShadowRealm = function(realm, adaptor) { + function fetchModuleTextExecutor(url) { + return (resolve, reject) => { + new Promise(adaptor(url)) + .then(text => realm.evaluate(text + ";\nundefined")) + .then(resolve, (e) => reject(e.toString())); + } + } + + realm.evaluate(` + (fetchModuleTextExecutor) => { + globalThis.fakeDynamicImport = function (url) { + return new Promise(fetchModuleTextExecutor(url)); + } + } + `)(fetchModuleTextExecutor); +}; + +/** + * Used when the hosting realm does not expose fetch(), i.e. in worklets. The + * port on the other side of the channel needs to send messages starting with + * 'fetchRequest::' and listen for messages starting with 'fetchResult::'. See + * testharness-shadowrealm-audioworkletprocessor.js. + * + * @param {port} MessagePort - the message port on which to listen for fetch + * requests + */ +globalThis.setupFakeFetchOverMessagePort = function (port) { + port.addEventListener("message", (event) => { + if (typeof event.data !== "string" || !event.data.startsWith("fetchRequest::")) { + return; + } + + fetch(event.data.slice("fetchRequest::".length)) + .then(res => res.text()) + .then( + text => port.postMessage(`fetchResult::success::${text}`), + error => port.postMessage(`fetchResult::fail::${error}`), + ); + }); + port.start(); +}