diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md
deleted file mode 100644
index e07926822e6..00000000000
--- a/test/fixtures/wpt/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Web Platform Test Fixtures
-
-The files in this folder, including this document,
-are generated by [`git node wpt`][].
-
-This folder contains a subset of the [Web Platform Tests][] for the
-implementation of Web APIs in Node.js.
-
-See [test/wpt](../../wpt/README.md) for information on how these tests are run.
-
-Last update:
-
-- common: https://github.com/web-platform-tests/wpt/tree/8bfc72a4f7/common
-- eventsource: https://github.com/web-platform-tests/wpt/tree/93ca7d3363/eventsource
-- fetch: https://github.com/web-platform-tests/wpt/tree/1b9332c3c8/fetch
-- FileAPI: https://github.com/web-platform-tests/wpt/tree/5aa50dd415/FileAPI
-- interfaces: https://github.com/web-platform-tests/wpt/tree/40d3681ef5/interfaces
-- mimesniff: https://github.com/web-platform-tests/wpt/tree/0e9d465d28/mimesniff
-- resources: https://github.com/web-platform-tests/wpt/tree/34dfef83fc/resources
-- service-workers: https://github.com/web-platform-tests/wpt/tree/3ebc2c5109/service-workers
-- storage: https://github.com/web-platform-tests/wpt/tree/9f1cfd6824/storage
-- websockets: https://github.com/web-platform-tests/wpt/tree/a7a594d8c0/websockets
-- xhr: https://github.com/web-platform-tests/wpt/tree/5aa50dd415/xhr
-
-[Web Platform Tests]: https://github.com/web-platform-tests/wpt
-[`git node wpt`]: https://github.com/nodejs/node-core-utils/blob/main/docs/git-node.md#git-node-wpt
diff --git a/test/fixtures/wpt/common/dispatcher/dispatcher.js b/test/fixtures/wpt/common/dispatcher/dispatcher.js
index ce17a7c9145..dab0100020b 100644
--- a/test/fixtures/wpt/common/dispatcher/dispatcher.js
+++ b/test/fixtures/wpt/common/dispatcher/dispatcher.js
@@ -16,6 +16,12 @@ function findLocation() {
if (location.href == 'about:srcdoc') {
return findLocationFromAncestors(window.parent);
}
+ if (location.protocol == 'blob:' || location.protocol == 'data:') {
+ // Allows working around blob and data URLs.
+ if (self.document && self.document.baseURI) {
+ return self.document.baseURI;
+ }
+ }
return location;
}
diff --git a/test/fixtures/wpt/common/security-features/tools/generate.py b/test/fixtures/wpt/common/security-features/tools/generate.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/common/security-features/tools/spec_validator.py b/test/fixtures/wpt/common/security-features/tools/spec_validator.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/fetch/api/basic/keepalive.any.js b/test/fixtures/wpt/fetch/api/basic/keepalive.any.js
index d6ec1f67920..55225e00aa0 100644
--- a/test/fixtures/wpt/fetch/api/basic/keepalive.any.js
+++ b/test/fixtures/wpt/fetch/api/basic/keepalive.any.js
@@ -18,7 +18,7 @@ const {
* document event.
*/
function keepaliveSimpleRequestTest(method) {
- for (const evt of ['load', 'pagehide', 'unload']) {
+ for (const evt of ['load', 'unload', 'pagehide']) {
const desc =
`[keepalive] simple ${method} request on '${evt}' [no payload]`;
promise_test(async (test) => {
@@ -30,7 +30,6 @@ function keepaliveSimpleRequestTest(method) {
if (evt != 'load') {
iframe.remove();
}
- assert_equals(await getTokenFromMessage(), token1);
assertStashedTokenAsync(desc, token1);
}, `${desc}; setting up`);
diff --git a/test/fixtures/wpt/fetch/api/basic/request-headers.any.js b/test/fixtures/wpt/fetch/api/basic/request-headers.any.js
index 8d2ad31e708..f6a7fe1494b 100644
--- a/test/fixtures/wpt/fetch/api/basic/request-headers.any.js
+++ b/test/fixtures/wpt/fetch/api/basic/request-headers.any.js
@@ -54,7 +54,7 @@ requestHeaders("Fetch with POST with Blob body", url, "POST", new Blob(["Test"])
requestHeaders("Fetch with POST with ArrayBuffer body", url, "POST", new ArrayBuffer(4), location.origin, "4");
requestHeaders("Fetch with POST with Uint8Array body", url, "POST", new Uint8Array(4), location.origin, "4");
requestHeaders("Fetch with POST with Int8Array body", url, "POST", new Int8Array(4), location.origin, "4");
-requestHeaders("Fetch with POST with Float16Array body", url, "POST", new Float16Array(1), location.origin, "2");
+requestHeaders("Fetch with POST with Float16Array body", url, "POST", () => new Float16Array(1), location.origin, "2");
requestHeaders("Fetch with POST with Float32Array body", url, "POST", new Float32Array(1), location.origin, "4");
requestHeaders("Fetch with POST with Float64Array body", url, "POST", new Float64Array(1), location.origin, "8");
requestHeaders("Fetch with POST with DataView body", url, "POST", new DataView(new ArrayBuffer(8), 0, 4), location.origin, "4");
diff --git a/test/fixtures/wpt/fetch/api/resources/keepalive-helper.js b/test/fixtures/wpt/fetch/api/resources/keepalive-helper.js
index f6f511631e5..1e75c060aea 100644
--- a/test/fixtures/wpt/fetch/api/resources/keepalive-helper.js
+++ b/test/fixtures/wpt/fetch/api/resources/keepalive-helper.js
@@ -174,3 +174,26 @@ function keepaliveRedirectInUnloadTest(desc, {
desc, token, {expectTokenExist: expectFetchSucceed});
}, `${desc}; setting up`);
}
+
+/**
+* utility to create pending keepalive fetch requests
+* The pending request state is achieved by ensuring the server (trickle.py) does not
+* immediately respond to the fetch requests.
+* The response delay is set as a url parameter.
+*/
+
+function createPendingKeepAliveRequest(delay, remote = false) {
+ // trickle.py is a script that can make a delayed response to the client request
+ const trickleRemoteURL = get_host_info().HTTPS_REMOTE_ORIGIN + '/fetch/api/resources/trickle.py?count=1&ms=';
+ const trickleLocalURL = get_host_info().HTTP_ORIGIN + '/fetch/api/resources/trickle.py?count=1&ms=';
+ url = remote ? trickleRemoteURL : trickleLocalURL;
+
+ const body = '*'.repeat(10);
+ return fetch(url + delay, { keepalive: true, body, method: 'POST' }).then(res => {
+ return res.text();
+ }).then(() => {
+ return new Promise(resolve => step_timeout(resolve, 1));
+ }).catch((error) => {
+ return Promise.reject(error);;
+ })
+}
diff --git a/test/fixtures/wpt/fetch/api/response/response-blob-realm.any.js b/test/fixtures/wpt/fetch/api/response/response-blob-realm.any.js
index 1be105416a0..1cc51fc71b6 100644
--- a/test/fixtures/wpt/fetch/api/response/response-blob-realm.any.js
+++ b/test/fixtures/wpt/fetch/api/response/response-blob-realm.any.js
@@ -1,3 +1,4 @@
+// META: global=window
// META: title=realm of Response bytes()
"use strict";
diff --git a/test/fixtures/wpt/fetch/compression-dictionary/dictionary-decompression.tentative.https.html b/test/fixtures/wpt/fetch/compression-dictionary/dictionary-decompression.tentative.https.html
index c7b3b7c3a5a..33aeb4466ba 100644
--- a/test/fixtures/wpt/fetch/compression-dictionary/dictionary-decompression.tentative.https.html
+++ b/test/fixtures/wpt/fetch/compression-dictionary/dictionary-decompression.tentative.https.html
@@ -20,7 +20,7 @@
// Check if the data compressed using Brotli with the dictionary can be
// decompressed.
- const data_url = `${kCompressedDataPath}?content_encoding=br-d`;
+ const data_url = `${kCompressedDataPath}?content_encoding=dcb`;
assert_equals(await (await fetch(data_url)).text(), kExpectedCompressedData);
}, 'Decompresion using Brotli with the dictionary works as expected');
@@ -34,7 +34,7 @@
// Check if the data compressed using Zstandard with the dictionary can be
// decompressed.
- const data_url = `${kCompressedDataPath}?content_encoding=zstd-d`;
+ const data_url = `${kCompressedDataPath}?content_encoding=dcz`;
assert_equals(await (await fetch(data_url)).text(), kExpectedCompressedData);
}, 'Decompresion using Zstandard with the dictionary works as expected');
@@ -50,7 +50,7 @@
// Check if the data compressed using Brotli with the dictionary can be
// decompressed.
const data_url =
- getRemoteHostUrl(`${kCompressedDataPath}?content_encoding=br-d`);
+ getRemoteHostUrl(`${kCompressedDataPath}?content_encoding=dcb`);
assert_equals(await (await fetch(data_url)).text(), kExpectedCompressedData);
}, 'Decompresion of a cross origin resource works as expected');
diff --git a/test/fixtures/wpt/fetch/compression-dictionary/dictionary-fetch-with-link-element.tentative.https.html b/test/fixtures/wpt/fetch/compression-dictionary/dictionary-fetch-with-link-element.tentative.https.html
index 23a271d4818..d465ceb3d85 100644
--- a/test/fixtures/wpt/fetch/compression-dictionary/dictionary-fetch-with-link-element.tentative.https.html
+++ b/test/fixtures/wpt/fetch/compression-dictionary/dictionary-fetch-with-link-element.tentative.https.html
@@ -11,9 +11,9 @@
diff --git a/test/fixtures/wpt/fetch/metadata/resources/post-to-owner.py b/test/fixtures/wpt/fetch/metadata/resources/post-to-owner.py
index 256dd6e49dc..2d4896867bb 100644
--- a/test/fixtures/wpt/fetch/metadata/resources/post-to-owner.py
+++ b/test/fixtures/wpt/fetch/metadata/resources/post-to-owner.py
@@ -7,30 +7,20 @@ def main(request, response):
(b"Content-Type", b"text/html"),
(b"Cache-Control", b"no-cache, no-store, must-revalidate")
]
- key = request.GET.first(b"key", None)
-
- # We serialize the key into JSON, so have to decode it first.
- if key is not None:
- key = key.decode('utf-8')
body = u"""
-
""" % (json.dumps({
u"dest": isomorphic_decode(request.headers.get(b"sec-fetch-dest", b"")),
u"mode": isomorphic_decode(request.headers.get(b"sec-fetch-mode", b"")),
u"site": isomorphic_decode(request.headers.get(b"sec-fetch-site", b"")),
u"user": isomorphic_decode(request.headers.get(b"sec-fetch-user", b"")),
- }), json.dumps(key))
+ }))
return headers, body
diff --git a/test/fixtures/wpt/fetch/metadata/tools/generate.py b/test/fixtures/wpt/fetch/metadata/tools/generate.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/interfaces/EXT_disjoint_timer_query.idl b/test/fixtures/wpt/interfaces/EXT_disjoint_timer_query.idl
index cf0c8d9a286..d2ed383c038 100644
--- a/test/fixtures/wpt/interfaces/EXT_disjoint_timer_query.idl
+++ b/test/fixtures/wpt/interfaces/EXT_disjoint_timer_query.idl
@@ -19,7 +19,7 @@ interface EXT_disjoint_timer_query {
const GLenum TIMESTAMP_EXT = 0x8E28;
const GLenum GPU_DISJOINT_EXT = 0x8FBB;
- WebGLTimerQueryEXT? createQueryEXT();
+ WebGLTimerQueryEXT createQueryEXT();
undefined deleteQueryEXT(WebGLTimerQueryEXT? query);
[WebGLHandlesContextLoss] boolean isQueryEXT(WebGLTimerQueryEXT? query);
undefined beginQueryEXT(GLenum target, WebGLTimerQueryEXT query);
diff --git a/test/fixtures/wpt/interfaces/FileAPI.idl b/test/fixtures/wpt/interfaces/FileAPI.idl
index aee0e65dcae..49219fce277 100644
--- a/test/fixtures/wpt/interfaces/FileAPI.idl
+++ b/test/fixtures/wpt/interfaces/FileAPI.idl
@@ -20,6 +20,7 @@ interface Blob {
[NewObject] ReadableStream stream();
[NewObject] Promise text();
[NewObject] Promise arrayBuffer();
+ [NewObject] Promise bytes();
};
enum EndingType { "transparent", "native" };
diff --git a/test/fixtures/wpt/interfaces/META.yml b/test/fixtures/wpt/interfaces/META.yml
index c1dd8dddf9e..94e06fb1ce5 100644
--- a/test/fixtures/wpt/interfaces/META.yml
+++ b/test/fixtures/wpt/interfaces/META.yml
@@ -1,2 +1,4 @@
suggested_reviewers:
- foolip
+ - past
+ - schenney-chromium
diff --git a/test/fixtures/wpt/interfaces/OES_vertex_array_object.idl b/test/fixtures/wpt/interfaces/OES_vertex_array_object.idl
index 8aeb7459f3b..e2252ad59d9 100644
--- a/test/fixtures/wpt/interfaces/OES_vertex_array_object.idl
+++ b/test/fixtures/wpt/interfaces/OES_vertex_array_object.idl
@@ -11,7 +11,7 @@ interface WebGLVertexArrayObjectOES : WebGLObject {
interface OES_vertex_array_object {
const GLenum VERTEX_ARRAY_BINDING_OES = 0x85B5;
- WebGLVertexArrayObjectOES? createVertexArrayOES();
+ WebGLVertexArrayObjectOES createVertexArrayOES();
undefined deleteVertexArrayOES(WebGLVertexArrayObjectOES? arrayObject);
[WebGLHandlesContextLoss] GLboolean isVertexArrayOES(WebGLVertexArrayObjectOES? arrayObject);
undefined bindVertexArrayOES(WebGLVertexArrayObjectOES? arrayObject);
diff --git a/test/fixtures/wpt/interfaces/WebCryptoAPI.idl b/test/fixtures/wpt/interfaces/WebCryptoAPI.idl
index 0e68ea82f59..ae85c1cfe46 100644
--- a/test/fixtures/wpt/interfaces/WebCryptoAPI.idl
+++ b/test/fixtures/wpt/interfaces/WebCryptoAPI.idl
@@ -68,7 +68,7 @@ interface SubtleCrypto {
sequence keyUsages );
Promise deriveBits(AlgorithmIdentifier algorithm,
CryptoKey baseKey,
- unsigned long length);
+ optional unsigned long? length = null);
Promise importKey(KeyFormat format,
(BufferSource or JsonWebKey) keyData,
diff --git a/test/fixtures/wpt/interfaces/compression.idl b/test/fixtures/wpt/interfaces/compression.idl
index 7525d7c9847..defe4ba55cd 100644
--- a/test/fixtures/wpt/interfaces/compression.idl
+++ b/test/fixtures/wpt/interfaces/compression.idl
@@ -1,7 +1,7 @@
// GENERATED CONTENT - DO NOT EDIT
// Content was automatically extracted by Reffy into webref
// (https://github.com/w3c/webref)
-// Source: Compression Streams (https://wicg.github.io/compression/)
+// Source: Compression Standard (https://compression.spec.whatwg.org/)
enum CompressionFormat {
"deflate",
diff --git a/test/fixtures/wpt/interfaces/compute-pressure.idl b/test/fixtures/wpt/interfaces/compute-pressure.idl
index a90febffc3b..77537feb106 100644
--- a/test/fixtures/wpt/interfaces/compute-pressure.idl
+++ b/test/fixtures/wpt/interfaces/compute-pressure.idl
@@ -21,7 +21,7 @@ interface PressureObserver {
undefined disconnect();
sequence takeRecords();
- [SameObject] static readonly attribute FrozenArray supportedSources;
+ [SameObject] static readonly attribute FrozenArray knownSources;
};
[Exposed=(DedicatedWorker,SharedWorker,Window), SecureContext]
diff --git a/test/fixtures/wpt/interfaces/credential-management.idl b/test/fixtures/wpt/interfaces/credential-management.idl
index 75e18319190..94cf6a58614 100644
--- a/test/fixtures/wpt/interfaces/credential-management.idl
+++ b/test/fixtures/wpt/interfaces/credential-management.idl
@@ -8,6 +8,7 @@ interface Credential {
readonly attribute USVString id;
readonly attribute DOMString type;
static Promise isConditionalMediationAvailable();
+ static Promise willRequestConditionalCreation();
};
[SecureContext]
@@ -45,6 +46,7 @@ enum CredentialMediationRequirement {
};
dictionary CredentialCreationOptions {
+ CredentialMediationRequirement mediation = "optional";
AbortSignal signal;
};
diff --git a/test/fixtures/wpt/interfaces/css-anchor-position.idl b/test/fixtures/wpt/interfaces/css-anchor-position.idl
index 5eeaa030b85..890d9929086 100644
--- a/test/fixtures/wpt/interfaces/css-anchor-position.idl
+++ b/test/fixtures/wpt/interfaces/css-anchor-position.idl
@@ -79,6 +79,6 @@ interface CSSPositionTryDescriptors : CSSStyleDeclaration {
attribute CSSOMString justify-self;
attribute CSSOMString positionAnchor;
attribute CSSOMString position-anchor;
- attribute CSSOMString insetArea;
- attribute CSSOMString inset-area;
+ attribute CSSOMString positionArea;
+ attribute CSSOMString position-area;
};
diff --git a/test/fixtures/wpt/interfaces/css-contain-3.idl b/test/fixtures/wpt/interfaces/css-conditional-5.idl
similarity index 76%
rename from test/fixtures/wpt/interfaces/css-contain-3.idl
rename to test/fixtures/wpt/interfaces/css-conditional-5.idl
index 0ecf3804954..b1919213ebc 100644
--- a/test/fixtures/wpt/interfaces/css-contain-3.idl
+++ b/test/fixtures/wpt/interfaces/css-conditional-5.idl
@@ -1,7 +1,7 @@
// GENERATED CONTENT - DO NOT EDIT
// Content was automatically extracted by Reffy into webref
// (https://github.com/w3c/webref)
-// Source: CSS Containment Module Level 3 (https://drafts.csswg.org/css-contain-3/)
+// Source: CSS Conditional Rules Module Level 5 (https://drafts.csswg.org/css-conditional-5/)
[Exposed=Window]
interface CSSContainerRule : CSSConditionRule {
diff --git a/test/fixtures/wpt/interfaces/css-conditional.idl b/test/fixtures/wpt/interfaces/css-conditional.idl
index d87f305fddf..eace34c0f49 100644
--- a/test/fixtures/wpt/interfaces/css-conditional.idl
+++ b/test/fixtures/wpt/interfaces/css-conditional.idl
@@ -15,10 +15,12 @@ interface CSSConditionRule : CSSGroupingRule {
[Exposed=Window]
interface CSSMediaRule : CSSConditionRule {
[SameObject, PutForwards=mediaText] readonly attribute MediaList media;
+ readonly attribute boolean matches;
};
[Exposed=Window]
interface CSSSupportsRule : CSSConditionRule {
+ readonly attribute boolean matches;
};
partial namespace CSS {
diff --git a/test/fixtures/wpt/interfaces/css-font-loading.idl b/test/fixtures/wpt/interfaces/css-font-loading.idl
index 100f1f7fed5..a5db8f3c494 100644
--- a/test/fixtures/wpt/interfaces/css-font-loading.idl
+++ b/test/fixtures/wpt/interfaces/css-font-loading.idl
@@ -3,8 +3,6 @@
// (https://github.com/w3c/webref)
// Source: CSS Font Loading Module Level 3 (https://drafts.csswg.org/css-font-loading-3/)
-typedef (ArrayBuffer or ArrayBufferView) BinaryData;
-
dictionary FontFaceDescriptors {
CSSOMString style = "normal";
CSSOMString weight = "normal";
@@ -22,7 +20,7 @@ enum FontFaceLoadStatus { "unloaded", "loading", "loaded", "error" };
[Exposed=(Window,Worker)]
interface FontFace {
- constructor(CSSOMString family, (CSSOMString or BinaryData) source,
+ constructor(CSSOMString family, (CSSOMString or BufferSource) source,
optional FontFaceDescriptors descriptors = {});
attribute CSSOMString family;
attribute CSSOMString style;
@@ -97,8 +95,6 @@ enum FontFaceSetLoadStatus { "loading", "loaded" };
[Exposed=(Window,Worker)]
interface FontFaceSet : EventTarget {
- constructor(sequence initialFaces);
-
setlike;
FontFaceSet add(FontFace font);
boolean delete(FontFace font);
diff --git a/test/fixtures/wpt/interfaces/css-mixins.idl b/test/fixtures/wpt/interfaces/css-mixins.idl
new file mode 100644
index 00000000000..49806ab5470
--- /dev/null
+++ b/test/fixtures/wpt/interfaces/css-mixins.idl
@@ -0,0 +1,7 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content was automatically extracted by Reffy into webref
+// (https://github.com/w3c/webref)
+// Source: CSS Functions and Mixins Module (https://drafts.csswg.org/css-mixins-1/)
+
+[Exposed=Window]
+interface CSSFunctionRule : CSSGroupingRule { };
diff --git a/test/fixtures/wpt/interfaces/css-nesting.idl b/test/fixtures/wpt/interfaces/css-nesting.idl
index 58d3247f904..1ad0404ecf9 100644
--- a/test/fixtures/wpt/interfaces/css-nesting.idl
+++ b/test/fixtures/wpt/interfaces/css-nesting.idl
@@ -4,6 +4,6 @@
// Source: CSS Nesting Module (https://drafts.csswg.org/css-nesting-1/)
[Exposed=Window]
-interface CSSNestRule : CSSGroupingRule {
+interface CSSNestedDeclarations : CSSRule {
[SameObject, PutForwards=cssText] readonly attribute CSSStyleProperties style;
};
diff --git a/test/fixtures/wpt/interfaces/css-scroll-snap-2.idl b/test/fixtures/wpt/interfaces/css-scroll-snap-2.idl
index a346969c56a..bb42d60600f 100644
--- a/test/fixtures/wpt/interfaces/css-scroll-snap-2.idl
+++ b/test/fixtures/wpt/interfaces/css-scroll-snap-2.idl
@@ -14,3 +14,8 @@ interface SnapEvent : Event {
readonly attribute Node? snapTargetBlock;
readonly attribute Node? snapTargetInline;
};
+
+partial interface mixin GlobalEventHandlers {
+ attribute EventHandler onsnapchanged;
+ attribute EventHandler onsnapchanging;
+};
diff --git a/test/fixtures/wpt/interfaces/css-view-transitions-2.idl b/test/fixtures/wpt/interfaces/css-view-transitions-2.idl
index 559870751a2..774cb655bda 100644
--- a/test/fixtures/wpt/interfaces/css-view-transitions-2.idl
+++ b/test/fixtures/wpt/interfaces/css-view-transitions-2.idl
@@ -3,10 +3,6 @@
// (https://github.com/w3c/webref)
// Source: CSS View Transitions Module Level 2 (https://drafts.csswg.org/css-view-transitions-2/)
-partial interface CSSRule {
- const unsigned short VIEW_TRANSITION_RULE = 15;
-};
-
enum ViewTransitionNavigation { "auto", "none" };
[Exposed=Window]
diff --git a/test/fixtures/wpt/interfaces/cssom-view.idl b/test/fixtures/wpt/interfaces/cssom-view.idl
index 57e559e7f12..f922bc81486 100644
--- a/test/fixtures/wpt/interfaces/cssom-view.idl
+++ b/test/fixtures/wpt/interfaces/cssom-view.idl
@@ -84,10 +84,14 @@ interface Screen {
partial interface Document {
Element? elementFromPoint(double x, double y);
sequence elementsFromPoint(double x, double y);
- CaretPosition? caretPositionFromPoint(double x, double y);
+ CaretPosition? caretPositionFromPoint(double x, double y, optional CaretPositionFromPointOptions options = {});
readonly attribute Element? scrollingElement;
};
+dictionary CaretPositionFromPointOptions {
+ sequence shadowRoots = [];
+};
+
[Exposed=Window]
interface CaretPosition {
readonly attribute Node offsetNode;
diff --git a/test/fixtures/wpt/interfaces/cssom.idl b/test/fixtures/wpt/interfaces/cssom.idl
index 005496e7ede..7f9aefdb97e 100644
--- a/test/fixtures/wpt/interfaces/cssom.idl
+++ b/test/fixtures/wpt/interfaces/cssom.idl
@@ -130,6 +130,8 @@ interface CSSPageDescriptors : CSSStyleDeclaration {
attribute [LegacyNullToEmptyString] CSSOMString margin-bottom;
attribute [LegacyNullToEmptyString] CSSOMString margin-left;
attribute [LegacyNullToEmptyString] CSSOMString size;
+ attribute [LegacyNullToEmptyString] CSSOMString pageOrientation;
+ attribute [LegacyNullToEmptyString] CSSOMString page-orientation;
attribute [LegacyNullToEmptyString] CSSOMString marks;
attribute [LegacyNullToEmptyString] CSSOMString bleed;
};
diff --git a/test/fixtures/wpt/interfaces/digital-identities.idl b/test/fixtures/wpt/interfaces/digital-identities.idl
index 2d1b7208502..bbb0c938303 100644
--- a/test/fixtures/wpt/interfaces/digital-identities.idl
+++ b/test/fixtures/wpt/interfaces/digital-identities.idl
@@ -1,7 +1,7 @@
// GENERATED CONTENT - DO NOT EDIT
// Content was automatically extracted by Reffy into webref
// (https://github.com/w3c/webref)
-// Source: Digital Credentials (https://wicg.github.io/digital-identities/)
+// Source: Digital Credentials (https://wicg.github.io/digital-credentials)
partial interface Navigator {
[SecureContext, SameObject] readonly attribute CredentialsContainer identity;
@@ -23,5 +23,5 @@ dictionary IdentityRequestProvider {
[Exposed=Window, SecureContext]
interface DigitalCredential : Credential {
readonly attribute DOMString protocol;
- [SameObject] readonly attribute Uint8Array data;
+ readonly attribute any data;
};
diff --git a/test/fixtures/wpt/interfaces/dom.idl b/test/fixtures/wpt/interfaces/dom.idl
index 72d61f5cfd8..99192924f4f 100644
--- a/test/fixtures/wpt/interfaces/dom.idl
+++ b/test/fixtures/wpt/interfaces/dom.idl
@@ -120,9 +120,9 @@ interface mixin ParentNode {
readonly attribute Element? lastElementChild;
readonly attribute unsigned long childElementCount;
- [CEReactions, Unscopable] undefined prepend((Node or TrustedScript or DOMString)... nodes);
- [CEReactions, Unscopable] undefined append((Node or TrustedScript or DOMString)... nodes);
- [CEReactions, Unscopable] undefined replaceChildren((Node or TrustedScript or DOMString)... nodes);
+ [CEReactions, Unscopable] undefined prepend((Node or DOMString)... nodes);
+ [CEReactions, Unscopable] undefined append((Node or DOMString)... nodes);
+ [CEReactions, Unscopable] undefined replaceChildren((Node or DOMString)... nodes);
Element? querySelector(DOMString selectors);
[NewObject] NodeList querySelectorAll(DOMString selectors);
@@ -139,9 +139,9 @@ Element includes NonDocumentTypeChildNode;
CharacterData includes NonDocumentTypeChildNode;
interface mixin ChildNode {
- [CEReactions, Unscopable] undefined before((Node or TrustedScript or DOMString)... nodes);
- [CEReactions, Unscopable] undefined after((Node or TrustedScript or DOMString)... nodes);
- [CEReactions, Unscopable] undefined replaceWith((Node or TrustedScript or DOMString)... nodes);
+ [CEReactions, Unscopable] undefined before((Node or DOMString)... nodes);
+ [CEReactions, Unscopable] undefined after((Node or DOMString)... nodes);
+ [CEReactions, Unscopable] undefined replaceWith((Node or DOMString)... nodes);
[CEReactions, Unscopable] undefined remove();
};
DocumentType includes ChildNode;
diff --git a/test/fixtures/wpt/interfaces/encrypted-media.idl b/test/fixtures/wpt/interfaces/encrypted-media.idl
index f5d5aa8d6c0..c034cd0bf0d 100644
--- a/test/fixtures/wpt/interfaces/encrypted-media.idl
+++ b/test/fixtures/wpt/interfaces/encrypted-media.idl
@@ -5,128 +5,118 @@
[Exposed=Window]
partial interface Navigator {
- [SecureContext] Promise requestMediaKeySystemAccess (DOMString keySystem, sequence supportedConfigurations);
+ [SecureContext] Promise requestMediaKeySystemAccess (
+ DOMString keySystem,
+ sequence supportedConfigurations);
};
enum MediaKeysRequirement {
- "required",
- "optional",
- "not-allowed"
+ "required",
+ "optional",
+ "not-allowed"
};
dictionary MediaKeySystemConfiguration {
- DOMString label = "";
- sequence initDataTypes = [];
- sequence audioCapabilities = [];
- sequence videoCapabilities = [];
- MediaKeysRequirement distinctiveIdentifier = "optional";
- MediaKeysRequirement persistentState = "optional";
- sequence sessionTypes;
+ DOMString label = "";
+ sequence initDataTypes = [];
+ sequence audioCapabilities = [];
+ sequence videoCapabilities = [];
+ MediaKeysRequirement distinctiveIdentifier = "optional";
+ MediaKeysRequirement persistentState = "optional";
+ sequence sessionTypes;
};
dictionary MediaKeySystemMediaCapability {
- DOMString contentType = "";
- DOMString? encryptionScheme = null;
- DOMString robustness = "";
+ DOMString contentType = "";
+ DOMString? encryptionScheme = null;
+ DOMString robustness = "";
};
[Exposed=Window, SecureContext] interface MediaKeySystemAccess {
- readonly attribute DOMString keySystem;
- MediaKeySystemConfiguration getConfiguration ();
- Promise createMediaKeys ();
+ readonly attribute DOMString keySystem;
+ MediaKeySystemConfiguration getConfiguration ();
+ Promise createMediaKeys ();
};
enum MediaKeySessionType {
- "temporary",
- "persistent-license"
+ "temporary",
+ "persistent-license"
};
[Exposed=Window, SecureContext] interface MediaKeys {
- MediaKeySession createSession (optional MediaKeySessionType sessionType = "temporary");
+ MediaKeySession createSession (optional MediaKeySessionType sessionType = "temporary");
Promise getStatusForPolicy (optional MediaKeysPolicy policy = {});
Promise setServerCertificate (BufferSource serverCertificate);
};
dictionary MediaKeysPolicy {
- HDCPVersion minHdcpVersion;
-};
-
-enum HDCPVersion {
- "1.0",
- "1.1",
- "1.2",
- "1.3",
- "1.4",
- "2.0",
- "2.1",
- "2.2",
- "2.3",
+ DOMString minHdcpVersion;
};
enum MediaKeySessionClosedReason {
- "internal-error",
- "closed-by-application",
- "release-acknowledged",
- "hardware-context-reset",
- "resource-evicted"
+ "internal-error",
+ "closed-by-application",
+ "release-acknowledged",
+ "hardware-context-reset",
+ "resource-evicted"
};
[Exposed=Window, SecureContext] interface MediaKeySession : EventTarget {
- readonly attribute DOMString sessionId;
- readonly attribute unrestricted double expiration;
- readonly attribute Promise closed;
- readonly attribute MediaKeyStatusMap keyStatuses;
- attribute EventHandler onkeystatuseschange;
- attribute EventHandler onmessage;
- Promise generateRequest (DOMString initDataType, BufferSource initData);
- Promise load (DOMString sessionId);
- Promise update (BufferSource response);
- Promise close ();
- Promise remove ();
+ readonly attribute DOMString sessionId;
+ readonly attribute unrestricted double expiration;
+ readonly attribute Promise closed;
+ readonly attribute MediaKeyStatusMap keyStatuses;
+ attribute EventHandler onkeystatuseschange;
+ attribute EventHandler onmessage;
+ Promise generateRequest (DOMString initDataType, BufferSource initData);
+ Promise load (DOMString sessionId);
+ Promise update (BufferSource response);
+ Promise close ();
+ Promise remove ();
};
[Exposed=Window, SecureContext] interface MediaKeyStatusMap {
- iterable;
- readonly attribute unsigned long size;
- boolean has (BufferSource keyId);
- (MediaKeyStatus or undefined) get (BufferSource keyId);
+ iterable;
+ readonly attribute unsigned long size;
+ boolean has (BufferSource keyId);
+ (MediaKeyStatus or undefined) get (BufferSource keyId);
};
enum MediaKeyStatus {
- "usable",
- "expired",
- "released",
- "output-restricted",
- "output-downscaled",
- "usable-in-future",
- "status-pending",
- "internal-error"
+ "usable",
+ "expired",
+ "released",
+ "output-restricted",
+ "output-downscaled",
+ "usable-in-future",
+ "status-pending",
+ "internal-error"
};
enum MediaKeyMessageType {
- "license-request",
- "license-renewal",
- "license-release",
- "individualization-request"
+ "license-request",
+ "license-renewal",
+ "license-release",
+ "individualization-request"
};
[Exposed=Window, SecureContext]
interface MediaKeyMessageEvent : Event {
- constructor(DOMString type, MediaKeyMessageEventInit eventInitDict);
- readonly attribute MediaKeyMessageType messageType;
- readonly attribute ArrayBuffer message;
+ constructor(DOMString type, MediaKeyMessageEventInit eventInitDict);
+ readonly attribute MediaKeyMessageType messageType;
+ readonly attribute ArrayBuffer message;
};
dictionary MediaKeyMessageEventInit : EventInit {
- required MediaKeyMessageType messageType;
- required ArrayBuffer message;
+ required MediaKeyMessageType messageType;
+ required ArrayBuffer message;
};
[Exposed=Window] partial interface HTMLMediaElement {
- [SecureContext] readonly attribute MediaKeys? mediaKeys;
- attribute EventHandler onencrypted;
- attribute EventHandler onwaitingforkey;
- [SecureContext] Promise setMediaKeys (MediaKeys? mediaKeys);
+ [SecureContext] readonly attribute MediaKeys? mediaKeys;
+ attribute EventHandler onencrypted;
+ attribute EventHandler onwaitingforkey;
+ [SecureContext] Promise setMediaKeys (MediaKeys? mediaKeys);
};
[Exposed=Window]
@@ -137,6 +127,6 @@ interface MediaEncryptedEvent : Event {
};
dictionary MediaEncryptedEventInit : EventInit {
- DOMString initDataType = "";
- ArrayBuffer? initData = null;
+ DOMString initDataType = "";
+ ArrayBuffer? initData = null;
};
diff --git a/test/fixtures/wpt/interfaces/fenced-frame.idl b/test/fixtures/wpt/interfaces/fenced-frame.idl
index 2107655aa92..e15cbf11da2 100644
--- a/test/fixtures/wpt/interfaces/fenced-frame.idl
+++ b/test/fixtures/wpt/interfaces/fenced-frame.idl
@@ -20,6 +20,8 @@ typedef USVString FencedFrameConfigURL;
[Exposed=Window, Serializable]
interface FencedFrameConfig {
+ constructor(USVString url);
+
readonly attribute FencedFrameConfigSize? containerWidth;
readonly attribute FencedFrameConfigSize? containerHeight;
readonly attribute FencedFrameConfigSize? contentWidth;
@@ -33,6 +35,9 @@ typedef (USVString or FencedFrameConfig) UrnOrConfig;
partial interface Navigator {
Promise deprecatedReplaceInURN(
UrnOrConfig urnOrConfig, record replacements);
+ Promise deprecatedURNtoURL(
+ UrnOrConfig urnOrConfig, optional boolean send_reports = false);
+ sequence adAuctionComponents(unsigned short numAdComponents);
};
enum FenceReportingDestination {
@@ -53,10 +58,17 @@ dictionary FenceEvent {
DOMString eventData;
sequence destination;
+ // Determines if this data can be sent in a reportEvent() beacon or automatic
+ // beacon that originates from a document that is cross-origin to the mapped
+ // URL of the fenced frame config that loaded this frame tree.
+ // Note that automatic beacon data can only be set from documents that are
+ // same-origin to the fenced frame config’s mapped URL, so this effectively
+ // opts in the data to being used in a cross-origin subframe.
+ boolean crossOriginExposed = false;
+
// When setting event data to be used later in an automatic beacon, the
// following properties are used:
boolean once = false;
- boolean crossOriginExposed = false;
// When reporting to a custom destination URL (with substitution of macros defined by
// the Protected Audience buyer), the following property is used:
diff --git a/test/fixtures/wpt/interfaces/fetch.idl b/test/fixtures/wpt/interfaces/fetch.idl
index 5038aeba6cd..965a82d13f5 100644
--- a/test/fixtures/wpt/interfaces/fetch.idl
+++ b/test/fixtures/wpt/interfaces/fetch.idl
@@ -26,6 +26,7 @@ interface mixin Body {
readonly attribute boolean bodyUsed;
[NewObject] Promise arrayBuffer();
[NewObject] Promise blob();
+ [NewObject] Promise bytes();
[NewObject] Promise formData();
[NewObject] Promise json();
[NewObject] Promise text();
diff --git a/test/fixtures/wpt/interfaces/geolocation.idl b/test/fixtures/wpt/interfaces/geolocation.idl
index 8c0acfc6cc1..062a38bebc5 100644
--- a/test/fixtures/wpt/interfaces/geolocation.idl
+++ b/test/fixtures/wpt/interfaces/geolocation.idl
@@ -1,7 +1,7 @@
// GENERATED CONTENT - DO NOT EDIT
// Content was automatically extracted by Reffy into webref
// (https://github.com/w3c/webref)
-// Source: Geolocation API (https://w3c.github.io/geolocation-api/)
+// Source: Geolocation (https://w3c.github.io/geolocation/)
partial interface Navigator {
[SameObject] readonly attribute Geolocation geolocation;
diff --git a/test/fixtures/wpt/interfaces/handwriting-recognition.idl b/test/fixtures/wpt/interfaces/handwriting-recognition.idl
new file mode 100644
index 00000000000..2bac6b5d9ca
--- /dev/null
+++ b/test/fixtures/wpt/interfaces/handwriting-recognition.idl
@@ -0,0 +1,100 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content was automatically extracted by Reffy into webref
+// (https://github.com/w3c/webref)
+// Source: Handwriting Recognition API (https://wicg.github.io/handwriting-recognition/)
+
+[SecureContext]
+partial interface Navigator {
+ Promise
+ queryHandwritingRecognizer(HandwritingModelConstraint constraint);
+};
+
+dictionary HandwritingModelConstraint {
+ required sequence languages;
+};
+
+dictionary HandwritingRecognizerQueryResult {
+ boolean textAlternatives;
+ boolean textSegmentation;
+ HandwritingHintsQueryResult hints;
+};
+
+dictionary HandwritingHintsQueryResult {
+ sequence recognitionType;
+ sequence inputType;
+ boolean textContext;
+ boolean alternatives;
+};
+
+enum HandwritingRecognitionType{
+ "text", "per-character"
+};
+
+enum HandwritingInputType {
+ "mouse", "stylus", "touch"
+};
+
+[SecureContext]
+partial interface Navigator {
+ Promise
+ createHandwritingRecognizer(HandwritingModelConstraint constraint);
+};
+
+[Exposed=Window, SecureContext]
+interface HandwritingRecognizer {
+ HandwritingDrawing startDrawing(optional HandwritingHints hints = {});
+
+ undefined finish();
+};
+
+dictionary HandwritingHints {
+ DOMString recognitionType = "text";
+ DOMString inputType = "mouse";
+ DOMString textContext;
+ unsigned long alternatives = 3;
+};
+
+[Exposed=Window, SecureContext]
+interface HandwritingDrawing {
+ undefined addStroke(HandwritingStroke stroke);
+ undefined removeStroke(HandwritingStroke stroke);
+ undefined clear();
+ sequence getStrokes();
+
+ Promise> getPrediction();
+};
+
+[SecureContext, Exposed=Window]
+interface HandwritingStroke {
+ constructor();
+ undefined addPoint(HandwritingPoint point);
+ sequence getPoints();
+ undefined clear();
+};
+
+dictionary HandwritingPoint {
+ required double x;
+ required double y;
+
+ // Optional. Number of milliseconds since a reference time point for a
+ // drawing.
+ DOMHighResTimeStamp t;
+};
+
+dictionary HandwritingPrediction {
+ required DOMString text;
+ sequence segmentationResult;
+};
+
+dictionary HandwritingSegment {
+ required DOMString grapheme;
+ required unsigned long beginIndex;
+ required unsigned long endIndex;
+ required sequence drawingSegments;
+};
+
+dictionary HandwritingDrawingSegment {
+ required unsigned long strokeIndex;
+ required unsigned long beginPointIndex;
+ required unsigned long endPointIndex;
+};
diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl
index aad8994b87d..f7ad9ac2dde 100644
--- a/test/fixtures/wpt/interfaces/html.idl
+++ b/test/fixtures/wpt/interfaces/html.idl
@@ -48,7 +48,7 @@ typedef (HTMLScriptElement or SVGScriptElement) HTMLOrSVGScriptElement;
[LegacyOverrideBuiltIns]
partial interface Document {
- static Document parseHTMLUnsafe(HTMLString html);
+ static Document parseHTMLUnsafe((TrustedHTML or DOMString) html);
// resource metadata management
[PutForwards=href, LegacyUnforgeable] readonly attribute Location? location;
@@ -77,8 +77,8 @@ partial interface Document {
[CEReactions] Document open(optional DOMString unused1, optional DOMString unused2); // both arguments are ignored
WindowProxy? open(USVString url, DOMString name, DOMString features);
[CEReactions] undefined close();
- [CEReactions] undefined write(HTMLString... text);
- [CEReactions] undefined writeln(HTMLString... text);
+ [CEReactions] undefined write((TrustedHTML or DOMString)... text);
+ [CEReactions] undefined writeln((TrustedHTML or DOMString)... text);
// user interaction
readonly attribute WindowProxy? defaultView;
@@ -452,7 +452,7 @@ interface HTMLIFrameElement : HTMLElement {
[HTMLConstructor] constructor();
[CEReactions] attribute USVString src;
- [CEReactions] attribute HTMLString srcdoc;
+ [CEReactions] attribute (TrustedHTML or DOMString) srcdoc;
[CEReactions] attribute DOMString name;
[SameObject, PutForwards=value] readonly attribute DOMTokenList sandbox;
[CEReactions] attribute DOMString allow;
@@ -1411,8 +1411,6 @@ interface mixin CanvasDrawPath {
interface mixin CanvasUserInterface {
undefined drawFocusIfNeeded(Element element);
undefined drawFocusIfNeeded(Path2D path, Element element);
- undefined scrollPathIntoView();
- undefined scrollPathIntoView(Path2D path);
};
interface mixin CanvasText {
@@ -2260,7 +2258,7 @@ interface mixin WindowEventHandlers {
attribute EventHandler onunload;
};
-typedef (DOMString or Function) TimerHandler;
+typedef (DOMString or Function or TrustedScript) TimerHandler;
interface mixin WindowOrWorkerGlobalScope {
[Replaceable] readonly attribute USVString origin;
@@ -2293,19 +2291,19 @@ Window includes WindowOrWorkerGlobalScope;
WorkerGlobalScope includes WindowOrWorkerGlobalScope;
partial interface Element {
- [CEReactions] undefined setHTMLUnsafe(HTMLString html);
+ [CEReactions] undefined setHTMLUnsafe((TrustedHTML or DOMString) html);
DOMString getHTML(optional GetHTMLOptions options = {});
- [CEReactions] attribute [LegacyNullToEmptyString] HTMLString innerHTML;
- [CEReactions] attribute [LegacyNullToEmptyString] HTMLString outerHTML;
- [CEReactions] undefined insertAdjacentHTML(DOMString position, HTMLString string);
+ [CEReactions] attribute (TrustedHTML or [LegacyNullToEmptyString] DOMString) innerHTML;
+ [CEReactions] attribute (TrustedHTML or [LegacyNullToEmptyString] DOMString) outerHTML;
+ [CEReactions] undefined insertAdjacentHTML(DOMString position, (TrustedHTML or DOMString) string);
};
partial interface ShadowRoot {
- [CEReactions] undefined setHTMLUnsafe(HTMLString html);
+ [CEReactions] undefined setHTMLUnsafe((TrustedHTML or DOMString) html);
DOMString getHTML(optional GetHTMLOptions options = {});
- [CEReactions] attribute [LegacyNullToEmptyString] HTMLString innerHTML;
+ [CEReactions] attribute (TrustedHTML or [LegacyNullToEmptyString] DOMString) innerHTML;
};
dictionary GetHTMLOptions {
@@ -2317,7 +2315,7 @@ dictionary GetHTMLOptions {
interface DOMParser {
constructor();
- [NewObject] Document parseFromString(HTMLString string, DOMParserSupportedType type);
+ [NewObject] Document parseFromString((TrustedHTML or DOMString) string, DOMParserSupportedType type);
};
enum DOMParserSupportedType {
@@ -2329,7 +2327,7 @@ enum DOMParserSupportedType {
};
partial interface Range {
- [CEReactions, NewObject] DocumentFragment createContextualFragment(HTMLString string);
+ [CEReactions, NewObject] DocumentFragment createContextualFragment((TrustedHTML or DOMString) string);
};
[Exposed=Window]
@@ -2544,7 +2542,7 @@ interface WorkerGlobalScope : EventTarget {
readonly attribute WorkerGlobalScope self;
readonly attribute WorkerLocation location;
readonly attribute WorkerNavigator navigator;
- undefined importScripts(ScriptURLString... urls);
+ undefined importScripts((TrustedScriptURL or USVString)... urls);
attribute OnErrorEventHandler onerror;
attribute EventHandler onlanguagechange;
@@ -2582,7 +2580,7 @@ interface mixin AbstractWorker {
[Exposed=(Window,DedicatedWorker,SharedWorker)]
interface Worker : EventTarget {
- constructor(ScriptURLString scriptURL, optional WorkerOptions options = {});
+ constructor((TrustedScriptURL or USVString) scriptURL, optional WorkerOptions options = {});
undefined terminate();
@@ -2604,7 +2602,7 @@ Worker includes AbstractWorker;
[Exposed=Window]
interface SharedWorker : EventTarget {
- constructor(ScriptURLString scriptURL, optional (DOMString or WorkerOptions) options = {});
+ constructor((TrustedScriptURL or USVString) scriptURL, optional (DOMString or WorkerOptions) options = {});
readonly attribute MessagePort port;
};
diff --git a/test/fixtures/wpt/interfaces/intersection-observer.idl b/test/fixtures/wpt/interfaces/intersection-observer.idl
index 8502a11357f..eb7f18f5213 100644
--- a/test/fixtures/wpt/interfaces/intersection-observer.idl
+++ b/test/fixtures/wpt/interfaces/intersection-observer.idl
@@ -12,6 +12,8 @@ interface IntersectionObserver {
readonly attribute DOMString rootMargin;
readonly attribute DOMString scrollMargin;
readonly attribute FrozenArray thresholds;
+ readonly attribute long delay;
+ readonly attribute boolean trackVisibility;
undefined observe(Element target);
undefined unobserve(Element target);
undefined disconnect();
@@ -26,6 +28,7 @@ interface IntersectionObserverEntry {
readonly attribute DOMRectReadOnly boundingClientRect;
readonly attribute DOMRectReadOnly intersectionRect;
readonly attribute boolean isIntersecting;
+ readonly attribute boolean isVisible;
readonly attribute double intersectionRatio;
readonly attribute Element target;
};
@@ -36,6 +39,7 @@ dictionary IntersectionObserverEntryInit {
required DOMRectInit boundingClientRect;
required DOMRectInit intersectionRect;
required boolean isIntersecting;
+ required boolean isVisible;
required double intersectionRatio;
required Element target;
};
@@ -45,4 +49,6 @@ dictionary IntersectionObserverInit {
DOMString rootMargin = "0px";
DOMString scrollMargin = "0px";
(double or sequence) threshold = 0;
+ long delay = 0;
+ boolean trackVisibility = false;
};
diff --git a/test/fixtures/wpt/interfaces/invokers.tentative.idl b/test/fixtures/wpt/interfaces/invokers.tentative.idl
index eb1b8247f06..4724d7deb08 100644
--- a/test/fixtures/wpt/interfaces/invokers.tentative.idl
+++ b/test/fixtures/wpt/interfaces/invokers.tentative.idl
@@ -1,15 +1,15 @@
interface mixin InvokerElement {
- [CEReactions,Reflect=invoketarget] attribute Element? invokeTargetElement;
- [CEReactions,Reflect=invokeaction] attribute DOMString invokeAction;
+ [CEReactions,Reflect=invoketarget] attribute Element? commandForElement;
+ [CEReactions,Reflect=invokeaction] attribute DOMString command;
};
-interface InvokeEvent : Event {
- constructor(DOMString type, optional InvokeEventInit eventInitDict = {});
+interface CommandEvent : Event {
+ constructor(DOMString type, optional CommandEventInit eventInitDict = {});
readonly attribute Element? invoker;
- readonly attribute DOMString action;
+ readonly attribute DOMString command;
};
-dictionary InvokeEventInit : EventInit {
+dictionary CommandEventInit : EventInit {
Element? invoker = null;
- DOMString action = "";
+ DOMString command = "";
};
diff --git a/test/fixtures/wpt/interfaces/mediacapture-streams.idl b/test/fixtures/wpt/interfaces/mediacapture-streams.idl
index f2ca21389e9..f6c8e2b82da 100644
--- a/test/fixtures/wpt/interfaces/mediacapture-streams.idl
+++ b/test/fixtures/wpt/interfaces/mediacapture-streams.idl
@@ -61,6 +61,7 @@ dictionary MediaTrackSupportedConstraints {
boolean channelCount = true;
boolean deviceId = true;
boolean groupId = true;
+ boolean backgroundBlur = true;
};
dictionary MediaTrackCapabilities {
@@ -79,6 +80,7 @@ dictionary MediaTrackCapabilities {
ULongRange channelCount;
DOMString deviceId;
DOMString groupId;
+ sequence backgroundBlur;
};
dictionary MediaTrackConstraints : MediaTrackConstraintSet {
@@ -101,6 +103,7 @@ dictionary MediaTrackConstraintSet {
ConstrainULong channelCount;
ConstrainDOMString deviceId;
ConstrainDOMString groupId;
+ ConstrainBoolean backgroundBlur;
};
dictionary MediaTrackSettings {
@@ -119,6 +122,7 @@ dictionary MediaTrackSettings {
unsigned long channelCount;
DOMString deviceId;
DOMString groupId;
+ boolean backgroundBlur;
};
enum VideoFacingModeEnum {
@@ -179,6 +183,17 @@ interface InputDeviceInfo : MediaDeviceInfo {
MediaTrackCapabilities getCapabilities();
};
+[Exposed=Window]
+interface DeviceChangeEvent : Event {
+ constructor(DOMString type, optional DeviceChangeEventInit eventInitDict = {});
+ [SameObject] readonly attribute FrozenArray devices;
+ [SameObject] readonly attribute FrozenArray userInsertedDevices;
+};
+
+dictionary DeviceChangeEventInit : EventInit {
+ sequence devices = [];
+};
+
partial interface MediaDevices {
MediaTrackSupportedConstraints getSupportedConstraints();
Promise getUserMedia(optional MediaStreamConstraints constraints = {});
diff --git a/test/fixtures/wpt/interfaces/mediacapture-transform.idl b/test/fixtures/wpt/interfaces/mediacapture-transform.idl
index 5b2c8fa67a6..1ce35452f0c 100644
--- a/test/fixtures/wpt/interfaces/mediacapture-transform.idl
+++ b/test/fixtures/wpt/interfaces/mediacapture-transform.idl
@@ -6,7 +6,7 @@
[Exposed=DedicatedWorker]
interface MediaStreamTrackProcessor {
constructor(MediaStreamTrackProcessorInit init);
- attribute ReadableStream readable;
+ readonly attribute ReadableStream readable;
};
dictionary MediaStreamTrackProcessorInit {
diff --git a/test/fixtures/wpt/interfaces/mediasession.idl b/test/fixtures/wpt/interfaces/mediasession.idl
index e6c8e464627..00bfe6ad216 100644
--- a/test/fixtures/wpt/interfaces/mediasession.idl
+++ b/test/fixtures/wpt/interfaces/mediasession.idl
@@ -26,10 +26,12 @@ enum MediaSessionAction {
"seekto",
"togglemicrophone",
"togglecamera",
+ "togglescreenshare",
"hangup",
"previousslide",
"nextslide",
- "enterpictureinpicture"
+ "enterpictureinpicture",
+ "voiceactivity"
};
callback MediaSessionActionHandler = undefined(MediaSessionActionDetails details);
@@ -47,6 +49,8 @@ interface MediaSession {
Promise setMicrophoneActive(boolean active);
Promise setCameraActive(boolean active);
+
+ Promise setScreenshareActive(boolean active);
};
[Exposed=Window]
diff --git a/test/fixtures/wpt/interfaces/orientation-event.idl b/test/fixtures/wpt/interfaces/orientation-event.idl
index ffacfe576f2..3683016d034 100644
--- a/test/fixtures/wpt/interfaces/orientation-event.idl
+++ b/test/fixtures/wpt/interfaces/orientation-event.idl
@@ -1,7 +1,7 @@
// GENERATED CONTENT - DO NOT EDIT
// Content was automatically extracted by Reffy into webref
// (https://github.com/w3c/webref)
-// Source: DeviceOrientation Event Specification (https://w3c.github.io/deviceorientation/)
+// Source: Device Orientation and Motion (https://w3c.github.io/deviceorientation/)
partial interface Window {
[SecureContext] attribute EventHandler ondeviceorientation;
diff --git a/test/fixtures/wpt/interfaces/payment-handler.idl b/test/fixtures/wpt/interfaces/payment-handler.idl
index 91c01297f40..8aa55e9e950 100644
--- a/test/fixtures/wpt/interfaces/payment-handler.idl
+++ b/test/fixtures/wpt/interfaces/payment-handler.idl
@@ -94,38 +94,3 @@ dictionary AddressInit {
DOMString recipient = "";
DOMString phone = "";
};
-
-dictionary PaymentOptions {
- boolean requestPayerName = false;
- boolean requestBillingAddress = false;
- boolean requestPayerEmail = false;
- boolean requestPayerPhone = false;
- boolean requestShipping = false;
- PaymentShippingType shippingType = "shipping";
-};
-
-dictionary PaymentShippingOption {
- required DOMString id;
- required DOMString label;
- required PaymentCurrencyAmount amount;
- boolean selected = false;
-};
-
-enum PaymentShippingType {
- "shipping",
- "delivery",
- "pickup"
-};
-
-dictionary AddressErrors {
- DOMString addressLine;
- DOMString city;
- DOMString country;
- DOMString dependentLocality;
- DOMString organization;
- DOMString phone;
- DOMString postalCode;
- DOMString recipient;
- DOMString region;
- DOMString sortingCode;
-};
diff --git a/test/fixtures/wpt/interfaces/payment-request.idl b/test/fixtures/wpt/interfaces/payment-request.idl
index 0a97d4d75c1..c1227d59c6b 100644
--- a/test/fixtures/wpt/interfaces/payment-request.idl
+++ b/test/fixtures/wpt/interfaces/payment-request.idl
@@ -1,13 +1,14 @@
// GENERATED CONTENT - DO NOT EDIT
// Content was automatically extracted by Reffy into webref
// (https://github.com/w3c/webref)
-// Source: Payment Request API 1.1 (https://w3c.github.io/payment-request/)
+// Source: Payment Request API (https://w3c.github.io/payment-request/)
[SecureContext, Exposed=Window]
interface PaymentRequest : EventTarget {
constructor(
sequence methodData,
- PaymentDetailsInit details
+ PaymentDetailsInit details,
+ optional PaymentOptions options = {}
);
[NewObject]
Promise show(optional Promise detailsPromise);
@@ -17,7 +18,12 @@ interface PaymentRequest : EventTarget {
Promise canMakePayment();
readonly attribute DOMString id;
+ readonly attribute ContactAddress? shippingAddress;
+ readonly attribute DOMString? shippingOption;
+ readonly attribute PaymentShippingType? shippingType;
+ attribute EventHandler onshippingaddresschange;
+ attribute EventHandler onshippingoptionchange;
attribute EventHandler onpaymentmethodchange;
};
@@ -33,6 +39,7 @@ dictionary PaymentCurrencyAmount {
dictionary PaymentDetailsBase {
sequence displayItems;
+ sequence shippingOptions;
sequence modifiers;
};
@@ -42,7 +49,10 @@ dictionary PaymentDetailsInit : PaymentDetailsBase {
};
dictionary PaymentDetailsUpdate : PaymentDetailsBase {
+ DOMString error;
PaymentItem total;
+ AddressErrors shippingAddressErrors;
+ PayerErrors payerErrors;
object paymentMethodErrors;
};
@@ -53,6 +63,21 @@ dictionary PaymentDetailsModifier {
object data;
};
+enum PaymentShippingType {
+ "shipping",
+ "delivery",
+ "pickup"
+};
+
+dictionary PaymentOptions {
+ boolean requestPayerName = false;
+ boolean requestBillingAddress = false;
+ boolean requestPayerEmail = false;
+ boolean requestPayerPhone = false;
+ boolean requestShipping = false;
+ PaymentShippingType shippingType = "shipping";
+};
+
dictionary PaymentItem {
required DOMString label;
required PaymentCurrencyAmount amount;
@@ -69,6 +94,13 @@ enum PaymentComplete {
"unknown"
};
+dictionary PaymentShippingOption {
+ required DOMString id;
+ required DOMString label;
+ required PaymentCurrencyAmount amount;
+ boolean selected = false;
+};
+
[SecureContext, Exposed=Window]
interface PaymentResponse : EventTarget {
[Default] object toJSON();
@@ -76,6 +108,11 @@ interface PaymentResponse : EventTarget {
readonly attribute DOMString requestId;
readonly attribute DOMString methodName;
readonly attribute object details;
+ readonly attribute ContactAddress? shippingAddress;
+ readonly attribute DOMString? shippingOption;
+ readonly attribute DOMString? payerName;
+ readonly attribute DOMString? payerEmail;
+ readonly attribute DOMString? payerPhone;
[NewObject]
Promise complete(
@@ -84,13 +121,36 @@ interface PaymentResponse : EventTarget {
);
[NewObject]
Promise retry(optional PaymentValidationErrors errorFields = {});
+
+ attribute EventHandler onpayerdetailchange;
};
dictionary PaymentValidationErrors {
+ PayerErrors payer;
+ AddressErrors shippingAddress;
DOMString error;
object paymentMethod;
};
+dictionary PayerErrors {
+ DOMString email;
+ DOMString name;
+ DOMString phone;
+};
+
+dictionary AddressErrors {
+ DOMString addressLine;
+ DOMString city;
+ DOMString country;
+ DOMString dependentLocality;
+ DOMString organization;
+ DOMString phone;
+ DOMString postalCode;
+ DOMString recipient;
+ DOMString region;
+ DOMString sortingCode;
+};
+
[SecureContext, Exposed=Window]
interface PaymentMethodChangeEvent : PaymentRequestUpdateEvent {
constructor(DOMString type, optional PaymentMethodChangeEventInit eventInitDict = {});
diff --git a/test/fixtures/wpt/interfaces/permissions-policy.idl b/test/fixtures/wpt/interfaces/permissions-policy.idl
index 16945e3a9b7..5878d8d150a 100644
--- a/test/fixtures/wpt/interfaces/permissions-policy.idl
+++ b/test/fixtures/wpt/interfaces/permissions-policy.idl
@@ -21,6 +21,7 @@ partial interface HTMLIFrameElement {
[Exposed=Window]
interface PermissionsPolicyViolationReportBody : ReportBody {
+ [Default] object toJSON();
readonly attribute DOMString featureId;
readonly attribute DOMString? sourceFile;
readonly attribute long? lineNumber;
diff --git a/test/fixtures/wpt/interfaces/pointerevents.idl b/test/fixtures/wpt/interfaces/pointerevents.idl
index 4ecb290ed41..0356e845058 100644
--- a/test/fixtures/wpt/interfaces/pointerevents.idl
+++ b/test/fixtures/wpt/interfaces/pointerevents.idl
@@ -16,6 +16,7 @@ dictionary PointerEventInit : MouseEventInit {
double azimuthAngle;
DOMString pointerType = "";
boolean isPrimary = false;
+ long persistentDeviceId = 0;
sequence coalescedEvents = [];
sequence predictedEvents = [];
};
@@ -35,6 +36,7 @@ interface PointerEvent : MouseEvent {
readonly attribute double azimuthAngle;
readonly attribute DOMString pointerType;
readonly attribute boolean isPrimary;
+ readonly attribute long persistentDeviceId;
[SecureContext] sequence getCoalescedEvents();
sequence getPredictedEvents();
};
diff --git a/test/fixtures/wpt/interfaces/pointerlock.idl b/test/fixtures/wpt/interfaces/pointerlock.idl
index 0204bf53424..afe19cc9ee6 100644
--- a/test/fixtures/wpt/interfaces/pointerlock.idl
+++ b/test/fixtures/wpt/interfaces/pointerlock.idl
@@ -3,8 +3,12 @@
// (https://github.com/w3c/webref)
// Source: Pointer Lock 2.0 (https://w3c.github.io/pointerlock/)
+dictionary PointerLockOptions {
+ boolean unadjustedMovement = false;
+};
+
partial interface Element {
- undefined requestPointerLock();
+ Promise requestPointerLock(optional PointerLockOptions options = {});
};
partial interface Document {
@@ -14,7 +18,7 @@ partial interface Document {
};
partial interface mixin DocumentOrShadowRoot {
- readonly attribute Element ? pointerLockElement;
+ readonly attribute Element? pointerLockElement;
};
partial interface MouseEvent {
diff --git a/test/fixtures/wpt/interfaces/push-api.idl b/test/fixtures/wpt/interfaces/push-api.idl
index f582788806c..b16a730b722 100644
--- a/test/fixtures/wpt/interfaces/push-api.idl
+++ b/test/fixtures/wpt/interfaces/push-api.idl
@@ -58,6 +58,7 @@ enum PushEncryptionKeyName {
interface PushMessageData {
ArrayBuffer arrayBuffer();
Blob blob();
+ Uint8Array bytes();
any json();
USVString text();
};
diff --git a/test/fixtures/wpt/interfaces/saa-non-cookie-storage.idl b/test/fixtures/wpt/interfaces/saa-non-cookie-storage.idl
new file mode 100644
index 00000000000..6cc0a7887be
--- /dev/null
+++ b/test/fixtures/wpt/interfaces/saa-non-cookie-storage.idl
@@ -0,0 +1,45 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content was automatically extracted by Reffy into webref
+// (https://github.com/w3c/webref)
+// Source: Extending Storage Access API (SAA) to non-cookie storage (https://privacycg.github.io/saa-non-cookie-storage/)
+
+dictionary StorageAccessTypes {
+ boolean all = false;
+ boolean cookies = false;
+ boolean sessionStorage = false;
+ boolean localStorage = false;
+ boolean indexedDB = false;
+ boolean locks = false;
+ boolean caches = false;
+ boolean getDirectory = false;
+ boolean estimate = false;
+ boolean createObjectURL = false;
+ boolean revokeObjectURL = false;
+ boolean BroadcastChannel = false;
+ boolean SharedWorker = false;
+};
+
+[Exposed=Window]
+interface StorageAccessHandle {
+ readonly attribute Storage sessionStorage;
+ readonly attribute Storage localStorage;
+ readonly attribute IDBFactory indexedDB;
+ readonly attribute LockManager locks;
+ readonly attribute CacheStorage caches;
+ Promise getDirectory();
+ Promise estimate();
+ DOMString createObjectURL((Blob or MediaSource) obj);
+ undefined revokeObjectURL(DOMString url);
+ BroadcastChannel BroadcastChannel(DOMString name);
+ SharedWorker SharedWorker(USVString scriptURL, optional (DOMString or SharedWorkerOptions) options = {});
+};
+
+partial interface Document {
+ Promise hasUnpartitionedCookieAccess();
+};
+
+enum SameSiteCookiesType { "all", "none" };
+
+dictionary SharedWorkerOptions : WorkerOptions {
+ SameSiteCookiesType sameSiteCookies;
+};
diff --git a/test/fixtures/wpt/interfaces/scheduling-apis.idl b/test/fixtures/wpt/interfaces/scheduling-apis.idl
index 1e84e79cd15..6f93db15a74 100644
--- a/test/fixtures/wpt/interfaces/scheduling-apis.idl
+++ b/test/fixtures/wpt/interfaces/scheduling-apis.idl
@@ -21,6 +21,7 @@ callback SchedulerPostTaskCallback = any ();
interface Scheduler {
Promise postTask(SchedulerPostTaskCallback callback,
optional SchedulerPostTaskOptions options = {});
+ Promise yield();
};
[Exposed=(Window, Worker)]
diff --git a/test/fixtures/wpt/interfaces/service-workers.idl b/test/fixtures/wpt/interfaces/service-workers.idl
index 1ddc6d71d83..87d48398f72 100644
--- a/test/fixtures/wpt/interfaces/service-workers.idl
+++ b/test/fixtures/wpt/interfaces/service-workers.idl
@@ -60,7 +60,7 @@ interface ServiceWorkerContainer : EventTarget {
readonly attribute ServiceWorker? controller;
readonly attribute Promise ready;
- [NewObject] Promise register(USVString scriptURL, optional RegistrationOptions options = {});
+ [NewObject] Promise register((TrustedScriptURL or USVString) scriptURL, optional RegistrationOptions options = {});
[NewObject] Promise<(ServiceWorkerRegistration or undefined)> getRegistration(optional USVString clientURL = "");
[NewObject] Promise> getRegistrations();
diff --git a/test/fixtures/wpt/interfaces/shared-storage.idl b/test/fixtures/wpt/interfaces/shared-storage.idl
index c40344e74d2..7f4976b7b20 100644
--- a/test/fixtures/wpt/interfaces/shared-storage.idl
+++ b/test/fixtures/wpt/interfaces/shared-storage.idl
@@ -5,10 +5,12 @@
typedef (USVString or FencedFrameConfig) SharedStorageResponse;
+enum SharedStorageDataOrigin { "context-origin", "script-origin" };
+
[Exposed=(Window)]
interface SharedStorageWorklet : Worklet {
Promise selectURL(DOMString name,
- FrozenArray urls,
+ sequence urls,
optional SharedStorageRunOperationMethodOptions options = {});
Promise run(DOMString name,
optional SharedStorageRunOperationMethodOptions options = {});
@@ -47,12 +49,12 @@ dictionary SharedStorageSetMethodOptions {
[Exposed=(Window)]
interface WindowSharedStorage : SharedStorage {
Promise selectURL(DOMString name,
- FrozenArray urls,
+ sequence urls,
optional SharedStorageRunOperationMethodOptions options = {});
Promise run(DOMString name,
optional SharedStorageRunOperationMethodOptions options = {});
- Promise createWorklet(USVString moduleURL, optional WorkletOptions options = {});
+ Promise createWorklet(USVString moduleURL, optional SharedStorageWorkletOptions options = {});
readonly attribute SharedStorageWorklet worklet;
};
@@ -63,6 +65,10 @@ dictionary SharedStorageRunOperationMethodOptions {
boolean keepAlive = false;
};
+dictionary SharedStorageWorkletOptions : WorkletOptions {
+ SharedStorageDataOrigin dataOrigin = "context-origin";
+};
+
partial interface Window {
[SecureContext] readonly attribute WindowSharedStorage? sharedStorage;
};
diff --git a/test/fixtures/wpt/interfaces/touch-events.idl b/test/fixtures/wpt/interfaces/touch-events.idl
index 9844f085381..19f55156a6e 100644
--- a/test/fixtures/wpt/interfaces/touch-events.idl
+++ b/test/fixtures/wpt/interfaces/touch-events.idl
@@ -53,9 +53,9 @@ interface TouchList {
};
dictionary TouchEventInit : EventModifierInit {
- sequence touches = [];
- sequence targetTouches = [];
- sequence changedTouches = [];
+ sequence touches = [];
+ sequence targetTouches = [];
+ sequence changedTouches = [];
};
[Exposed=Window]
diff --git a/test/fixtures/wpt/interfaces/trusted-types.idl b/test/fixtures/wpt/interfaces/trusted-types.idl
index a0f88e4e6c3..b6008e18725 100644
--- a/test/fixtures/wpt/interfaces/trusted-types.idl
+++ b/test/fixtures/wpt/interfaces/trusted-types.idl
@@ -58,9 +58,6 @@ callback CreateHTMLCallback = DOMString? (DOMString input, any... arguments);
callback CreateScriptCallback = DOMString? (DOMString input, any... arguments);
callback CreateScriptURLCallback = USVString? (DOMString input, any... arguments);
-typedef [StringContext=TrustedHTML] DOMString HTMLString;
-typedef [StringContext=TrustedScript] DOMString ScriptString;
-typedef [StringContext=TrustedScriptURL] USVString ScriptURLString;
typedef (TrustedHTML or TrustedScript or TrustedScriptURL) TrustedType;
partial interface mixin WindowOrWorkerGlobalScope {
diff --git a/test/fixtures/wpt/interfaces/turtledove.idl b/test/fixtures/wpt/interfaces/turtledove.idl
index 39e90ddae19..8ed7903f0b1 100644
--- a/test/fixtures/wpt/interfaces/turtledove.idl
+++ b/test/fixtures/wpt/interfaces/turtledove.idl
@@ -20,7 +20,6 @@ dictionary AuctionAd {
dictionary GenerateBidInterestGroup {
required USVString owner;
required USVString name;
- required double lifetimeMs;
boolean enableBiddingSignalsPrioritization = false;
record priorityVector;
@@ -42,6 +41,7 @@ dictionary GenerateBidInterestGroup {
dictionary AuctionAdInterestGroup : GenerateBidInterestGroup {
double priority = 0.0;
record prioritySignalsOverrides;
+ required double lifetimeMs;
DOMString additionalBidKey;
};
@@ -64,6 +64,11 @@ partial interface Navigator {
[SecureContext]
partial interface Navigator {
Promise<(USVString or FencedFrameConfig)?> runAdAuction(AuctionAdConfig config);
+ readonly attribute boolean deprecatedRunAdAuctionEnforcesKAnonymity;
+};
+
+dictionary AuctionRealTimeReportingConfig {
+ required DOMString type;
};
dictionary AuctionAdConfig {
@@ -85,6 +90,7 @@ dictionary AuctionAdConfig {
unsigned long long reportingTimeout;
USVString sellerCurrency;
Promise> perBuyerCurrencies;
+ record perBuyerMultiBidLimits;
record perBuyerGroupLimits;
record perBuyerExperimentGroupIds;
record> perBuyerPrioritySignals;
@@ -93,11 +99,18 @@ dictionary AuctionAdConfig {
sequence> allSlotsRequestedSizes;
Promise additionalBids;
DOMString auctionNonce;
+ AuctionRealTimeReportingConfig sellerRealTimeReportingConfig;
+ record perBuyerRealTimeReportingConfig;
sequence componentAuctions = [];
AbortSignal? signal;
Promise resolveToConfig;
};
+[SecureContext]
+partial interface Navigator {
+ boolean canLoadAdAuctionFencedFrame();
+};
+
[SecureContext]
partial interface Navigator {
Promise createAuctionNonce();
@@ -113,11 +126,22 @@ interface ForDebuggingOnly {
undefined reportAdAuctionLoss(USVString url);
};
+[Exposed=InterestGroupBiddingAndScoringScriptRunnerGlobalScope]
+interface RealTimeReporting {
+ undefined contributeToHistogram(RealTimeContribution contribution);
+};
+
+dictionary RealTimeContribution {
+ required long bucket;
+ required double priorityWeight;
+ long latencyThreshold;
+};
+
[Exposed=InterestGroupBiddingAndScoringScriptRunnerGlobalScope,
Global=InterestGroupBiddingAndScoringScriptRunnerGlobalScope]
interface InterestGroupBiddingAndScoringScriptRunnerGlobalScope : InterestGroupScriptRunnerGlobalScope {
-
readonly attribute ForDebuggingOnly forDebuggingOnly;
+ readonly attribute RealTimeReporting realTimeReporting;
};
[Exposed=InterestGroupBiddingScriptRunnerGlobalScope,
@@ -125,7 +149,7 @@ interface InterestGroupBiddingAndScoringScriptRunnerGlobalScope : InterestGroupS
InterestGroupBiddingScriptRunnerGlobalScope)]
interface InterestGroupBiddingScriptRunnerGlobalScope
: InterestGroupBiddingAndScoringScriptRunnerGlobalScope {
- boolean setBid(optional GenerateBidOutput generateBidOutput = {});
+ boolean setBid(optional (GenerateBidOutput or sequence) oneOrManyBids = []);
undefined setPriority(double priority);
undefined setPrioritySignalsOverride(DOMString key, optional double? priority);
};
@@ -145,6 +169,8 @@ dictionary GenerateBidOutput {
double adCost;
unrestricted double modelingSignals;
boolean allowComponentAuction = false;
+ unsigned long targetNumAdComponents;
+ unsigned long numMandatoryAdComponents = 0;
};
[Exposed=InterestGroupScoringScriptRunnerGlobalScope,
@@ -199,11 +225,14 @@ dictionary BiddingBrowserSignals {
required long bidCount;
required long recency;
required long adComponentsLimit;
+ required unsigned short multiBidLimit;
+ record requestedSize;
USVString topLevelSeller;
sequence prevWinsMs;
object wasmHelper;
unsigned long dataVersion;
+ unsigned long crossOriginDataVersion;
boolean forDebuggingOnlyInCooldownOrLockout = false;
};
@@ -214,7 +243,9 @@ dictionary ScoringBrowserSignals {
required unsigned long biddingDurationMsec;
required DOMString bidCurrency;
+ record renderSize;
unsigned long dataVersion;
+ unsigned long crossOriginDataVersion;
sequence adComponents;
boolean forDebuggingOnlyInCooldownOrLockout = false;
};
diff --git a/test/fixtures/wpt/interfaces/webaudio.idl b/test/fixtures/wpt/interfaces/webaudio.idl
index 73dc35d2609..74aab7e2362 100644
--- a/test/fixtures/wpt/interfaces/webaudio.idl
+++ b/test/fixtures/wpt/interfaces/webaudio.idl
@@ -82,6 +82,7 @@ interface AudioContext : BaseAudioContext {
[SecureContext] readonly attribute (DOMString or AudioSinkInfo) sinkId;
[SecureContext] readonly attribute AudioRenderCapacity renderCapacity;
attribute EventHandler onsinkchange;
+ attribute EventHandler onerror;
AudioTimestamp getOutputTimestamp ();
Promise resume ();
Promise suspend ();
diff --git a/test/fixtures/wpt/interfaces/webauthn.idl b/test/fixtures/wpt/interfaces/webauthn.idl
index cf1a2fbdc48..10dd0f7e059 100644
--- a/test/fixtures/wpt/interfaces/webauthn.idl
+++ b/test/fixtures/wpt/interfaces/webauthn.idl
@@ -19,7 +19,7 @@ typedef DOMString Base64URLString;
typedef object PublicKeyCredentialJSON;
dictionary RegistrationResponseJSON {
- required Base64URLString id;
+ required DOMString id;
required Base64URLString rawId;
required AuthenticatorAttestationResponseJSON response;
DOMString authenticatorAttachment;
@@ -38,14 +38,14 @@ dictionary AuthenticatorAttestationResponseJSON {
// algorithm then the public key must be parsed directly from
// attestationObject or authenticatorData.
Base64URLString publicKey;
- required long long publicKeyAlgorithm;
+ required COSEAlgorithmIdentifier publicKeyAlgorithm;
// This value contains copies of some of the fields above. See
// section “Easily accessing credential data”.
required Base64URLString attestationObject;
};
dictionary AuthenticationResponseJSON {
- required Base64URLString id;
+ required DOMString id;
required Base64URLString rawId;
required AuthenticatorAssertionResponseJSON response;
DOMString authenticatorAttachment;
@@ -106,8 +106,8 @@ dictionary PublicKeyCredentialUserEntityJSON {
};
dictionary PublicKeyCredentialDescriptorJSON {
- required Base64URLString id;
required DOMString type;
+ required Base64URLString id;
sequence transports;
};
@@ -211,7 +211,7 @@ enum AttestationConveyancePreference {
dictionary PublicKeyCredentialRequestOptions {
required BufferSource challenge;
unsigned long timeout;
- USVString rpId;
+ DOMString rpId;
sequence allowCredentials = [];
DOMString userVerification = "preferred";
sequence hints = [];
@@ -228,8 +228,8 @@ dictionary CollectedClientData {
required DOMString type;
required DOMString challenge;
required DOMString origin;
- DOMString topOrigin;
boolean crossOrigin;
+ DOMString topOrigin;
};
dictionary TokenBinding {
@@ -268,10 +268,11 @@ enum UserVerificationRequirement {
enum ClientCapability {
"conditionalCreate",
- "conditionalMediation",
+ "conditionalGet",
"hybridTransport",
"passkeyPlatformAuthenticator",
"userVerifyingPlatformAuthenticator",
+ "relatedOrigins"
};
enum PublicKeyCredentialHints {
@@ -356,32 +357,3 @@ dictionary AuthenticationExtensionsLargeBlobOutputs {
ArrayBuffer blob;
boolean written;
};
-
-partial dictionary AuthenticationExtensionsClientInputs {
- boolean uvm;
-};
-
-typedef sequence UvmEntry;
-typedef sequence UvmEntries;
-
-partial dictionary AuthenticationExtensionsClientOutputs {
- UvmEntries uvm;
-};
-
-dictionary AuthenticationExtensionsSupplementalPubKeysInputs {
- required sequence scopes;
- DOMString attestation = "indirect";
- sequence attestationFormats = [];
-};
-
-partial dictionary AuthenticationExtensionsClientInputs {
- AuthenticationExtensionsSupplementalPubKeysInputs supplementalPubKeys;
-};
-
-dictionary AuthenticationExtensionsSupplementalPubKeysOutputs {
- required sequence signatures;
-};
-
-partial dictionary AuthenticationExtensionsClientOutputs {
- AuthenticationExtensionsSupplementalPubKeysOutputs supplementalPubKeys;
-};
diff --git a/test/fixtures/wpt/interfaces/webcodecs-av1-codec-registration.idl b/test/fixtures/wpt/interfaces/webcodecs-av1-codec-registration.idl
index ab20879728d..00e4493d3c0 100644
--- a/test/fixtures/wpt/interfaces/webcodecs-av1-codec-registration.idl
+++ b/test/fixtures/wpt/interfaces/webcodecs-av1-codec-registration.idl
@@ -3,14 +3,6 @@
// (https://github.com/w3c/webref)
// Source: AV1 WebCodecs Registration (https://w3c.github.io/webcodecs/av1_codec_registration.html)
-partial dictionary VideoEncoderConfig {
- AV1EncoderConfig av1;
-};
-
-dictionary AV1EncoderConfig {
- boolean forceScreenContentTools = false;
-};
-
partial dictionary VideoEncoderEncodeOptions {
VideoEncoderEncodeOptionsForAv1 av1;
};
diff --git a/test/fixtures/wpt/interfaces/webcodecs.idl b/test/fixtures/wpt/interfaces/webcodecs.idl
index c754b2b036c..52731257f1f 100644
--- a/test/fixtures/wpt/interfaces/webcodecs.idl
+++ b/test/fixtures/wpt/interfaces/webcodecs.idl
@@ -229,7 +229,7 @@ dictionary EncodedAudioChunkInit {
required EncodedAudioChunkType type;
[EnforceRange] required long long timestamp; // microseconds
[EnforceRange] unsigned long long duration; // microseconds
- required BufferSource data;
+ required AllowSharedBufferSource data;
sequence transfer = [];
};
@@ -488,7 +488,7 @@ interface ImageDecoder {
static Promise isTypeSupported(DOMString type);
};
-typedef (BufferSource or ReadableStream) ImageBufferSource;
+typedef (AllowSharedBufferSource or ReadableStream) ImageBufferSource;
dictionary ImageDecoderInit {
required DOMString type;
required ImageBufferSource data;
diff --git a/test/fixtures/wpt/interfaces/webgl1.idl b/test/fixtures/wpt/interfaces/webgl1.idl
index 655c294fc1e..c345b142721 100644
--- a/test/fixtures/wpt/interfaces/webgl1.idl
+++ b/test/fixtures/wpt/interfaces/webgl1.idl
@@ -561,12 +561,12 @@ interface mixin WebGLRenderingContextBase
undefined copyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
GLint x, GLint y, GLsizei width, GLsizei height);
- WebGLBuffer? createBuffer();
- WebGLFramebuffer? createFramebuffer();
- WebGLProgram? createProgram();
- WebGLRenderbuffer? createRenderbuffer();
+ WebGLBuffer createBuffer();
+ WebGLFramebuffer createFramebuffer();
+ WebGLProgram createProgram();
+ WebGLRenderbuffer createRenderbuffer();
WebGLShader? createShader(GLenum type);
- WebGLTexture? createTexture();
+ WebGLTexture createTexture();
undefined cullFace(GLenum mode);
diff --git a/test/fixtures/wpt/interfaces/webgl2.idl b/test/fixtures/wpt/interfaces/webgl2.idl
index 25c2b4dad28..92ff70b3f86 100644
--- a/test/fixtures/wpt/interfaces/webgl2.idl
+++ b/test/fixtures/wpt/interfaces/webgl2.idl
@@ -423,7 +423,7 @@ interface mixin WebGL2RenderingContextBase
undefined clearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
/* Query Objects */
- WebGLQuery? createQuery();
+ WebGLQuery createQuery();
undefined deleteQuery(WebGLQuery? query);
[WebGLHandlesContextLoss] GLboolean isQuery(WebGLQuery? query);
undefined beginQuery(GLenum target, WebGLQuery query);
@@ -432,7 +432,7 @@ interface mixin WebGL2RenderingContextBase
any getQueryParameter(WebGLQuery query, GLenum pname);
/* Sampler Objects */
- WebGLSampler? createSampler();
+ WebGLSampler createSampler();
undefined deleteSampler(WebGLSampler? sampler);
[WebGLHandlesContextLoss] GLboolean isSampler(WebGLSampler? sampler);
undefined bindSampler(GLuint unit, WebGLSampler? sampler);
@@ -449,7 +449,7 @@ interface mixin WebGL2RenderingContextBase
any getSyncParameter(WebGLSync sync, GLenum pname);
/* Transform Feedback */
- WebGLTransformFeedback? createTransformFeedback();
+ WebGLTransformFeedback createTransformFeedback();
undefined deleteTransformFeedback(WebGLTransformFeedback? tf);
[WebGLHandlesContextLoss] GLboolean isTransformFeedback(WebGLTransformFeedback? tf);
undefined bindTransformFeedback (GLenum target, WebGLTransformFeedback? tf);
@@ -472,7 +472,7 @@ interface mixin WebGL2RenderingContextBase
undefined uniformBlockBinding(WebGLProgram program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
/* Vertex Array Objects */
- WebGLVertexArrayObject? createVertexArray();
+ WebGLVertexArrayObject createVertexArray();
undefined deleteVertexArray(WebGLVertexArrayObject? vertexArray);
[WebGLHandlesContextLoss] GLboolean isVertexArray(WebGLVertexArrayObject? vertexArray);
undefined bindVertexArray(WebGLVertexArrayObject? array);
diff --git a/test/fixtures/wpt/interfaces/webgpu.idl b/test/fixtures/wpt/interfaces/webgpu.idl
index ef5b9c730ab..f575306efef 100644
--- a/test/fixtures/wpt/interfaces/webgpu.idl
+++ b/test/fixtures/wpt/interfaces/webgpu.idl
@@ -92,10 +92,10 @@ enum GPUPowerPreference {
interface GPUAdapter {
[SameObject] readonly attribute GPUSupportedFeatures features;
[SameObject] readonly attribute GPUSupportedLimits limits;
+ [SameObject] readonly attribute GPUAdapterInfo info;
readonly attribute boolean isFallbackAdapter;
Promise requestDevice(optional GPUDeviceDescriptor descriptor = {});
- Promise requestAdapterInfo();
};
dictionary GPUDeviceDescriptor
@@ -109,6 +109,7 @@ enum GPUFeatureName {
"depth-clip-control",
"depth32float-stencil8",
"texture-compression-bc",
+ "texture-compression-bc-sliced-3d",
"texture-compression-etc2",
"texture-compression-astc",
"timestamp-query",
@@ -117,6 +118,8 @@ enum GPUFeatureName {
"rg11b10ufloat-renderable",
"bgra8unorm-storage",
"float32-filterable",
+ "clip-distances",
+ "dual-source-blending",
};
[Exposed=(Window, Worker), SecureContext]
@@ -750,6 +753,10 @@ enum GPUBlendFactor {
"src-alpha-saturated",
"constant",
"one-minus-constant",
+ "src1",
+ "one-minus-src1",
+ "src1-alpha",
+ "one-minus-src1-alpha",
};
enum GPUBlendOperation {
@@ -1183,12 +1190,22 @@ enum GPUCanvasAlphaMode {
"premultiplied",
};
+enum GPUCanvasToneMappingMode {
+ "standard",
+ "extended",
+};
+
+dictionary GPUCanvasToneMapping {
+ GPUCanvasToneMappingMode mode = "standard";
+};
+
dictionary GPUCanvasConfiguration {
required GPUDevice device;
required GPUTextureFormat format;
GPUTextureUsageFlags usage = 0x10; // GPUTextureUsage.RENDER_ATTACHMENT
sequence viewFormats = [];
PredefinedColorSpace colorSpace = "srgb";
+ GPUCanvasToneMapping toneMapping = {};
GPUCanvasAlphaMode alphaMode = "opaque";
};
diff --git a/test/fixtures/wpt/interfaces/webmidi.idl b/test/fixtures/wpt/interfaces/webmidi.idl
index 9bab8a5f961..f4a1a29b2e1 100644
--- a/test/fixtures/wpt/interfaces/webmidi.idl
+++ b/test/fixtures/wpt/interfaces/webmidi.idl
@@ -17,22 +17,22 @@ dictionary MIDIOptions {
boolean software;
};
-[SecureContext, Exposed=Window] interface MIDIInputMap {
+[SecureContext, Exposed=(Window,Worker)] interface MIDIInputMap {
readonly maplike ;
};
-[SecureContext, Exposed=Window] interface MIDIOutputMap {
+[SecureContext, Exposed=(Window,Worker)] interface MIDIOutputMap {
readonly maplike ;
};
-[SecureContext, Exposed=Window] interface MIDIAccess: EventTarget {
+[SecureContext, Exposed=(Window,Worker), Transferable] interface MIDIAccess: EventTarget {
readonly attribute MIDIInputMap inputs;
readonly attribute MIDIOutputMap outputs;
attribute EventHandler onstatechange;
readonly attribute boolean sysexEnabled;
};
-[SecureContext, Exposed=Window] interface MIDIPort: EventTarget {
+[SecureContext, Exposed=(Window,Worker)] interface MIDIPort: EventTarget {
readonly attribute DOMString id;
readonly attribute DOMString? manufacturer;
readonly attribute DOMString? name;
@@ -45,11 +45,11 @@ dictionary MIDIOptions {
Promise close();
};
-[SecureContext, Exposed=Window] interface MIDIInput: MIDIPort {
+[SecureContext, Exposed=(Window,Worker)] interface MIDIInput: MIDIPort {
attribute EventHandler onmidimessage;
};
-[SecureContext, Exposed=Window] interface MIDIOutput : MIDIPort {
+[SecureContext, Exposed=(Window,Worker)] interface MIDIOutput : MIDIPort {
undefined send(sequence data, optional DOMHighResTimeStamp timestamp = 0);
undefined clear();
};
@@ -70,7 +70,7 @@ enum MIDIPortConnectionState {
"pending",
};
-[SecureContext, Exposed=Window]
+[SecureContext, Exposed=(Window,Worker)]
interface MIDIMessageEvent : Event {
constructor(DOMString type, optional MIDIMessageEventInit eventInitDict = {});
readonly attribute Uint8Array? data;
@@ -80,7 +80,7 @@ dictionary MIDIMessageEventInit: EventInit {
Uint8Array data;
};
-[SecureContext, Exposed=Window]
+[SecureContext, Exposed=(Window,Worker)]
interface MIDIConnectionEvent : Event {
constructor(DOMString type, optional MIDIConnectionEventInit eventInitDict = {});
readonly attribute MIDIPort? port;
diff --git a/test/fixtures/wpt/interfaces/webnn.idl b/test/fixtures/wpt/interfaces/webnn.idl
index 9af2879214e..ef29aaeefda 100644
--- a/test/fixtures/wpt/interfaces/webnn.idl
+++ b/test/fixtures/wpt/interfaces/webnn.idl
@@ -11,7 +11,8 @@ WorkerNavigator includes NavigatorML;
enum MLDeviceType {
"cpu",
- "gpu"
+ "gpu",
+ "npu"
};
enum MLPowerPreference {
@@ -31,7 +32,7 @@ interface ML {
Promise createContext(GPUDevice gpuDevice);
};
-typedef record MLNamedArrayBufferViews;
+typedef record MLNamedArrayBufferViews;
dictionary MLComputeResult {
MLNamedArrayBufferViews inputs;
@@ -74,10 +75,13 @@ interface MLOperand {
sequence shape();
};
-[SecureContext, Exposed=(Window, DedicatedWorker)]
-interface MLActivation {};
+dictionary MLOperatorOptions {
+ USVString label = "";
+};
+
+typedef (bigint or unrestricted double) MLNumber;
-typedef record MLNamedOperands;
+typedef record MLNamedOperands;
[SecureContext, Exposed=(Window, DedicatedWorker)]
interface MLGraphBuilder {
@@ -85,35 +89,35 @@ interface MLGraphBuilder {
constructor(MLContext context);
// Create an operand for a graph input.
- MLOperand input(DOMString name, MLOperandDescriptor descriptor);
+ MLOperand input(USVString name, MLOperandDescriptor descriptor);
// Create an operand for a graph constant.
MLOperand constant(MLOperandDescriptor descriptor, ArrayBufferView bufferView);
- // Create a single-value operand from the specified number of the specified type.
- MLOperand constant(double value, optional MLOperandDataType type = "float32");
+ // Create a scalar operand from the specified number of the specified type.
+ MLOperand constant(MLOperandDataType type, MLNumber value);
// Compile the graph up to the specified output operands asynchronously.
Promise build(MLNamedOperands outputs);
};
-dictionary MLArgMinMaxOptions {
- sequence<[EnforceRange] unsigned long> axes;
+dictionary MLArgMinMaxOptions : MLOperatorOptions {
boolean keepDimensions = false;
- boolean selectLastIndex = false;
+ MLOperandDataType outputDataType = "int32";
};
partial interface MLGraphBuilder {
- MLOperand argMin(MLOperand input, optional MLArgMinMaxOptions options = {});
- MLOperand argMax(MLOperand input, optional MLArgMinMaxOptions options = {});
+ MLOperand argMin(MLOperand input, [EnforceRange] unsigned long axis,
+ optional MLArgMinMaxOptions options = {});
+ MLOperand argMax(MLOperand input, [EnforceRange] unsigned long axis,
+ optional MLArgMinMaxOptions options = {});
};
-dictionary MLBatchNormalizationOptions {
+dictionary MLBatchNormalizationOptions : MLOperatorOptions {
MLOperand scale;
MLOperand bias;
[EnforceRange] unsigned long axis = 1;
- float epsilon = 1e-5;
- MLActivation activation;
+ double epsilon = 1e-5;
};
partial interface MLGraphBuilder {
@@ -122,21 +126,24 @@ partial interface MLGraphBuilder {
};
partial interface MLGraphBuilder {
- MLOperand cast(MLOperand input, MLOperandDataType type);
+ MLOperand cast(MLOperand input,
+ MLOperandDataType type,
+ optional MLOperatorOptions options = {});
};
-dictionary MLClampOptions {
- float minValue;
- float maxValue;
+dictionary MLClampOptions : MLOperatorOptions {
+ MLNumber minValue;
+ MLNumber maxValue;
};
partial interface MLGraphBuilder {
MLOperand clamp(MLOperand input, optional MLClampOptions options = {});
- MLActivation clamp(optional MLClampOptions options = {});
};
partial interface MLGraphBuilder {
- MLOperand concat(sequence inputs, [EnforceRange] unsigned long axis);
+ MLOperand concat(sequence inputs,
+ [EnforceRange] unsigned long axis,
+ optional MLOperatorOptions options = {});
};
enum MLConv2dFilterOperandLayout {
@@ -146,7 +153,7 @@ enum MLConv2dFilterOperandLayout {
"ihwo"
};
-dictionary MLConv2dOptions {
+dictionary MLConv2dOptions : MLOperatorOptions {
sequence<[EnforceRange] unsigned long> padding;
sequence<[EnforceRange] unsigned long> strides;
sequence<[EnforceRange] unsigned long> dilations;
@@ -154,7 +161,6 @@ dictionary MLConv2dOptions {
MLInputOperandLayout inputLayout = "nchw";
MLConv2dFilterOperandLayout filterLayout = "oihw";
MLOperand bias;
- MLActivation activation;
};
partial interface MLGraphBuilder {
@@ -169,7 +175,7 @@ enum MLConvTranspose2dFilterOperandLayout {
"ohwi"
};
-dictionary MLConvTranspose2dOptions {
+dictionary MLConvTranspose2dOptions : MLOperatorOptions {
sequence<[EnforceRange] unsigned long> padding;
sequence<[EnforceRange] unsigned long> strides;
sequence<[EnforceRange] unsigned long> dilations;
@@ -179,7 +185,6 @@ dictionary MLConvTranspose2dOptions {
MLInputOperandLayout inputLayout = "nchw";
MLConvTranspose2dFilterOperandLayout filterLayout = "iohw";
MLOperand bias;
- MLActivation activation;
};
partial interface MLGraphBuilder {
@@ -188,54 +193,65 @@ partial interface MLGraphBuilder {
};
partial interface MLGraphBuilder {
- MLOperand add(MLOperand a, MLOperand b);
- MLOperand sub(MLOperand a, MLOperand b);
- MLOperand mul(MLOperand a, MLOperand b);
- MLOperand div(MLOperand a, MLOperand b);
- MLOperand max(MLOperand a, MLOperand b);
- MLOperand min(MLOperand a, MLOperand b);
- MLOperand pow(MLOperand a, MLOperand b);
+ MLOperand add(MLOperand a, MLOperand b, optional MLOperatorOptions options = {});
+ MLOperand sub(MLOperand a, MLOperand b, optional MLOperatorOptions options = {});
+ MLOperand mul(MLOperand a, MLOperand b, optional MLOperatorOptions options = {});
+ MLOperand div(MLOperand a, MLOperand b, optional MLOperatorOptions options = {});
+ MLOperand max(MLOperand a, MLOperand b, optional MLOperatorOptions options = {});
+ MLOperand min(MLOperand a, MLOperand b, optional MLOperatorOptions options = {});
+ MLOperand pow(MLOperand a, MLOperand b, optional MLOperatorOptions options = {});
};
partial interface MLGraphBuilder {
- MLOperand equal(MLOperand a, MLOperand b);
- MLOperand greater(MLOperand a, MLOperand b);
- MLOperand greaterOrEqual(MLOperand a, MLOperand b);
- MLOperand lesser(MLOperand a, MLOperand b);
- MLOperand lesserOrEqual(MLOperand a, MLOperand b);
- MLOperand not(MLOperand a);
+ MLOperand equal(MLOperand a,
+ MLOperand b,
+ optional MLOperatorOptions options = {});
+ MLOperand greater(MLOperand a,
+ MLOperand b,
+ optional MLOperatorOptions options = {});
+ MLOperand greaterOrEqual(MLOperand a,
+ MLOperand b,
+ optional MLOperatorOptions options = {});
+ MLOperand lesser(MLOperand a,
+ MLOperand b,
+ optional MLOperatorOptions options = {});
+ MLOperand lesserOrEqual(MLOperand a,
+ MLOperand b,
+ optional MLOperatorOptions options = {});
+ MLOperand logicalNot(MLOperand a, optional MLOperatorOptions options = {});
};
partial interface MLGraphBuilder {
- MLOperand abs(MLOperand input);
- MLOperand ceil(MLOperand input);
- MLOperand cos(MLOperand input);
- MLOperand erf(MLOperand input);
- MLOperand exp(MLOperand input);
- MLOperand floor(MLOperand input);
- MLOperand identity(MLOperand input);
- MLOperand log(MLOperand input);
- MLOperand neg(MLOperand input);
- MLOperand reciprocal(MLOperand input);
- MLOperand sin(MLOperand input);
- MLOperand sqrt(MLOperand input);
- MLOperand tan(MLOperand input);
+ MLOperand abs(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand ceil(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand cos(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand erf(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand exp(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand floor(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand identity(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand log(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand neg(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand reciprocal(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand sin(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand sqrt(MLOperand input, optional MLOperatorOptions options = {});
+ MLOperand tan(MLOperand input, optional MLOperatorOptions options = {});
};
-dictionary MLEluOptions {
- float alpha = 1;
+dictionary MLEluOptions : MLOperatorOptions {
+ double alpha = 1;
};
partial interface MLGraphBuilder {
MLOperand elu(MLOperand input, optional MLEluOptions options = {});
- MLActivation elu(optional MLEluOptions options = {});
};
partial interface MLGraphBuilder {
- MLOperand expand(MLOperand input, sequence<[EnforceRange] unsigned long> newShape);
+ MLOperand expand(MLOperand input,
+ sequence<[EnforceRange] unsigned long> newShape,
+ optional MLOperatorOptions options = {});
};
-dictionary MLGatherOptions {
+dictionary MLGatherOptions : MLOperatorOptions {
[EnforceRange] unsigned long axis = 0;
};
@@ -246,14 +262,13 @@ partial interface MLGraphBuilder {
};
partial interface MLGraphBuilder {
- MLOperand gelu(MLOperand input);
- MLActivation gelu();
+ MLOperand gelu(MLOperand input, optional MLOperatorOptions options = {});
};
-dictionary MLGemmOptions {
+dictionary MLGemmOptions : MLOperatorOptions {
MLOperand c;
- float alpha = 1.0;
- float beta = 1.0;
+ double alpha = 1.0;
+ double beta = 1.0;
boolean aTranspose = false;
boolean bTranspose = false;
};
@@ -267,13 +282,19 @@ enum MLGruWeightLayout {
"rzn" // reset-update-new gate ordering
};
+enum MLRecurrentNetworkActivation {
+ "relu",
+ "sigmoid",
+ "tanh"
+};
+
enum MLRecurrentNetworkDirection {
"forward",
"backward",
"both"
};
-dictionary MLGruOptions {
+dictionary MLGruOptions : MLOperatorOptions {
MLOperand bias;
MLOperand recurrentBias;
MLOperand initialHiddenState;
@@ -281,7 +302,7 @@ dictionary MLGruOptions {
boolean returnSequence = false;
MLRecurrentNetworkDirection direction = "forward";
MLGruWeightLayout layout = "zrn";
- sequence activations;
+ sequence activations;
};
partial interface MLGraphBuilder {
@@ -293,12 +314,12 @@ partial interface MLGraphBuilder {
optional MLGruOptions options = {});
};
-dictionary MLGruCellOptions {
+dictionary MLGruCellOptions : MLOperatorOptions {
MLOperand bias;
MLOperand recurrentBias;
boolean resetAfter = true;
MLGruWeightLayout layout = "zrn";
- sequence activations;
+ sequence activations;
};
partial interface MLGraphBuilder {
@@ -310,25 +331,23 @@ partial interface MLGraphBuilder {
optional MLGruCellOptions options = {});
};
-dictionary MLHardSigmoidOptions {
- float alpha = 0.2;
- float beta = 0.5;
+dictionary MLHardSigmoidOptions : MLOperatorOptions {
+ double alpha = 0.2;
+ double beta = 0.5;
};
partial interface MLGraphBuilder {
MLOperand hardSigmoid(MLOperand input, optional MLHardSigmoidOptions options = {});
- MLActivation hardSigmoid(optional MLHardSigmoidOptions options = {});
};
partial interface MLGraphBuilder {
- MLOperand hardSwish(MLOperand input);
- MLActivation hardSwish();
+ MLOperand hardSwish(MLOperand input, optional MLOperatorOptions options = {});
};
-dictionary MLInstanceNormalizationOptions {
+dictionary MLInstanceNormalizationOptions : MLOperatorOptions {
MLOperand scale;
MLOperand bias;
- float epsilon = 1e-5;
+ double epsilon = 1e-5;
MLInputOperandLayout layout = "nchw";
};
@@ -337,11 +356,11 @@ partial interface MLGraphBuilder {
optional MLInstanceNormalizationOptions options = {});
};
-dictionary MLLayerNormalizationOptions {
+dictionary MLLayerNormalizationOptions : MLOperatorOptions {
MLOperand scale;
MLOperand bias;
sequence<[EnforceRange] unsigned long> axes;
- float epsilon = 1e-5;
+ double epsilon = 1e-5;
};
partial interface MLGraphBuilder {
@@ -349,23 +368,21 @@ partial interface MLGraphBuilder {
optional MLLayerNormalizationOptions options = {});
};
-dictionary MLLeakyReluOptions {
- float alpha = 0.01;
+dictionary MLLeakyReluOptions : MLOperatorOptions {
+ double alpha = 0.01;
};
partial interface MLGraphBuilder {
MLOperand leakyRelu(MLOperand input, optional MLLeakyReluOptions options = {});
- MLActivation leakyRelu(optional MLLeakyReluOptions options = {});
};
-dictionary MLLinearOptions {
- float alpha = 1;
- float beta = 0;
+dictionary MLLinearOptions : MLOperatorOptions {
+ double alpha = 1;
+ double beta = 0;
};
partial interface MLGraphBuilder {
MLOperand linear(MLOperand input, optional MLLinearOptions options = {});
- MLActivation linear(optional MLLinearOptions options = {});
};
enum MLLstmWeightLayout {
@@ -373,7 +390,7 @@ enum MLLstmWeightLayout {
"ifgo" // input-forget-cell-output gate ordering
};
-dictionary MLLstmOptions {
+dictionary MLLstmOptions : MLOperatorOptions {
MLOperand bias;
MLOperand recurrentBias;
MLOperand peepholeWeight;
@@ -382,7 +399,7 @@ dictionary MLLstmOptions {
boolean returnSequence = false;
MLRecurrentNetworkDirection direction = "forward";
MLLstmWeightLayout layout = "iofg";
- sequence activations;
+ sequence activations;
};
partial interface MLGraphBuilder {
@@ -394,12 +411,12 @@ partial interface MLGraphBuilder {
optional MLLstmOptions options = {});
};
-dictionary MLLstmCellOptions {
+dictionary MLLstmCellOptions : MLOperatorOptions {
MLOperand bias;
MLOperand recurrentBias;
MLOperand peepholeWeight;
MLLstmWeightLayout layout = "iofg";
- sequence activations;
+ sequence activations;
};
partial interface MLGraphBuilder {
@@ -413,7 +430,7 @@ partial interface MLGraphBuilder {
};
partial interface MLGraphBuilder {
- MLOperand matmul(MLOperand a, MLOperand b);
+ MLOperand matmul(MLOperand a, MLOperand b, optional MLOperatorOptions options = {});
};
enum MLPaddingMode {
@@ -423,9 +440,9 @@ enum MLPaddingMode {
"symmetric"
};
-dictionary MLPadOptions {
+dictionary MLPadOptions : MLOperatorOptions {
MLPaddingMode mode = "constant";
- float value = 0;
+ MLNumber value = 0;
};
partial interface MLGraphBuilder {
@@ -440,7 +457,7 @@ enum MLRoundingType {
"ceil"
};
-dictionary MLPool2dOptions {
+dictionary MLPool2dOptions : MLOperatorOptions {
sequence<[EnforceRange] unsigned long> windowDimensions;
sequence<[EnforceRange] unsigned long> padding;
sequence<[EnforceRange] unsigned long> strides;
@@ -457,10 +474,12 @@ partial interface MLGraphBuilder {
};
partial interface MLGraphBuilder {
- MLOperand prelu(MLOperand input, MLOperand slope);
+ MLOperand prelu(MLOperand input,
+ MLOperand slope,
+ optional MLOperatorOptions options = {});
};
-dictionary MLReduceOptions {
+dictionary MLReduceOptions : MLOperatorOptions {
sequence<[EnforceRange] unsigned long> axes;
boolean keepDimensions = false;
};
@@ -479,8 +498,7 @@ partial interface MLGraphBuilder {
};
partial interface MLGraphBuilder {
- MLOperand relu(MLOperand input);
- MLActivation relu();
+ MLOperand relu(MLOperand input, optional MLOperatorOptions options = {});
};
enum MLInterpolationMode {
@@ -488,7 +506,7 @@ enum MLInterpolationMode {
"linear"
};
-dictionary MLResample2dOptions {
+dictionary MLResample2dOptions : MLOperatorOptions {
MLInterpolationMode mode = "nearest-neighbor";
sequence scales;
sequence<[EnforceRange] unsigned long> sizes;
@@ -500,36 +518,37 @@ partial interface MLGraphBuilder {
};
partial interface MLGraphBuilder {
- MLOperand reshape(MLOperand input, sequence<[EnforceRange] unsigned long> newShape);
+ MLOperand reshape(MLOperand input,
+ sequence<[EnforceRange] unsigned long> newShape,
+ optional MLOperatorOptions options = {});
};
partial interface MLGraphBuilder {
- MLOperand sigmoid(MLOperand input);
- MLActivation sigmoid();
+ MLOperand sigmoid(MLOperand input, optional MLOperatorOptions options = {});
};
partial interface MLGraphBuilder {
MLOperand slice(MLOperand input,
sequence<[EnforceRange] unsigned long> starts,
- sequence<[EnforceRange] unsigned long> sizes);
+ sequence<[EnforceRange] unsigned long> sizes,
+ optional MLOperatorOptions options = {});
};
partial interface MLGraphBuilder {
- MLOperand softmax(MLOperand input, unsigned long axis);
- MLActivation softmax(unsigned long axis);
+ MLOperand softmax(MLOperand input,
+ [EnforceRange] unsigned long axis,
+ optional MLOperatorOptions options = {});
};
partial interface MLGraphBuilder {
- MLOperand softplus(MLOperand input);
- MLActivation softplus();
+ MLOperand softplus(MLOperand input, optional MLOperatorOptions options = {});
};
partial interface MLGraphBuilder {
- MLOperand softsign(MLOperand input);
- MLActivation softsign();
+ MLOperand softsign(MLOperand input, optional MLOperatorOptions options = {});
};
-dictionary MLSplitOptions {
+dictionary MLSplitOptions : MLOperatorOptions {
[EnforceRange] unsigned long axis = 0;
};
@@ -541,11 +560,10 @@ partial interface MLGraphBuilder {
};
partial interface MLGraphBuilder {
- MLOperand tanh(MLOperand input);
- MLActivation tanh();
+ MLOperand tanh(MLOperand input, optional MLOperatorOptions options = {});
};
-dictionary MLTransposeOptions {
+dictionary MLTransposeOptions : MLOperatorOptions {
sequence<[EnforceRange] unsigned long> permutation;
};
@@ -553,7 +571,7 @@ partial interface MLGraphBuilder {
MLOperand transpose(MLOperand input, optional MLTransposeOptions options = {});
};
-dictionary MLTriangularOptions {
+dictionary MLTriangularOptions : MLOperatorOptions {
boolean upper = true;
[EnforceRange] long diagonal = 0;
};
@@ -563,5 +581,8 @@ partial interface MLGraphBuilder {
};
partial interface MLGraphBuilder {
- MLOperand where(MLOperand condition, MLOperand input, MLOperand other);
+ MLOperand where(MLOperand condition,
+ MLOperand trueValue,
+ MLOperand falseValue,
+ optional MLOperatorOptions options = {});
};
diff --git a/test/fixtures/wpt/interfaces/webrtc.idl b/test/fixtures/wpt/interfaces/webrtc.idl
index 65e7aa622c5..6bfbdb898c5 100644
--- a/test/fixtures/wpt/interfaces/webrtc.idl
+++ b/test/fixtures/wpt/interfaces/webrtc.idl
@@ -148,7 +148,7 @@ interface RTCSessionDescription {
constructor(RTCSessionDescriptionInit descriptionInitDict);
readonly attribute RTCSdpType type;
readonly attribute DOMString sdp;
- [Default] object toJSON();
+ [Default] RTCSessionDescriptionInit toJSON();
};
dictionary RTCSessionDescriptionInit {
@@ -345,13 +345,10 @@ dictionary RTCRtpCodecParameters : RTCRtpCodec {
};
dictionary RTCRtpCapabilities {
- required sequence codecs;
+ required sequence codecs;
required sequence headerExtensions;
};
-dictionary RTCRtpCodecCapability : RTCRtpCodec {
-};
-
dictionary RTCRtpHeaderExtensionCapability {
required DOMString uri;
};
@@ -434,9 +431,10 @@ dictionary RTCIceParameters {
DOMString password;
};
-dictionary RTCIceCandidatePair {
- required RTCIceCandidate local;
- required RTCIceCandidate remote;
+[Exposed=Window]
+interface RTCIceCandidatePair {
+ [SameObject] readonly attribute RTCIceCandidate local;
+ [SameObject] readonly attribute RTCIceCandidate remote;
};
enum RTCIceGathererState {
diff --git a/test/fixtures/wpt/interfaces/webtransport.idl b/test/fixtures/wpt/interfaces/webtransport.idl
index e598059c93e..2136cad41e3 100644
--- a/test/fixtures/wpt/interfaces/webtransport.idl
+++ b/test/fixtures/wpt/interfaces/webtransport.idl
@@ -84,24 +84,24 @@ dictionary WebTransportSendStreamOptions {
};
dictionary WebTransportConnectionStats {
- unsigned long long bytesSent;
- unsigned long long packetsSent;
- unsigned long long bytesLost;
- unsigned long long packetsLost;
- unsigned long long bytesReceived;
- unsigned long long packetsReceived;
- DOMHighResTimeStamp smoothedRtt;
- DOMHighResTimeStamp rttVariation;
- DOMHighResTimeStamp minRtt;
- WebTransportDatagramStats datagrams;
- unsigned long long? estimatedSendRate;
+ unsigned long long bytesSent = 0;
+ unsigned long long packetsSent = 0;
+ unsigned long long bytesLost = 0;
+ unsigned long long packetsLost = 0;
+ unsigned long long bytesReceived = 0;
+ unsigned long long packetsReceived = 0;
+ required DOMHighResTimeStamp smoothedRtt;
+ required DOMHighResTimeStamp rttVariation;
+ required DOMHighResTimeStamp minRtt;
+ required WebTransportDatagramStats datagrams;
+ required unsigned long long? estimatedSendRate;
};
dictionary WebTransportDatagramStats {
- unsigned long long droppedIncoming;
- unsigned long long expiredIncoming;
- unsigned long long expiredOutgoing;
- unsigned long long lostOutgoing;
+ unsigned long long droppedIncoming = 0;
+ unsigned long long expiredIncoming = 0;
+ unsigned long long expiredOutgoing = 0;
+ unsigned long long lostOutgoing = 0;
};
[Exposed=(Window,Worker), SecureContext, Transferable]
@@ -113,9 +113,9 @@ interface WebTransportSendStream : WritableStream {
};
dictionary WebTransportSendStreamStats {
- unsigned long long bytesWritten;
- unsigned long long bytesSent;
- unsigned long long bytesAcknowledged;
+ unsigned long long bytesWritten = 0;
+ unsigned long long bytesSent = 0;
+ unsigned long long bytesAcknowledged = 0;
};
[Exposed=(Window,Worker), SecureContext]
@@ -129,8 +129,8 @@ interface WebTransportReceiveStream : ReadableStream {
};
dictionary WebTransportReceiveStreamStats {
- unsigned long long bytesReceived;
- unsigned long long bytesRead;
+ unsigned long long bytesReceived = 0;
+ unsigned long long bytesRead = 0;
};
[Exposed=(Window,Worker), SecureContext]
diff --git a/test/fixtures/wpt/interfaces/webxr-depth-sensing.idl b/test/fixtures/wpt/interfaces/webxr-depth-sensing.idl
index c44f029436f..b77b59c0ce6 100644
--- a/test/fixtures/wpt/interfaces/webxr-depth-sensing.idl
+++ b/test/fixtures/wpt/interfaces/webxr-depth-sensing.idl
@@ -10,7 +10,8 @@ enum XRDepthUsage {
enum XRDepthDataFormat {
"luminance-alpha",
- "float32"
+ "float32",
+ "unsigned-short",
};
dictionary XRDepthStateInit {
@@ -50,6 +51,9 @@ partial interface XRFrame {
[Exposed=Window]
interface XRWebGLDepthInformation : XRDepthInformation {
[SameObject] readonly attribute WebGLTexture texture;
+
+ readonly attribute XRTextureType textureType;
+ readonly attribute unsigned long? imageIndex;
};
partial interface XRWebGLBinding {
diff --git a/test/fixtures/wpt/interfaces/webxr-hit-test.idl b/test/fixtures/wpt/interfaces/webxr-hit-test.idl
index fa4fb71c9de..d01bffe2703 100644
--- a/test/fixtures/wpt/interfaces/webxr-hit-test.idl
+++ b/test/fixtures/wpt/interfaces/webxr-hit-test.idl
@@ -11,13 +11,13 @@ enum XRHitTestTrackableType {
dictionary XRHitTestOptionsInit {
required XRSpace space;
- FrozenArray entityTypes;
+ sequence entityTypes;
XRRay offsetRay;
};
dictionary XRTransientInputHitTestOptionsInit {
required DOMString profile;
- FrozenArray entityTypes;
+ sequence entityTypes;
XRRay offsetRay;
};
@@ -48,8 +48,8 @@ partial interface XRSession {
};
partial interface XRFrame {
- FrozenArray getHitTestResults(XRHitTestSource hitTestSource);
- FrozenArray getHitTestResultsForTransientInput(XRTransientInputHitTestSource hitTestSource);
+ sequence getHitTestResults(XRHitTestSource hitTestSource);
+ sequence getHitTestResultsForTransientInput(XRTransientInputHitTestSource hitTestSource);
};
dictionary XRRayDirectionInit {
diff --git a/test/fixtures/wpt/mimesniff/META.yml b/test/fixtures/wpt/mimesniff/META.yml
index fd41c87fad8..739628b8045 100644
--- a/test/fixtures/wpt/mimesniff/META.yml
+++ b/test/fixtures/wpt/mimesniff/META.yml
@@ -1,3 +1,4 @@
spec: https://mimesniff.spec.whatwg.org/
suggested_reviewers:
- annevk
+ - GPHemsley
diff --git a/test/fixtures/wpt/mimesniff/media/resources/make-vectors.sh b/test/fixtures/wpt/mimesniff/media/resources/make-vectors.sh
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/mimesniff/sniffing/html.window.js b/test/fixtures/wpt/mimesniff/sniffing/html.window.js
new file mode 100644
index 00000000000..3fef45bb477
--- /dev/null
+++ b/test/fixtures/wpt/mimesniff/sniffing/html.window.js
@@ -0,0 +1,12 @@
+["atom", "rss"].forEach(item => {
+ async_test(t => {
+ const popup = window.open(`support/${item}.html`);
+ t.add_cleanup(() => popup.close());
+ popup.onload = t.step_func_done(() => {
+ assert_equals(popup.document.contentType, "text/html");
+ assert_equals(popup.document.documentElement.localName, "html");
+ assert_equals(popup.document.documentElement.namespaceURI, "http://www.w3.org/1999/xhtml");
+ assert_equals(popup.document.querySelector("b").namespaceURI, "http://www.w3.org/1999/xhtml");
+ });
+ }, `HTML is not sniffed for a "feed": ${item}`);
+});
diff --git a/test/fixtures/wpt/mimesniff/sniffing/support/atom.html b/test/fixtures/wpt/mimesniff/sniffing/support/atom.html
new file mode 100644
index 00000000000..b343d6d6154
--- /dev/null
+++ b/test/fixtures/wpt/mimesniff/sniffing/support/atom.html
@@ -0,0 +1,3 @@
+
+ HELLO
+
diff --git a/test/fixtures/wpt/mimesniff/sniffing/support/rss.html b/test/fixtures/wpt/mimesniff/sniffing/support/rss.html
new file mode 100644
index 00000000000..d708b0d8ebd
--- /dev/null
+++ b/test/fixtures/wpt/mimesniff/sniffing/support/rss.html
@@ -0,0 +1,3 @@
+
+ HELLO
+
diff --git a/test/fixtures/wpt/resources/chromium/README.md b/test/fixtures/wpt/resources/chromium/README.md
new file mode 100644
index 00000000000..be090b332fc
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/README.md
@@ -0,0 +1,7 @@
+This directory contains Chromium-specific test resources, including mocks for
+test-only APIs implemented with
+[MojoJS](https://chromium.googlesource.com/chromium/src/+/main/mojo/public/js/README.md).
+
+Please do **not** copy `*.mojom.m.js` into this directory. Follow this doc if you
+want to add new MojoJS-backed mocks:
+https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_platform_tests.md#mojojs
diff --git a/test/fixtures/wpt/resources/chromium/contacts_manager_mock.js b/test/fixtures/wpt/resources/chromium/contacts_manager_mock.js
new file mode 100644
index 00000000000..049685242bc
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/contacts_manager_mock.js
@@ -0,0 +1,90 @@
+// Copyright 2018 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {ContactsManager, ContactsManagerReceiver} from '/gen/third_party/blink/public/mojom/contacts/contacts_manager.mojom.m.js';
+
+self.WebContactsTest = (() => {
+ class MockContacts {
+ constructor() {
+ this.receiver_ = new ContactsManagerReceiver(this);
+
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(ContactsManager.$interfaceName);
+ this.interceptor_.oninterfacerequest =
+ e => this.receiver_.$.bindHandle(e.handle);
+ this.interceptor_.start();
+
+ this.selectedContacts_ = [];
+ }
+
+ formatAddress_(address) {
+ // These are all required fields in the mojo definition.
+ return {
+ country: address.country || '',
+ addressLine: address.addressLine || [],
+ region: address.region || '',
+ city: address.city || '',
+ dependentLocality: address.dependentLocality || '',
+ postalCode: address.postCode || '',
+ sortingCode: address.sortingCode || '',
+ organization: address.organization || '',
+ recipient: address.recipient || '',
+ phone: address.phone || '',
+ };
+ }
+
+ async select(multiple, includeNames, includeEmails, includeTel, includeAddresses, includeIcons) {
+ if (this.selectedContacts_ === null)
+ return {contacts: null};
+
+ const contactInfos = await Promise.all(this.selectedContacts_.map(async contact => {
+ const contactInfo = {};
+ if (includeNames)
+ contactInfo.name = contact.name || [];
+ if (includeEmails)
+ contactInfo.email = contact.email || [];
+ if (includeTel)
+ contactInfo.tel = contact.tel || [];
+ if (includeAddresses) {
+ contactInfo.address = (contact.address || []).map(address => this.formatAddress_(address));
+ }
+ if (includeIcons) {
+ contactInfo.icon = await Promise.all(
+ (contact.icon || []).map(async blob => ({
+ mimeType: blob.type,
+ data: (await blob.text()).split('').map(s => s.charCodeAt(0)),
+ })));
+ }
+ return contactInfo;
+ }));
+
+ if (!contactInfos.length) return {contacts: []};
+ if (!multiple) return {contacts: [contactInfos[0]]};
+ return {contacts: contactInfos};
+ }
+
+ setSelectedContacts(contacts) {
+ this.selectedContacts_ = contacts;
+ }
+
+ reset() {
+ this.receiver_.$.close();
+ this.interceptor_.stop();
+ }
+ }
+
+ const mockContacts = new MockContacts();
+
+ class ContactsTestChromium {
+ constructor() {
+ Object.freeze(this); // Make it immutable.
+ }
+
+ setSelectedContacts(contacts) {
+ mockContacts.setSelectedContacts(contacts);
+ }
+ }
+
+ return ContactsTestChromium;
+})();
diff --git a/test/fixtures/wpt/resources/chromium/content-index-helpers.js b/test/fixtures/wpt/resources/chromium/content-index-helpers.js
new file mode 100644
index 00000000000..936fe84c9b1
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/content-index-helpers.js
@@ -0,0 +1,9 @@
+import {ContentIndexService} from '/gen/third_party/blink/public/mojom/content_index/content_index.mojom.m.js';
+
+// Returns a promise if the chromium based browser fetches icons for
+// content-index.
+export async function fetchesIcons() {
+ const remote = ContentIndexService.getRemote();
+ const {iconSizes} = await remote.getIconSizes();
+ return iconSizes.length > 0;
+};
diff --git a/test/fixtures/wpt/resources/chromium/enable-hyperlink-auditing.js b/test/fixtures/wpt/resources/chromium/enable-hyperlink-auditing.js
new file mode 100644
index 00000000000..263f6512f09
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/enable-hyperlink-auditing.js
@@ -0,0 +1,2 @@
+if (window.testRunner)
+ testRunner.overridePreference("WebKitHyperlinkAuditingEnabled", 1);
diff --git a/test/fixtures/wpt/resources/chromium/fake-hid.js b/test/fixtures/wpt/resources/chromium/fake-hid.js
new file mode 100644
index 00000000000..70a01490d87
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/fake-hid.js
@@ -0,0 +1,297 @@
+import {HidConnectionReceiver, HidDeviceInfo} from '/gen/services/device/public/mojom/hid.mojom.m.js';
+import {HidService, HidServiceReceiver} from '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js';
+
+// Fake implementation of device.mojom.HidConnection. HidConnection represents
+// an open connection to a HID device and can be used to send and receive
+// reports.
+class FakeHidConnection {
+ constructor(client) {
+ this.client_ = client;
+ this.receiver_ = new HidConnectionReceiver(this);
+ this.expectedWrites_ = [];
+ this.expectedGetFeatureReports_ = [];
+ this.expectedSendFeatureReports_ = [];
+ }
+
+ bindNewPipeAndPassRemote() {
+ return this.receiver_.$.bindNewPipeAndPassRemote();
+ }
+
+ // Simulate an input report sent from the device to the host. The connection
+ // client's onInputReport method will be called with the provided |reportId|
+ // and |buffer|.
+ simulateInputReport(reportId, reportData) {
+ if (this.client_) {
+ this.client_.onInputReport(reportId, reportData);
+ }
+ }
+
+ // Specify the result for an expected call to write. If |success| is true the
+ // write will be successful, otherwise it will simulate a failure. The
+ // parameters of the next write call must match |reportId| and |buffer|.
+ queueExpectedWrite(success, reportId, reportData) {
+ this.expectedWrites_.push({
+ params: {reportId, data: reportData},
+ result: {success},
+ });
+ }
+
+ // Specify the result for an expected call to getFeatureReport. If |success|
+ // is true the operation is successful, otherwise it will simulate a failure.
+ // The parameter of the next getFeatureReport call must match |reportId|.
+ queueExpectedGetFeatureReport(success, reportId, reportData) {
+ this.expectedGetFeatureReports_.push({
+ params: {reportId},
+ result: {success, buffer: reportData},
+ });
+ }
+
+ // Specify the result for an expected call to sendFeatureReport. If |success|
+ // is true the operation is successful, otherwise it will simulate a failure.
+ // The parameters of the next sendFeatureReport call must match |reportId| and
+ // |buffer|.
+ queueExpectedSendFeatureReport(success, reportId, reportData) {
+ this.expectedSendFeatureReports_.push({
+ params: {reportId, data: reportData},
+ result: {success},
+ });
+ }
+
+ // Asserts that there are no more expected operations.
+ assertExpectationsMet() {
+ assert_equals(this.expectedWrites_.length, 0);
+ assert_equals(this.expectedGetFeatureReports_.length, 0);
+ assert_equals(this.expectedSendFeatureReports_.length, 0);
+ }
+
+ read() {}
+
+ // Implementation of HidConnection::Write. Causes an assertion failure if
+ // there are no expected write operations, or if the parameters do not match
+ // the expected call.
+ async write(reportId, buffer) {
+ let expectedWrite = this.expectedWrites_.shift();
+ assert_not_equals(expectedWrite, undefined);
+ assert_equals(reportId, expectedWrite.params.reportId);
+ let actual = new Uint8Array(buffer);
+ compareDataViews(
+ new DataView(actual.buffer, actual.byteOffset),
+ new DataView(
+ expectedWrite.params.data.buffer,
+ expectedWrite.params.data.byteOffset));
+ return expectedWrite.result;
+ }
+
+ // Implementation of HidConnection::GetFeatureReport. Causes an assertion
+ // failure if there are no expected write operations, or if the parameters do
+ // not match the expected call.
+ async getFeatureReport(reportId) {
+ let expectedGetFeatureReport = this.expectedGetFeatureReports_.shift();
+ assert_not_equals(expectedGetFeatureReport, undefined);
+ assert_equals(reportId, expectedGetFeatureReport.params.reportId);
+ return expectedGetFeatureReport.result;
+ }
+
+ // Implementation of HidConnection::SendFeatureReport. Causes an assertion
+ // failure if there are no expected write operations, or if the parameters do
+ // not match the expected call.
+ async sendFeatureReport(reportId, buffer) {
+ let expectedSendFeatureReport = this.expectedSendFeatureReports_.shift();
+ assert_not_equals(expectedSendFeatureReport, undefined);
+ assert_equals(reportId, expectedSendFeatureReport.params.reportId);
+ let actual = new Uint8Array(buffer);
+ compareDataViews(
+ new DataView(actual.buffer, actual.byteOffset),
+ new DataView(
+ expectedSendFeatureReport.params.data.buffer,
+ expectedSendFeatureReport.params.data.byteOffset));
+ return expectedSendFeatureReport.result;
+ }
+}
+
+
+// A fake implementation of the HidService mojo interface. HidService manages
+// HID device access for clients in the render process. Typically, when a client
+// requests access to a HID device a chooser dialog is shown with a list of
+// available HID devices. Selecting a device from the chooser also grants
+// permission for the client to access that device.
+//
+// The fake implementation allows tests to simulate connected devices. It also
+// skips the chooser dialog and instead allows tests to specify which device
+// should be selected. All devices are treated as if the user had already
+// granted permission. It is possible to revoke permission with forget() later.
+class FakeHidService {
+ constructor() {
+ this.interceptor_ = new MojoInterfaceInterceptor(HidService.$interfaceName);
+ this.interceptor_.oninterfacerequest = e => this.bind(e.handle);
+ this.receiver_ = new HidServiceReceiver(this);
+ this.nextGuidValue_ = 0;
+ this.simulateConnectFailure_ = false;
+ this.reset();
+ }
+
+ start() {
+ this.interceptor_.start();
+ }
+
+ stop() {
+ this.interceptor_.stop();
+ }
+
+ reset() {
+ this.devices_ = new Map();
+ this.allowedDevices_ = new Map();
+ this.fakeConnections_ = new Map();
+ this.selectedDevices_ = [];
+ }
+
+ // Creates and returns a HidDeviceInfo with the specified device IDs.
+ makeDevice(vendorId, productId) {
+ let guidValue = ++this.nextGuidValue_;
+ let info = new HidDeviceInfo();
+ info.guid = 'guid-' + guidValue.toString();
+ info.physicalDeviceId = 'physical-device-id-' + guidValue.toString();
+ info.vendorId = vendorId;
+ info.productId = productId;
+ info.productName = 'product name';
+ info.serialNumber = '0';
+ info.reportDescriptor = new Uint8Array();
+ info.collections = [];
+ info.deviceNode = 'device node';
+ return info;
+ }
+
+ // Simulates a connected device the client has already been granted permission
+ // to. Returns the key used to store the device in the map. The key is either
+ // the physical device ID, or the device GUID if it has no physical device ID.
+ addDevice(deviceInfo, grantPermission = true) {
+ let key = deviceInfo.physicalDeviceId;
+ if (key.length === 0)
+ key = deviceInfo.guid;
+
+ let devices = this.devices_.get(key) || [];
+ devices.push(deviceInfo);
+ this.devices_.set(key, devices);
+
+ if (grantPermission) {
+ let allowedDevices = this.allowedDevices_.get(key) || [];
+ allowedDevices.push(deviceInfo);
+ this.allowedDevices_.set(key, allowedDevices);
+ }
+
+ if (this.client_)
+ this.client_.deviceAdded(deviceInfo);
+ return key;
+ }
+
+ // Simulates disconnecting a connected device.
+ removeDevice(key) {
+ let devices = this.devices_.get(key);
+ this.devices_.delete(key);
+ if (this.client_ && devices) {
+ devices.forEach(deviceInfo => {
+ this.client_.deviceRemoved(deviceInfo);
+ });
+ }
+ }
+
+ // Simulates updating the device information for a connected device.
+ changeDevice(deviceInfo) {
+ let key = deviceInfo.physicalDeviceId;
+ if (key.length === 0)
+ key = deviceInfo.guid;
+
+ let devices = this.devices_.get(key) || [];
+ let i = devices.length;
+ while (i--) {
+ if (devices[i].guid == deviceInfo.guid)
+ devices.splice(i, 1);
+ }
+ devices.push(deviceInfo);
+ this.devices_.set(key, devices);
+
+ let allowedDevices = this.allowedDevices_.get(key) || [];
+ let j = allowedDevices.length;
+ while (j--) {
+ if (allowedDevices[j].guid == deviceInfo.guid)
+ allowedDevices.splice(j, 1);
+ }
+ allowedDevices.push(deviceInfo);
+ this.allowedDevices_.set(key, allowedDevices);
+
+ if (this.client_)
+ this.client_.deviceChanged(deviceInfo);
+ return key;
+ }
+
+ // Sets a flag that causes the next call to connect() to fail.
+ simulateConnectFailure() {
+ this.simulateConnectFailure_ = true;
+ }
+
+ // Sets the key of the device that will be returned as the selected item the
+ // next time requestDevice is called. The device with this key must have been
+ // previously added with addDevice.
+ setSelectedDevice(key) {
+ this.selectedDevices_ = this.devices_.get(key);
+ }
+
+ // Returns the fake HidConnection object for this device, if there is one. A
+ // connection is created once the device is opened.
+ getFakeConnection(guid) {
+ return this.fakeConnections_.get(guid);
+ }
+
+ bind(handle) {
+ this.receiver_.$.bindHandle(handle);
+ }
+
+ registerClient(client) {
+ this.client_ = client;
+ }
+
+ // Returns an array of connected devices the client has already been granted
+ // permission to access.
+ async getDevices() {
+ let devices = [];
+ this.allowedDevices_.forEach((value) => {
+ devices = devices.concat(value);
+ });
+ return {devices};
+ }
+
+ // Simulates a device chooser prompt, returning |selectedDevices_| as the
+ // simulated selection. |options| is ignored.
+ async requestDevice(options) {
+ return {devices: this.selectedDevices_};
+ }
+
+ // Returns a fake connection to the device with the specified GUID. If
+ // |connectionClient| is not null, its onInputReport method will be called
+ // when input reports are received. If simulateConnectFailure() was called
+ // then a null connection is returned instead, indicating failure.
+ async connect(guid, connectionClient) {
+ if (this.simulateConnectFailure_) {
+ this.simulateConnectFailure_ = false;
+ return {connection: null};
+ }
+ const fakeConnection = new FakeHidConnection(connectionClient);
+ this.fakeConnections_.set(guid, fakeConnection);
+ return {connection: fakeConnection.bindNewPipeAndPassRemote()};
+ }
+
+ // Removes the allowed device.
+ async forget(deviceInfo) {
+ for (const [key, value] of this.allowedDevices_) {
+ for (const device of value) {
+ if (device.guid == deviceInfo.guid) {
+ this.allowedDevices_.delete(key);
+ break;
+ }
+ }
+ }
+ return {success: true};
+ }
+}
+
+export const fakeHidService = new FakeHidService();
diff --git a/test/fixtures/wpt/resources/chromium/fake-serial.js b/test/fixtures/wpt/resources/chromium/fake-serial.js
new file mode 100644
index 00000000000..8614b4d64f9
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/fake-serial.js
@@ -0,0 +1,469 @@
+import {SerialPortFlushMode, SerialPortRemote, SerialReceiveError, SerialPortReceiver, SerialSendError} from '/gen/services/device/public/mojom/serial.mojom.m.js';
+import {SerialService, SerialServiceReceiver} from '/gen/third_party/blink/public/mojom/serial/serial.mojom.m.js';
+
+// Implementation of an UnderlyingSource to create a ReadableStream from a Mojo
+// data pipe consumer handle.
+class DataPipeSource {
+ constructor(consumer) {
+ this.consumer_ = consumer;
+ }
+
+ async pull(controller) {
+ let chunk = new ArrayBuffer(64);
+ let {result, numBytes} = this.consumer_.readData(chunk);
+ if (result == Mojo.RESULT_OK) {
+ controller.enqueue(new Uint8Array(chunk, 0, numBytes));
+ return;
+ } else if (result == Mojo.RESULT_FAILED_PRECONDITION) {
+ controller.close();
+ return;
+ } else if (result == Mojo.RESULT_SHOULD_WAIT) {
+ await this.readable();
+ return this.pull(controller);
+ }
+ }
+
+ cancel() {
+ if (this.watcher_)
+ this.watcher_.cancel();
+ this.consumer_.close();
+ }
+
+ readable() {
+ return new Promise((resolve) => {
+ this.watcher_ =
+ this.consumer_.watch({ readable: true, peerClosed: true }, () => {
+ this.watcher_.cancel();
+ this.watcher_ = undefined;
+ resolve();
+ });
+ });
+ }
+}
+
+// Implementation of an UnderlyingSink to create a WritableStream from a Mojo
+// data pipe producer handle.
+class DataPipeSink {
+ constructor(producer) {
+ this._producer = producer;
+ }
+
+ async write(chunk, controller) {
+ while (true) {
+ let {result, numBytes} = this._producer.writeData(chunk);
+ if (result == Mojo.RESULT_OK) {
+ if (numBytes == chunk.byteLength) {
+ return;
+ }
+ chunk = chunk.slice(numBytes);
+ } else if (result == Mojo.RESULT_FAILED_PRECONDITION) {
+ throw new DOMException('The pipe is closed.', 'InvalidStateError');
+ } else if (result == Mojo.RESULT_SHOULD_WAIT) {
+ await this.writable();
+ }
+ }
+ }
+
+ close() {
+ assert_equals(undefined, this._watcher);
+ this._producer.close();
+ }
+
+ abort(reason) {
+ if (this._watcher)
+ this._watcher.cancel();
+ this._producer.close();
+ }
+
+ writable() {
+ return new Promise((resolve) => {
+ this._watcher =
+ this._producer.watch({ writable: true, peerClosed: true }, () => {
+ this._watcher.cancel();
+ this._watcher = undefined;
+ resolve();
+ });
+ });
+ }
+}
+
+// Implementation of device.mojom.SerialPort.
+class FakeSerialPort {
+ constructor() {
+ this.inputSignals_ = {
+ dataCarrierDetect: false,
+ clearToSend: false,
+ ringIndicator: false,
+ dataSetReady: false
+ };
+ this.inputSignalFailure_ = false;
+ this.outputSignals_ = {
+ dataTerminalReady: false,
+ requestToSend: false,
+ break: false
+ };
+ this.outputSignalFailure_ = false;
+ }
+
+ open(options, client) {
+ if (this.receiver_ !== undefined) {
+ // Port already open.
+ return null;
+ }
+
+ let port = new SerialPortRemote();
+ this.receiver_ = new SerialPortReceiver(this);
+ this.receiver_.$.bindHandle(port.$.bindNewPipeAndPassReceiver().handle);
+
+ this.options_ = options;
+ this.client_ = client;
+ // OS typically sets DTR on open.
+ this.outputSignals_.dataTerminalReady = true;
+
+ return port;
+ }
+
+ write(data) {
+ return this.writer_.write(data);
+ }
+
+ read() {
+ return this.reader_.read();
+ }
+
+ // Reads from the port until at least |targetLength| is read or the stream is
+ // closed. The data is returned as a combined Uint8Array.
+ readWithLength(targetLength) {
+ return readWithLength(this.reader_, targetLength);
+ }
+
+ simulateReadError(error) {
+ this.writer_.close();
+ this.writer_.releaseLock();
+ this.writer_ = undefined;
+ this.writable_ = undefined;
+ this.client_.onReadError(error);
+ }
+
+ simulateParityError() {
+ this.simulateReadError(SerialReceiveError.PARITY_ERROR);
+ }
+
+ simulateDisconnectOnRead() {
+ this.simulateReadError(SerialReceiveError.DISCONNECTED);
+ }
+
+ simulateWriteError(error) {
+ this.reader_.cancel();
+ this.reader_ = undefined;
+ this.readable_ = undefined;
+ this.client_.onSendError(error);
+ }
+
+ simulateSystemErrorOnWrite() {
+ this.simulateWriteError(SerialSendError.SYSTEM_ERROR);
+ }
+
+ simulateDisconnectOnWrite() {
+ this.simulateWriteError(SerialSendError.DISCONNECTED);
+ }
+
+ simulateInputSignals(signals) {
+ this.inputSignals_ = signals;
+ }
+
+ simulateInputSignalFailure(fail) {
+ this.inputSignalFailure_ = fail;
+ }
+
+ get outputSignals() {
+ return this.outputSignals_;
+ }
+
+ simulateOutputSignalFailure(fail) {
+ this.outputSignalFailure_ = fail;
+ }
+
+ writable() {
+ if (this.writable_)
+ return Promise.resolve();
+
+ if (!this.writablePromise_) {
+ this.writablePromise_ = new Promise((resolve) => {
+ this.writableResolver_ = resolve;
+ });
+ }
+
+ return this.writablePromise_;
+ }
+
+ readable() {
+ if (this.readable_)
+ return Promise.resolve();
+
+ if (!this.readablePromise_) {
+ this.readablePromise_ = new Promise((resolve) => {
+ this.readableResolver_ = resolve;
+ });
+ }
+
+ return this.readablePromise_;
+ }
+
+ async startWriting(in_stream) {
+ this.readable_ = new ReadableStream(new DataPipeSource(in_stream));
+ this.reader_ = this.readable_.getReader();
+ if (this.readableResolver_) {
+ this.readableResolver_();
+ this.readableResolver_ = undefined;
+ this.readablePromise_ = undefined;
+ }
+ }
+
+ async startReading(out_stream) {
+ this.writable_ = new WritableStream(new DataPipeSink(out_stream));
+ this.writer_ = this.writable_.getWriter();
+ if (this.writableResolver_) {
+ this.writableResolver_();
+ this.writableResolver_ = undefined;
+ this.writablePromise_ = undefined;
+ }
+ }
+
+ async flush(mode) {
+ switch (mode) {
+ case SerialPortFlushMode.kReceive:
+ this.writer_.abort();
+ this.writer_.releaseLock();
+ this.writer_ = undefined;
+ this.writable_ = undefined;
+ break;
+ case SerialPortFlushMode.kTransmit:
+ if (this.reader_) {
+ this.reader_.cancel();
+ this.reader_ = undefined;
+ }
+ this.readable_ = undefined;
+ break;
+ }
+ }
+
+ async drain() {
+ await this.reader_.closed;
+ }
+
+ async getControlSignals() {
+ if (this.inputSignalFailure_) {
+ return {signals: null};
+ }
+
+ const signals = {
+ dcd: this.inputSignals_.dataCarrierDetect,
+ cts: this.inputSignals_.clearToSend,
+ ri: this.inputSignals_.ringIndicator,
+ dsr: this.inputSignals_.dataSetReady
+ };
+ return {signals};
+ }
+
+ async setControlSignals(signals) {
+ if (this.outputSignalFailure_) {
+ return {success: false};
+ }
+
+ if (signals.hasDtr) {
+ this.outputSignals_.dataTerminalReady = signals.dtr;
+ }
+ if (signals.hasRts) {
+ this.outputSignals_.requestToSend = signals.rts;
+ }
+ if (signals.hasBrk) {
+ this.outputSignals_.break = signals.brk;
+ }
+ return { success: true };
+ }
+
+ async configurePort(options) {
+ this.options_ = options;
+ return { success: true };
+ }
+
+ async getPortInfo() {
+ return {
+ bitrate: this.options_.bitrate,
+ dataBits: this.options_.datBits,
+ parityBit: this.options_.parityBit,
+ stopBits: this.options_.stopBits,
+ ctsFlowControl:
+ this.options_.hasCtsFlowControl && this.options_.ctsFlowControl,
+ };
+ }
+
+ async close() {
+ // OS typically clears DTR on close.
+ this.outputSignals_.dataTerminalReady = false;
+ if (this.writer_) {
+ this.writer_.close();
+ this.writer_.releaseLock();
+ this.writer_ = undefined;
+ }
+ this.writable_ = undefined;
+
+ // Close the receiver asynchronously so the reply to this message can be
+ // sent first.
+ const receiver = this.receiver_;
+ this.receiver_ = undefined;
+ setTimeout(() => {
+ receiver.$.close();
+ }, 0);
+
+ return {};
+ }
+}
+
+// Implementation of blink.mojom.SerialService.
+class FakeSerialService {
+ constructor() {
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(SerialService.$interfaceName);
+ this.interceptor_.oninterfacerequest = e => this.bind(e.handle);
+ this.receiver_ = new SerialServiceReceiver(this);
+ this.clients_ = [];
+ this.nextToken_ = 0;
+ this.reset();
+ }
+
+ start() {
+ this.interceptor_.start();
+ }
+
+ stop() {
+ this.interceptor_.stop();
+ }
+
+ reset() {
+ this.ports_ = new Map();
+ this.selectedPort_ = null;
+ }
+
+ addPort(info) {
+ let portInfo = {};
+ if (info?.usbVendorId !== undefined) {
+ portInfo.hasUsbVendorId = true;
+ portInfo.usbVendorId = info.usbVendorId;
+ }
+ if (info?.usbProductId !== undefined) {
+ portInfo.hasUsbProductId = true;
+ portInfo.usbProductId = info.usbProductId;
+ }
+ portInfo.connected = true;
+ if (info?.connected !== undefined) {
+ portInfo.connected = info.connected;
+ }
+
+ let token = ++this.nextToken_;
+ portInfo.token = {high: 0n, low: BigInt(token)};
+
+ let record = {
+ portInfo: portInfo,
+ fakePort: new FakeSerialPort(),
+ };
+ this.ports_.set(token, record);
+
+ if (portInfo.connected) {
+ for (let client of this.clients_) {
+ client.onPortConnectedStateChanged(portInfo);
+ }
+ }
+
+ return token;
+ }
+
+ removePort(token) {
+ let record = this.ports_.get(token);
+ if (record === undefined) {
+ return;
+ }
+
+ this.ports_.delete(token);
+
+ record.portInfo.connected = false;
+ for (let client of this.clients_) {
+ client.onPortConnectedStateChanged(record.portInfo);
+ }
+ }
+
+ setPortConnectedState(token, connected) {
+ let record = this.ports_.get(token);
+ if (record === undefined) {
+ return;
+ }
+
+ let was_connected = record.portInfo.connected;
+ if (was_connected === connected) {
+ return;
+ }
+
+ record.portInfo.connected = connected;
+ for (let client of this.clients_) {
+ client.onPortConnectedStateChanged(record.portInfo);
+ }
+ }
+
+ setSelectedPort(token) {
+ this.selectedPort_ = this.ports_.get(token);
+ }
+
+ getFakePort(token) {
+ let record = this.ports_.get(token);
+ if (record === undefined)
+ return undefined;
+ return record.fakePort;
+ }
+
+ bind(handle) {
+ this.receiver_.$.bindHandle(handle);
+ }
+
+ async setClient(client_remote) {
+ this.clients_.push(client_remote);
+ }
+
+ async getPorts() {
+ return {
+ ports: Array.from(this.ports_, ([token, record]) => record.portInfo)
+ };
+ }
+
+ async requestPort(filters) {
+ if (this.selectedPort_)
+ return { port: this.selectedPort_.portInfo };
+ else
+ return { port: null };
+ }
+
+ async openPort(token, options, client) {
+ let record = this.ports_.get(Number(token.low));
+ if (record !== undefined) {
+ return {port: record.fakePort.open(options, client)};
+ } else {
+ return {port: null};
+ }
+ }
+
+ async forgetPort(token) {
+ let record = this.ports_.get(Number(token.low));
+ if (record === undefined) {
+ return {success: false};
+ }
+
+ this.ports_.delete(Number(token.low));
+ if (record.fakePort.receiver_) {
+ record.fakePort.receiver_.$.close();
+ record.fakePort.receiver_ = undefined;
+ }
+ return {success: true};
+ }
+}
+
+export const fakeSerialService = new FakeSerialService();
diff --git a/test/fixtures/wpt/resources/chromium/mock-barcodedetection.js b/test/fixtures/wpt/resources/chromium/mock-barcodedetection.js
new file mode 100644
index 00000000000..b0d2e0af0ae
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-barcodedetection.js
@@ -0,0 +1,136 @@
+import {BarcodeDetectionReceiver, BarcodeFormat} from '/gen/services/shape_detection/public/mojom/barcodedetection.mojom.m.js';
+import {BarcodeDetectionProvider, BarcodeDetectionProviderReceiver} from '/gen/services/shape_detection/public/mojom/barcodedetection_provider.mojom.m.js';
+
+self.BarcodeDetectionTest = (() => {
+ // Class that mocks BarcodeDetectionProvider interface defined in
+ // https://cs.chromium.org/chromium/src/services/shape_detection/public/mojom/barcodedetection_provider.mojom
+ class MockBarcodeDetectionProvider {
+ constructor() {
+ this.receiver_ = new BarcodeDetectionProviderReceiver(this);
+
+ this.interceptor_ = new MojoInterfaceInterceptor(
+ BarcodeDetectionProvider.$interfaceName);
+ this.interceptor_.oninterfacerequest = e => {
+ if (this.should_close_pipe_on_request_)
+ e.handle.close();
+ else
+ this.receiver_.$.bindHandle(e.handle);
+ }
+ this.interceptor_.start();
+ this.should_close_pipe_on_request_ = false;
+ }
+
+ createBarcodeDetection(request, options) {
+ this.mockService_ = new MockBarcodeDetection(request, options);
+ }
+
+ enumerateSupportedFormats() {
+ return {
+ supportedFormats: [
+ BarcodeFormat.AZTEC,
+ BarcodeFormat.DATA_MATRIX,
+ BarcodeFormat.QR_CODE,
+ ]
+ };
+ }
+
+ getFrameData() {
+ return this.mockService_.bufferData_;
+ }
+
+ getFormats() {
+ return this.mockService_.options_.formats;
+ }
+
+ reset() {
+ this.mockService_ = null;
+ this.should_close_pipe_on_request_ = false;
+ this.receiver_.$.close();
+ this.interceptor_.stop();
+ }
+
+ // simulate a 'no implementation available' case
+ simulateNoImplementation() {
+ this.should_close_pipe_on_request_ = true;
+ }
+ }
+
+ // Class that mocks BarcodeDetection interface defined in
+ // https://cs.chromium.org/chromium/src/services/shape_detection/public/mojom/barcodedetection.mojom
+ class MockBarcodeDetection {
+ constructor(request, options) {
+ this.options_ = options;
+ this.receiver_ = new BarcodeDetectionReceiver(this);
+ this.receiver_.$.bindHandle(request.handle);
+ }
+
+ detect(bitmapData) {
+ this.bufferData_ =
+ new Uint32Array(getArrayBufferFromBigBuffer(bitmapData.pixelData));
+ return {
+ results: [
+ {
+ rawValue : "cats",
+ boundingBox: { x: 1.0, y: 1.0, width: 100.0, height: 100.0 },
+ format: BarcodeFormat.QR_CODE,
+ cornerPoints: [
+ { x: 1.0, y: 1.0 },
+ { x: 101.0, y: 1.0 },
+ { x: 101.0, y: 101.0 },
+ { x: 1.0, y: 101.0 }
+ ],
+ },
+ {
+ rawValue : "dogs",
+ boundingBox: { x: 2.0, y: 2.0, width: 50.0, height: 50.0 },
+ format: BarcodeFormat.CODE_128,
+ cornerPoints: [
+ { x: 2.0, y: 2.0 },
+ { x: 52.0, y: 2.0 },
+ { x: 52.0, y: 52.0 },
+ { x: 2.0, y: 52.0 }
+ ],
+ },
+ ],
+ };
+ }
+ }
+
+ let testInternal = {
+ initialized: false,
+ MockBarcodeDetectionProvider: null
+ }
+
+ class BarcodeDetectionTestChromium {
+ constructor() {
+ Object.freeze(this); // Make it immutable.
+ }
+
+ initialize() {
+ if (testInternal.initialized)
+ throw new Error('Call reset() before initialize().');
+
+ testInternal.MockBarcodeDetectionProvider = new MockBarcodeDetectionProvider;
+ testInternal.initialized = true;
+ }
+
+ // Resets state of barcode detection mocks between test runs.
+ async reset() {
+ if (!testInternal.initialized)
+ throw new Error('Call initialize() before reset().');
+ testInternal.MockBarcodeDetectionProvider.reset();
+ testInternal.MockBarcodeDetectionProvider = null;
+ testInternal.initialized = false;
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ MockBarcodeDetectionProvider() {
+ return testInternal.MockBarcodeDetectionProvider;
+ }
+ }
+
+ return BarcodeDetectionTestChromium;
+})();
+
+self.BarcodeFormat = BarcodeFormat;
diff --git a/test/fixtures/wpt/resources/chromium/mock-barcodedetection.js.headers b/test/fixtures/wpt/resources/chromium/mock-barcodedetection.js.headers
new file mode 100644
index 00000000000..6c61a34a4ec
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-barcodedetection.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
\ No newline at end of file
diff --git a/test/fixtures/wpt/resources/chromium/mock-battery-monitor.headers b/test/fixtures/wpt/resources/chromium/mock-battery-monitor.headers
new file mode 100644
index 00000000000..6805c323df5
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-battery-monitor.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/wpt/resources/chromium/mock-battery-monitor.js b/test/fixtures/wpt/resources/chromium/mock-battery-monitor.js
new file mode 100644
index 00000000000..8fa27bc56a1
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-battery-monitor.js
@@ -0,0 +1,61 @@
+import {BatteryMonitor, BatteryMonitorReceiver} from '/gen/services/device/public/mojom/battery_monitor.mojom.m.js';
+
+class MockBatteryMonitor {
+ constructor() {
+ this.receiver_ = new BatteryMonitorReceiver(this);
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(BatteryMonitor.$interfaceName);
+ this.interceptor_.oninterfacerequest = e =>
+ this.receiver_.$.bindHandle(e.handle);
+ this.reset();
+ }
+
+ start() {
+ this.interceptor_.start();
+ }
+
+ stop() {
+ this.interceptor_.stop();
+ }
+
+ reset() {
+ this.pendingRequests_ = [];
+ this.status_ = null;
+ this.lastKnownStatus_ = null;
+ }
+
+ queryNextStatus() {
+ const result = new Promise(resolve => this.pendingRequests_.push(resolve));
+ this.runCallbacks_();
+ return result;
+ }
+
+ setBatteryStatus(charging, chargingTime, dischargingTime, level) {
+ this.status_ = {charging, chargingTime, dischargingTime, level};
+ this.lastKnownStatus_ = this.status_;
+ this.runCallbacks_();
+ }
+
+ verifyBatteryStatus(manager) {
+ assert_not_equals(manager, undefined);
+ assert_not_equals(this.lastKnownStatus_, null);
+ assert_equals(manager.charging, this.lastKnownStatus_.charging);
+ assert_equals(manager.chargingTime, this.lastKnownStatus_.chargingTime);
+ assert_equals(
+ manager.dischargingTime, this.lastKnownStatus_.dischargingTime);
+ assert_equals(manager.level, this.lastKnownStatus_.level);
+ }
+
+ runCallbacks_() {
+ if (!this.status_ || !this.pendingRequests_.length)
+ return;
+
+ let result = {status: this.status_};
+ while (this.pendingRequests_.length) {
+ this.pendingRequests_.pop()(result);
+ }
+ this.status_ = null;
+ }
+}
+
+export const mockBatteryMonitor = new MockBatteryMonitor();
diff --git a/test/fixtures/wpt/resources/chromium/mock-facedetection.js b/test/fixtures/wpt/resources/chromium/mock-facedetection.js
new file mode 100644
index 00000000000..7ae658621ee
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-facedetection.js
@@ -0,0 +1,130 @@
+import {FaceDetectionReceiver, LandmarkType} from '/gen/services/shape_detection/public/mojom/facedetection.mojom.m.js';
+import {FaceDetectionProvider, FaceDetectionProviderReceiver} from '/gen/services/shape_detection/public/mojom/facedetection_provider.mojom.m.js';
+
+self.FaceDetectionTest = (() => {
+ // Class that mocks FaceDetectionProvider interface defined in
+ // https://cs.chromium.org/chromium/src/services/shape_detection/public/mojom/facedetection_provider.mojom
+ class MockFaceDetectionProvider {
+ constructor() {
+ this.receiver_ = new FaceDetectionProviderReceiver(this);
+
+ this.interceptor_ = new MojoInterfaceInterceptor(
+ FaceDetectionProvider.$interfaceName);
+ this.interceptor_.oninterfacerequest =
+ e => this.receiver_.$.bindHandle(e.handle);
+ this.interceptor_.start();
+ }
+
+ createFaceDetection(request, options) {
+ this.mockService_ = new MockFaceDetection(request, options);
+ }
+
+ getFrameData() {
+ return this.mockService_.bufferData_;
+ }
+
+ getMaxDetectedFaces() {
+ return this.mockService_.maxDetectedFaces_;
+ }
+
+ getFastMode () {
+ return this.mockService_.fastMode_;
+ }
+
+ reset() {
+ this.mockService_ = null;
+ this.receiver_.$.close();
+ this.interceptor_.stop();
+ }
+ }
+
+ // Class that mocks FaceDetection interface defined in
+ // https://cs.chromium.org/chromium/src/services/shape_detection/public/mojom/facedetection.mojom
+ class MockFaceDetection {
+ constructor(request, options) {
+ this.maxDetectedFaces_ = options.maxDetectedFaces;
+ this.fastMode_ = options.fastMode;
+ this.receiver_ = new FaceDetectionReceiver(this);
+ this.receiver_.$.bindHandle(request.handle);
+ }
+
+ detect(bitmapData) {
+ this.bufferData_ =
+ new Uint32Array(getArrayBufferFromBigBuffer(bitmapData.pixelData));
+ return Promise.resolve({
+ results: [
+ {
+ boundingBox: {x: 1.0, y: 1.0, width: 100.0, height: 100.0},
+ landmarks: [{
+ type: LandmarkType.EYE,
+ locations: [{x: 4.0, y: 5.0}]
+ },
+ {
+ type: LandmarkType.EYE,
+ locations: [
+ {x: 4.0, y: 5.0}, {x: 5.0, y: 4.0}, {x: 6.0, y: 3.0},
+ {x: 7.0, y: 4.0}, {x: 8.0, y: 5.0}, {x: 7.0, y: 6.0},
+ {x: 6.0, y: 7.0}, {x: 5.0, y: 6.0}
+ ]
+ }]
+ },
+ {
+ boundingBox: {x: 2.0, y: 2.0, width: 200.0, height: 200.0},
+ landmarks: [{
+ type: LandmarkType.NOSE,
+ locations: [{x: 100.0, y: 50.0}]
+ },
+ {
+ type: LandmarkType.NOSE,
+ locations: [
+ {x: 80.0, y: 50.0}, {x: 70.0, y: 60.0}, {x: 60.0, y: 70.0},
+ {x: 70.0, y: 60.0}, {x: 80.0, y: 70.0}, {x: 90.0, y: 80.0},
+ {x: 100.0, y: 70.0}, {x: 90.0, y: 60.0}, {x: 80.0, y: 50.0}
+ ]
+ }]
+ },
+ {
+ boundingBox: {x: 3.0, y: 3.0, width: 300.0, height: 300.0},
+ landmarks: []
+ },
+ ]
+ });
+ }
+ }
+
+ let testInternal = {
+ initialized: false,
+ MockFaceDetectionProvider: null
+ }
+
+ class FaceDetectionTestChromium {
+ constructor() {
+ Object.freeze(this); // Make it immutable.
+ }
+
+ initialize() {
+ if (testInternal.initialized)
+ throw new Error('Call reset() before initialize().');
+
+ testInternal.MockFaceDetectionProvider = new MockFaceDetectionProvider;
+ testInternal.initialized = true;
+ }
+
+ // Resets state of face detection mocks between test runs.
+ async reset() {
+ if (!testInternal.initialized)
+ throw new Error('Call initialize() before reset().');
+ testInternal.MockFaceDetectionProvider.reset();
+ testInternal.MockFaceDetectionProvider = null;
+ testInternal.initialized = false;
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ MockFaceDetectionProvider() {
+ return testInternal.MockFaceDetectionProvider;
+ }
+ }
+
+ return FaceDetectionTestChromium;
+})();
diff --git a/test/fixtures/wpt/resources/chromium/mock-facedetection.js.headers b/test/fixtures/wpt/resources/chromium/mock-facedetection.js.headers
new file mode 100644
index 00000000000..6c61a34a4ec
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-facedetection.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
\ No newline at end of file
diff --git a/test/fixtures/wpt/resources/chromium/mock-idle-detection.js b/test/fixtures/wpt/resources/chromium/mock-idle-detection.js
new file mode 100644
index 00000000000..54fe5dd01e8
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-idle-detection.js
@@ -0,0 +1,80 @@
+import {IdleManager, IdleManagerError, IdleManagerReceiver} from '/gen/third_party/blink/public/mojom/idle/idle_manager.mojom.m.js';
+
+/**
+ * This is a testing framework that enables us to test the user idle detection
+ * by intercepting the connection between the renderer and the browser and
+ * exposing a mocking API for tests.
+ *
+ * Usage:
+ *
+ * 1) Include in your file.
+ * 2) Set expectations
+ * expect(addMonitor).andReturn((threshold, monitorPtr, callback) => {
+ * // mock behavior
+ * })
+ * 3) Call navigator.idle.query()
+ *
+ * The mocking API is blink agnostic and is designed such that other engines
+ * could implement it too. Here are the symbols that are exposed to tests:
+ *
+ * - function addMonitor(): the main/only function that can be mocked.
+ * - function expect(): the main/only function that enables us to mock it.
+ * - function close(): disconnects the interceptor.
+ * - enum UserIdleState {IDLE, ACTIVE}: blink agnostic constants.
+ * - enum ScreenIdleState {LOCKED, UNLOCKED}: blink agnostic constants.
+ */
+
+class FakeIdleMonitor {
+ addMonitor(threshold, monitorPtr, callback) {
+ return this.handler.addMonitor(threshold, monitorPtr);
+ }
+ setHandler(handler) {
+ this.handler = handler;
+ return this;
+ }
+ setBinding(binding) {
+ this.binding = binding;
+ return this;
+ }
+ close() {
+ this.binding.$.close();
+ }
+}
+
+self.IdleDetectorError = {};
+
+self.addMonitor = function addMonitor(threshold, monitorPtr, callback) {
+ throw new Error("expected to be overriden by tests");
+}
+
+async function close() {
+ interceptor.close();
+}
+
+self.expect = function(call) {
+ return {
+ andReturn(callback) {
+ let handler = {};
+ handler[call.name] = callback;
+ interceptor.setHandler(handler);
+ }
+ };
+};
+
+function intercept() {
+ let result = new FakeIdleMonitor();
+
+ let binding = new IdleManagerReceiver(result);
+ let interceptor = new MojoInterfaceInterceptor(IdleManager.$interfaceName);
+ interceptor.oninterfacerequest = e => binding.$.bindHandle(e.handle);
+ interceptor.start();
+
+ self.IdleDetectorError.SUCCESS = IdleManagerError.kSuccess;
+ self.IdleDetectorError.PERMISSION_DISABLED =
+ IdleManagerError.kPermissionDisabled;
+
+ result.setBinding(binding);
+ return result;
+}
+
+const interceptor = intercept();
diff --git a/test/fixtures/wpt/resources/chromium/mock-imagecapture.js b/test/fixtures/wpt/resources/chromium/mock-imagecapture.js
new file mode 100644
index 00000000000..8424e1e36c8
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-imagecapture.js
@@ -0,0 +1,309 @@
+import {BackgroundBlurMode, FillLightMode, ImageCapture, ImageCaptureReceiver, MeteringMode, RedEyeReduction} from '/gen/media/capture/mojom/image_capture.mojom.m.js';
+
+self.ImageCaptureTest = (() => {
+ // Class that mocks ImageCapture interface defined in
+ // https://cs.chromium.org/chromium/src/media/capture/mojom/image_capture.mojom
+ class MockImageCapture {
+ constructor() {
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(ImageCapture.$interfaceName);
+ this.interceptor_.oninterfacerequest =
+ e => this.receiver_.$.bindHandle(e.handle);
+ this.interceptor_.start();
+
+ this.state_ = {
+ state: {
+ supportedWhiteBalanceModes: [
+ MeteringMode.SINGLE_SHOT,
+ MeteringMode.CONTINUOUS
+ ],
+ currentWhiteBalanceMode: MeteringMode.CONTINUOUS,
+ supportedExposureModes: [
+ MeteringMode.MANUAL,
+ MeteringMode.SINGLE_SHOT,
+ MeteringMode.CONTINUOUS
+ ],
+ currentExposureMode: MeteringMode.MANUAL,
+ supportedFocusModes: [
+ MeteringMode.MANUAL,
+ MeteringMode.SINGLE_SHOT
+ ],
+ currentFocusMode: MeteringMode.MANUAL,
+ pointsOfInterest: [{
+ x: 0.4,
+ y: 0.6
+ }],
+
+ exposureCompensation: {
+ min: -200.0,
+ max: 200.0,
+ current: 33.0,
+ step: 33.0
+ },
+ exposureTime: {
+ min: 100.0,
+ max: 100000.0,
+ current: 1000.0,
+ step: 100.0
+ },
+ colorTemperature: {
+ min: 2500.0,
+ max: 6500.0,
+ current: 6000.0,
+ step: 1000.0
+ },
+ iso: {
+ min: 100.0,
+ max: 12000.0,
+ current: 400.0,
+ step: 1.0
+ },
+
+ brightness: {
+ min: 1.0,
+ max: 10.0,
+ current: 5.0,
+ step: 1.0
+ },
+ contrast: {
+ min: 2.0,
+ max: 9.0,
+ current: 5.0,
+ step: 1.0
+ },
+ saturation: {
+ min: 3.0,
+ max: 8.0,
+ current: 6.0,
+ step: 1.0
+ },
+ sharpness: {
+ min: 4.0,
+ max: 7.0,
+ current: 7.0,
+ step: 1.0
+ },
+
+ focusDistance: {
+ min: 1.0,
+ max: 10.0,
+ current: 3.0,
+ step: 1.0
+ },
+
+ pan: {
+ min: 0.0,
+ max: 10.0,
+ current: 5.0,
+ step: 2.0
+ },
+
+ tilt: {
+ min: 0.0,
+ max: 10.0,
+ current: 5.0,
+ step: 2.0
+ },
+
+ zoom: {
+ min: 0.0,
+ max: 10.0,
+ current: 5.0,
+ step: 5.0
+ },
+
+ supportsTorch: true,
+ torch: false,
+
+ redEyeReduction: RedEyeReduction.CONTROLLABLE,
+ height: {
+ min: 240.0,
+ max: 2448.0,
+ current: 240.0,
+ step: 2.0
+ },
+ width: {
+ min: 320.0,
+ max: 3264.0,
+ current: 320.0,
+ step: 3.0
+ },
+ fillLightMode: [FillLightMode.AUTO, FillLightMode.FLASH],
+
+ supportedBackgroundBlurModes: [
+ BackgroundBlurMode.OFF,
+ BackgroundBlurMode.BLUR
+ ],
+ backgroundBlurMode: BackgroundBlurMode.OFF,
+ }
+ };
+ this.panTiltZoomPermissionStatus_ = null;
+ this.settings_ = null;
+ this.receiver_ = new ImageCaptureReceiver(this);
+ }
+
+ reset() {
+ this.receiver_.$.close();
+ this.interceptor_.stop();
+ }
+
+ async getPhotoState(source_id) {
+ const shouldKeepPanTiltZoom = await this.isPanTiltZoomPermissionGranted();
+ if (shouldKeepPanTiltZoom)
+ return Promise.resolve(this.state_);
+
+ const newState = {...this.state_};
+ newState.state.pan = {};
+ newState.state.tilt = {};
+ newState.state.zoom = {};
+ return Promise.resolve(newState);
+ }
+
+ async setPhotoOptions(source_id, settings) {
+ const isAllowedToControlPanTiltZoom = await this.isPanTiltZoomPermissionGranted();
+ if (!isAllowedToControlPanTiltZoom &&
+ (settings.hasPan || settings.hasTilt || settings.hasZoom)) {
+ return Promise.resolve({ success: false });
+ }
+ this.settings_ = settings;
+ if (settings.hasIso)
+ this.state_.state.iso.current = settings.iso;
+ if (settings.hasHeight)
+ this.state_.state.height.current = settings.height;
+ if (settings.hasWidth)
+ this.state_.state.width.current = settings.width;
+ if (settings.hasPan)
+ this.state_.state.pan.current = settings.pan;
+ if (settings.hasTilt)
+ this.state_.state.tilt.current = settings.tilt;
+ if (settings.hasZoom)
+ this.state_.state.zoom.current = settings.zoom;
+ if (settings.hasFocusMode)
+ this.state_.state.currentFocusMode = settings.focusMode;
+ if (settings.hasFocusDistance)
+ this.state_.state.focusDistance.current = settings.focusDistance;
+
+ if (settings.pointsOfInterest.length > 0) {
+ this.state_.state.pointsOfInterest =
+ settings.pointsOfInterest;
+ }
+
+ if (settings.hasExposureMode)
+ this.state_.state.currentExposureMode = settings.exposureMode;
+
+ if (settings.hasExposureCompensation) {
+ this.state_.state.exposureCompensation.current =
+ settings.exposureCompensation;
+ }
+ if (settings.hasExposureTime) {
+ this.state_.state.exposureTime.current =
+ settings.exposureTime;
+ }
+ if (settings.hasWhiteBalanceMode) {
+ this.state_.state.currentWhiteBalanceMode =
+ settings.whiteBalanceMode;
+ }
+ if (settings.hasFillLightMode)
+ this.state_.state.fillLightMode = [settings.fillLightMode];
+ if (settings.hasRedEyeReduction)
+ this.state_.state.redEyeReduction = settings.redEyeReduction;
+ if (settings.hasColorTemperature) {
+ this.state_.state.colorTemperature.current =
+ settings.colorTemperature;
+ }
+ if (settings.hasBrightness)
+ this.state_.state.brightness.current = settings.brightness;
+ if (settings.hasContrast)
+ this.state_.state.contrast.current = settings.contrast;
+ if (settings.hasSaturation)
+ this.state_.state.saturation.current = settings.saturation;
+ if (settings.hasSharpness)
+ this.state_.state.sharpness.current = settings.sharpness;
+
+ if (settings.hasTorch)
+ this.state_.state.torch = settings.torch;
+
+ if (settings.hasBackgroundBlurMode)
+ this.state_.state.backgroundBlurMode = [settings.backgroundBlurMode];
+
+ return Promise.resolve({
+ success: true
+ });
+ }
+
+ takePhoto(source_id) {
+ return Promise.resolve({
+ blob: {
+ mimeType: 'image/cat',
+ data: new Array(2)
+ }
+ });
+ }
+
+ async isPanTiltZoomPermissionGranted() {
+ if (!this.panTiltZoomPermissionStatus_) {
+ this.panTiltZoomPermissionStatus_ = await navigator.permissions.query({
+ name: "camera",
+ panTiltZoom: true
+ });
+ }
+ return this.panTiltZoomPermissionStatus_.state == "granted";
+ }
+
+ state() {
+ return this.state_.state;
+ }
+
+ turnOffBackgroundBlurMode() {
+ this.state_.state.backgroundBlurMode = BackgroundBlurMode.OFF;
+ }
+ turnOnBackgroundBlurMode() {
+ this.state_.state.backgroundBlurMode = BackgroundBlurMode.BLUR;
+ }
+ turnOffSupportedBackgroundBlurModes() {
+ this.state_.state.supportedBackgroundBlurModes = [BackgroundBlurMode.OFF];
+ }
+ turnOnSupportedBackgroundBlurModes() {
+ this.state_.state.supportedBackgroundBlurModes = [BackgroundBlurMode.BLUR];
+ }
+
+ options() {
+ return this.settings_;
+ }
+ }
+
+ let testInternal = {
+ initialized: false,
+ mockImageCapture: null
+ }
+
+ class ImageCaptureTestChromium {
+
+ constructor() {
+ Object.freeze(this); // Make it immutable.
+ }
+
+ initialize() {
+ if (testInternal.initialized)
+ throw new Error('Call reset() before initialize().');
+
+ testInternal.mockImageCapture = new MockImageCapture;
+ testInternal.initialized = true;
+ }
+ // Resets state of image capture mocks between test runs.
+ async reset() {
+ if (!testInternal.initialized)
+ throw new Error('Call initialize() before reset().');
+ testInternal.mockImageCapture.reset();
+ testInternal.mockImageCapture = null;
+ testInternal.initialized = false;
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+ mockImageCapture() {
+ return testInternal.mockImageCapture;
+ }
+ }
+
+ return ImageCaptureTestChromium;
+})();
diff --git a/test/fixtures/wpt/resources/chromium/mock-managed-config.js b/test/fixtures/wpt/resources/chromium/mock-managed-config.js
new file mode 100644
index 00000000000..c9980e1285b
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-managed-config.js
@@ -0,0 +1,91 @@
+'use strict'
+
+import{ManagedConfigurationObserverRemote, ManagedConfigurationService, ManagedConfigurationServiceReceiver} from '/gen/third_party/blink/public/mojom/device/device.mojom.m.js';
+
+
+self.ManagedConfigTest = (() => {
+ // Class that mocks ManagedConfigurationService interface defined in
+ // https://source.chromium.org/chromium/chromium/src/third_party/blink/public/mojom/device/device.mojom
+ class MockManagedConfig {
+ constructor() {
+ this.receiver_ = new ManagedConfigurationServiceReceiver(this);
+ this.interceptor_ = new MojoInterfaceInterceptor(
+ ManagedConfigurationService.$interfaceName);
+ this.interceptor_.oninterfacerequest = e =>
+ this.receiver_.$.bindHandle(e.handle);
+ this.interceptor_.start();
+ this.subscription_ = null;
+ this.reset();
+ }
+
+ reset() {
+ this.configuration_ = null;
+ this.onObserverAdd_ = null;
+ }
+
+ async getManagedConfiguration(keys) {
+ if (this.configuration_ === null) {
+ return {};
+ }
+
+ return {
+ configurations: Object.keys(this.configuration_)
+ .filter(key => keys.includes(key))
+ .reduce(
+ (obj, key) => {
+ obj[key] =
+ JSON.stringify(this.configuration_[key]);
+ return obj;
+ },
+ {})
+ };
+ }
+
+ subscribeToManagedConfiguration(remote) {
+ this.subscription_ = remote;
+ if (this.onObserverAdd_ !== null) {
+ this.onObserverAdd_();
+ }
+ }
+
+ setManagedConfig(value) {
+ this.configuration_ = value;
+ if (this.subscription_ !== null) {
+ this.subscription_.onConfigurationChanged();
+ }
+ }
+ }
+
+ let testInternal = {
+ initialized: false,
+ mockManagedConfig: null
+ }
+
+ class ManagedConfigTestChromium {
+ constructor() {
+ Object.freeze(this); // Make it immutable.
+ }
+
+ initialize() {
+ if (testInternal.mockManagedConfig !== null) {
+ testInternal.mockManagedConfig.reset();
+ return;
+ }
+
+ testInternal.mockManagedConfig = new MockManagedConfig;
+ testInternal.initialized = true;
+ }
+
+ setManagedConfig(config) {
+ testInternal.mockManagedConfig.setManagedConfig(config);
+ }
+
+ async nextObserverAdded() {
+ await new Promise(resolve => {
+ testInternal.mockManagedConfig.onObserverAdd_ = resolve;
+ });
+ }
+ }
+
+ return ManagedConfigTestChromium;
+})();
diff --git a/test/fixtures/wpt/resources/chromium/mock-pressure-service.js b/test/fixtures/wpt/resources/chromium/mock-pressure-service.js
new file mode 100644
index 00000000000..610d02a9164
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-pressure-service.js
@@ -0,0 +1,131 @@
+import {PressureClientRemote, PressureManagerAddClientError} from "/gen/services/device/public/mojom/pressure_manager.mojom.m.js";
+import {PressureSource, PressureState} from '/gen/services/device/public/mojom/pressure_update.mojom.m.js'
+import {WebPressureManager, WebPressureManagerReceiver} from '/gen/third_party/blink/public/mojom/compute_pressure/web_pressure_manager.mojom.m.js'
+
+class MockWebPressureService {
+ constructor() {
+ this.receiver_ = new WebPressureManagerReceiver(this);
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(WebPressureManager.$interfaceName);
+ this.interceptor_.oninterfacerequest = e => {
+ this.receiver_.$.bindHandle(e.handle);
+ };
+ this.reset();
+ this.mojomSourceType_ = new Map([['cpu', PressureSource.kCpu]]);
+ this.mojomStateType_ = new Map([
+ ['nominal', PressureState.kNominal], ['fair', PressureState.kFair],
+ ['serious', PressureState.kSerious], ['critical', PressureState.kCritical]
+ ]);
+ this.pressureServiceReadingTimerId_ = null;
+ }
+
+ start() {
+ this.interceptor_.start();
+ }
+
+ stop() {
+ this.stopPlatformCollector();
+ this.receiver_.$.close();
+ this.interceptor_.stop();
+
+ // Wait for an event loop iteration to let any pending mojo commands in
+ // the pressure service finish.
+ return new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ reset() {
+ this.observers_ = [];
+ this.pressureUpdate_ = null;
+ this.pressureServiceReadingTimerId_ = null;
+ this.addClientError_ = null;
+ this.updatesDelivered_ = 0;
+ }
+
+ async addClient(source) {
+ // TODO(crbug.com/1342184): Consider other sources.
+ // For now, "cpu" is the only source.
+ if (source !== PressureSource.kCpu)
+ throw new Error('Call addClient() with a wrong PressureSource');
+
+ if (this.addClientError_ !== null) {
+ return {result: {error: this.addClientError_}};
+ }
+
+ const pressureClientRemote = new PressureClientRemote();
+ pressureClientRemote.onConnectionError.addListener(() => {
+ // Remove this observer from observer array.
+ this.observers_.splice(this.observers_.indexOf(pressureClientRemote), 1);
+ });
+ const pendingReceiver = pressureClientRemote.$.bindNewPipeAndPassReceiver();
+ this.observers_.push(pressureClientRemote);
+
+ return {result: {pressureClient: pendingReceiver}};
+ }
+
+ startPlatformCollector(sampleInterval) {
+ if (sampleInterval === 0)
+ return;
+
+ if (this.pressureServiceReadingTimerId_ != null)
+ this.stopPlatformCollector();
+
+ this.pressureServiceReadingTimerId_ = self.setInterval(() => {
+ if (this.pressureUpdate_ === null || this.observers_.length === 0)
+ return;
+
+ // Because we cannot retrieve directly the timeOrigin internal in
+ // TimeTicks from Chromium, we need to create a timestamp that
+ // would help to test basic functionality.
+ // by multiplying performance.timeOrigin by 10 we make sure that the
+ // origin is higher than the internal time origin in TimeTicks.
+ // performance.now() allows us to have an increase matching the TimeTicks
+ // that would be read from the platform collector.
+ this.pressureUpdate_.timestamp = {
+ internalValue:
+ Math.round((performance.timeOrigin * 10) + performance.now()) * 1000
+ };
+ for (let observer of this.observers_)
+ observer.onPressureUpdated(this.pressureUpdate_);
+ this.updatesDelivered_++;
+ }, sampleInterval);
+ }
+
+ stopPlatformCollector() {
+ if (this.pressureServiceReadingTimerId_ != null) {
+ self.clearInterval(this.pressureServiceReadingTimerId_);
+ this.pressureServiceReadingTimerId_ = null;
+ }
+ this.updatesDelivered_ = 0;
+ }
+
+ updatesDelivered() {
+ return this.updatesDelivered_;
+ }
+
+ setPressureUpdate(source, state) {
+ if (!this.mojomSourceType_.has(source))
+ throw new Error(`PressureSource '${source}' is invalid`);
+
+ if (!this.mojomStateType_.has(state))
+ throw new Error(`PressureState '${state}' is invalid`);
+
+ this.pressureUpdate_ = {
+ source: this.mojomSourceType_.get(source),
+ state: this.mojomStateType_.get(state),
+ };
+ }
+
+ setExpectedFailure(expectedException) {
+ assert_true(
+ expectedException instanceof DOMException,
+ 'setExpectedFailure() expects a DOMException instance');
+ if (expectedException.name === 'NotSupportedError') {
+ this.addClientError_ = PressureManagerAddClientError.kNotSupported;
+ } else {
+ throw new TypeError(
+ `Unexpected DOMException '${expectedException.name}'`);
+ }
+ }
+}
+
+export const mockPressureService = new MockWebPressureService();
diff --git a/test/fixtures/wpt/resources/chromium/mock-pressure-service.js.headers b/test/fixtures/wpt/resources/chromium/mock-pressure-service.js.headers
new file mode 100644
index 00000000000..6805c323df5
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-pressure-service.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/wpt/resources/chromium/mock-subapps.js b/test/fixtures/wpt/resources/chromium/mock-subapps.js
new file mode 100644
index 00000000000..b81936713b1
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-subapps.js
@@ -0,0 +1,89 @@
+'use strict';
+
+import {SubAppsService, SubAppsServiceReceiver, SubAppsServiceResultCode} from '/gen/third_party/blink/public/mojom/subapps/sub_apps_service.mojom.m.js';
+
+self.SubAppsServiceTest = (() => {
+ // Class that mocks SubAppsService interface defined in /third_party/blink/public/mojom/subapps/sub_apps_service.mojom
+
+ class MockSubAppsService {
+ constructor() {
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(SubAppsService.$interfaceName);
+ this.receiver_ = new SubAppsServiceReceiver(this);
+ this.interceptor_.oninterfacerequest =
+ e => this.receiver_.$.bindHandle(e.handle);
+ this.interceptor_.start();
+ }
+
+ reset() {
+ this.interceptor_.stop();
+ this.receiver_.$.close();
+ }
+
+ add(sub_apps) {
+ return Promise.resolve({
+ result: testInternal.addCallReturnValue,
+ });
+ }
+
+ list() {
+ return Promise.resolve({
+ result: {
+ resultCode: testInternal.serviceResultCode,
+ subAppsList: testInternal.listCallReturnValue,
+ }
+ });
+ }
+
+ remove() {
+ return Promise.resolve({
+ result: testInternal.removeCallReturnValue,
+ });
+ }
+ }
+
+ let testInternal = {
+ initialized: false,
+ mockSubAppsService: null,
+ serviceResultCode: 0,
+ addCallReturnValue: [],
+ listCallReturnValue: [],
+ removeCallReturnValue: [],
+ }
+
+ class SubAppsServiceTestChromium {
+ constructor() {
+ Object.freeze(this); // Make it immutable.
+ }
+
+ initialize(service_result_code, add_call_return_value, list_call_return_value, remove_call_return_value) {
+ if (!testInternal.initialized) {
+ testInternal = {
+ mockSubAppsService: new MockSubAppsService(),
+ initialized: true,
+ serviceResultCode: service_result_code,
+ addCallReturnValue: add_call_return_value,
+ listCallReturnValue: list_call_return_value,
+ removeCallReturnValue: remove_call_return_value,
+ };
+ };
+ }
+
+ async reset() {
+ if (testInternal.initialized) {
+ testInternal.mockSubAppsService.reset();
+ testInternal = {
+ mockSubAppsService: null,
+ initialized: false,
+ serviceResultCode: 0,
+ addCallReturnValue: [],
+ listCallReturnValue: [],
+ removeCallReturnValue: [],
+ };
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+ }
+ }
+
+ return SubAppsServiceTestChromium;
+})();
diff --git a/test/fixtures/wpt/resources/chromium/mock-textdetection.js b/test/fixtures/wpt/resources/chromium/mock-textdetection.js
new file mode 100644
index 00000000000..52ca987e286
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-textdetection.js
@@ -0,0 +1,92 @@
+import {TextDetection, TextDetectionReceiver} from '/gen/services/shape_detection/public/mojom/textdetection.mojom.m.js';
+
+self.TextDetectionTest = (() => {
+ // Class that mocks TextDetection interface defined in
+ // https://cs.chromium.org/chromium/src/services/shape_detection/public/mojom/textdetection.mojom
+ class MockTextDetection {
+ constructor() {
+ this.receiver_ = new TextDetectionReceiver(this);
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(TextDetection.$interfaceName);
+ this.interceptor_.oninterfacerequest =
+ e => this.receiver_.$.bindHandle(e.handle);
+ this.interceptor_.start();
+ }
+
+ detect(bitmapData) {
+ this.bufferData_ =
+ new Uint32Array(getArrayBufferFromBigBuffer(bitmapData.pixelData));
+ return Promise.resolve({
+ results: [
+ {
+ rawValue : "cats",
+ boundingBox: { x: 1.0, y: 1.0, width: 100.0, height: 100.0 },
+ cornerPoints: [
+ { x: 1.0, y: 1.0 },
+ { x: 101.0, y: 1.0 },
+ { x: 101.0, y: 101.0 },
+ { x: 1.0, y: 101.0 }
+ ]
+ },
+ {
+ rawValue : "dogs",
+ boundingBox: { x: 2.0, y: 2.0, width: 50.0, height: 50.0 },
+ cornerPoints: [
+ { x: 2.0, y: 2.0 },
+ { x: 52.0, y: 2.0 },
+ { x: 52.0, y: 52.0 },
+ { x: 2.0, y: 52.0 }
+ ]
+ },
+ ],
+ });
+ }
+
+ getFrameData() {
+ return this.bufferData_;
+ }
+
+ reset() {
+ this.receiver_.$.close();
+ this.interceptor_.stop();
+ }
+
+ }
+
+ let testInternal = {
+ initialized: false,
+ MockTextDetection: null
+ }
+
+ class TextDetectionTestChromium {
+ constructor() {
+ Object.freeze(this); // Make it immutable.
+ }
+
+ initialize() {
+ if (testInternal.initialized)
+ throw new Error('Call reset() before initialize().');
+
+ testInternal.MockTextDetection = new MockTextDetection;
+ testInternal.initialized = true;
+ }
+
+ // Resets state of text detection mocks between test runs.
+ async reset() {
+ if (!testInternal.initialized)
+ throw new Error('Call initialize() before reset().');
+ testInternal.MockTextDetection.reset();
+ testInternal.MockTextDetection = null;
+ testInternal.initialized = false;
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ MockTextDetection() {
+ return testInternal.MockTextDetection;
+ }
+ }
+
+ return TextDetectionTestChromium;
+
+})();
diff --git a/test/fixtures/wpt/resources/chromium/mock-textdetection.js.headers b/test/fixtures/wpt/resources/chromium/mock-textdetection.js.headers
new file mode 100644
index 00000000000..6c61a34a4ec
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/mock-textdetection.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
\ No newline at end of file
diff --git a/test/fixtures/wpt/resources/chromium/nfc-mock.js b/test/fixtures/wpt/resources/chromium/nfc-mock.js
new file mode 100644
index 00000000000..31a71b9e220
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/nfc-mock.js
@@ -0,0 +1,437 @@
+import {NDEFErrorType, NDEFRecordTypeCategory, NFC, NFCReceiver} from '/gen/services/device/public/mojom/nfc.mojom.m.js';
+
+// Converts between NDEFMessageInit https://w3c.github.io/web-nfc/#dom-ndefmessage
+// and mojom.NDEFMessage structure, so that watch function can be tested.
+function toMojoNDEFMessage(message) {
+ let ndefMessage = {data: []};
+ for (let record of message.records) {
+ ndefMessage.data.push(toMojoNDEFRecord(record));
+ }
+ return ndefMessage;
+}
+
+function toMojoNDEFRecord(record) {
+ let nfcRecord = {};
+ // Simply checks the existence of ':' to decide whether it's an external
+ // type or a local type. As a mock, no need to really implement the validation
+ // algorithms for them.
+ if (record.recordType.startsWith(':')) {
+ nfcRecord.category = NDEFRecordTypeCategory.kLocal;
+ } else if (record.recordType.search(':') != -1) {
+ nfcRecord.category = NDEFRecordTypeCategory.kExternal;
+ } else {
+ nfcRecord.category = NDEFRecordTypeCategory.kStandardized;
+ }
+ nfcRecord.recordType = record.recordType;
+ nfcRecord.mediaType = record.mediaType;
+ nfcRecord.id = record.id;
+ if (record.recordType == 'text') {
+ nfcRecord.encoding = record.encoding == null? 'utf-8': record.encoding;
+ nfcRecord.lang = record.lang == null? 'en': record.lang;
+ }
+ nfcRecord.data = toByteArray(record.data);
+ if (record.data != null && record.data.records !== undefined) {
+ // |record.data| may be an NDEFMessageInit, i.e. the payload is a message.
+ nfcRecord.payloadMessage = toMojoNDEFMessage(record.data);
+ }
+ return nfcRecord;
+}
+
+// Converts JS objects to byte array.
+function toByteArray(data) {
+ if (data instanceof ArrayBuffer)
+ return new Uint8Array(data);
+ else if (ArrayBuffer.isView(data))
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+
+ let byteArray = new Uint8Array(0);
+ let tmpData = data;
+ if (typeof tmpData === 'object' || typeof tmpData === 'number')
+ tmpData = JSON.stringify(tmpData);
+
+ if (typeof tmpData === 'string')
+ byteArray = new TextEncoder().encode(tmpData);
+
+ return byteArray;
+}
+
+// Compares NDEFRecords that were provided / received by the mock service.
+// TODO: Use different getters to get received record data,
+// see spec changes at https://github.com/w3c/web-nfc/pull/243.
+self.compareNDEFRecords = function(providedRecord, receivedRecord) {
+ assert_equals(providedRecord.recordType, receivedRecord.recordType);
+
+ if (providedRecord.id === undefined) {
+ assert_equals(null, receivedRecord.id);
+ } else {
+ assert_equals(providedRecord.id, receivedRecord.id);
+ }
+
+ if (providedRecord.mediaType === undefined) {
+ assert_equals(null, receivedRecord.mediaType);
+ } else {
+ assert_equals(providedRecord.mediaType, receivedRecord.mediaType);
+ }
+
+ assert_not_equals(providedRecord.recordType, 'empty');
+
+ if (providedRecord.recordType == 'text') {
+ assert_equals(
+ providedRecord.encoding == null? 'utf-8': providedRecord.encoding,
+ receivedRecord.encoding);
+ assert_equals(providedRecord.lang == null? 'en': providedRecord.lang,
+ receivedRecord.lang);
+ } else {
+ assert_equals(null, receivedRecord.encoding);
+ assert_equals(null, receivedRecord.lang);
+ }
+
+ assert_array_equals(toByteArray(providedRecord.data),
+ new Uint8Array(receivedRecord.data));
+}
+
+// Compares NDEFWriteOptions structures that were provided to API and
+// received by the mock mojo service.
+self.assertNDEFWriteOptionsEqual = function(provided, received) {
+ if (provided.overwrite !== undefined)
+ assert_equals(provided.overwrite, !!received.overwrite);
+ else
+ assert_equals(!!received.overwrite, true);
+}
+
+// Compares NDEFReaderOptions structures that were provided to API and
+// received by the mock mojo service.
+self.assertNDEFReaderOptionsEqual = function(provided, received) {
+ if (provided.url !== undefined)
+ assert_equals(provided.url, received.url);
+ else
+ assert_equals(received.url, '');
+
+ if (provided.mediaType !== undefined)
+ assert_equals(provided.mediaType, received.mediaType);
+ else
+ assert_equals(received.mediaType, '');
+
+ if (provided.recordType !== undefined) {
+ assert_equals(provided.recordType, received.recordType);
+ }
+}
+
+function createNDEFError(type) {
+ return {error: (type != null ? {errorType: type, errorMessage: ''} : null)};
+}
+
+self.WebNFCTest = (() => {
+ class MockNFC {
+ constructor() {
+ this.receiver_ = new NFCReceiver(this);
+
+ this.interceptor_ = new MojoInterfaceInterceptor(NFC.$interfaceName);
+ this.interceptor_.oninterfacerequest = e => {
+ if (this.should_close_pipe_on_request_)
+ e.handle.close();
+ else
+ this.receiver_.$.bindHandle(e.handle);
+ }
+
+ this.interceptor_.start();
+
+ this.hw_status_ = NFCHWStatus.ENABLED;
+ this.pushed_message_ = null;
+ this.pending_write_options_ = null;
+ this.pending_push_promise_func_ = null;
+ this.push_completed_ = true;
+ this.pending_make_read_only_promise_func_ = null;
+ this.make_read_only_completed_ = true;
+ this.client_ = null;
+ this.watchers_ = [];
+ this.reading_messages_ = [];
+ this.operations_suspended_ = false;
+ this.is_formatted_tag_ = false;
+ this.data_transfer_failed_ = false;
+ this.should_close_pipe_on_request_ = false;
+ }
+
+ // NFC delegate functions.
+ async push(message, options) {
+ const error = this.getHWError();
+ if (error)
+ return error;
+ // Cancels previous pending push operation.
+ if (this.pending_push_promise_func_) {
+ this.cancelPendingPushOperation();
+ }
+
+ this.pushed_message_ = message;
+ this.pending_write_options_ = options;
+ return new Promise(resolve => {
+ if (this.operations_suspended_ || !this.push_completed_) {
+ // Leaves the push pending.
+ this.pending_push_promise_func_ = resolve;
+ } else if (this.is_formatted_tag_ && !options.overwrite) {
+ // Resolves with NotAllowedError if there are NDEF records on the device
+ // and overwrite is false.
+ resolve(createNDEFError(NDEFErrorType.NOT_ALLOWED));
+ } else if (this.data_transfer_failed_) {
+ // Resolves with NetworkError if data transfer fails.
+ resolve(createNDEFError(NDEFErrorType.IO_ERROR));
+ } else {
+ resolve(createNDEFError(null));
+ }
+ });
+ }
+
+ async cancelPush() {
+ this.cancelPendingPushOperation();
+ return createNDEFError(null);
+ }
+
+ async makeReadOnly(options) {
+ const error = this.getHWError();
+ if (error)
+ return error;
+ // Cancels previous pending makeReadOnly operation.
+ if (this.pending_make_read_only_promise_func_) {
+ this.cancelPendingMakeReadOnlyOperation();
+ }
+
+ if (this.operations_suspended_ || !this.make_read_only_completed_) {
+ // Leaves the makeReadOnly pending.
+ return new Promise(resolve => {
+ this.pending_make_read_only_promise_func_ = resolve;
+ });
+ } else if (this.data_transfer_failed_) {
+ // Resolves with NetworkError if data transfer fails.
+ return createNDEFError(NDEFErrorType.IO_ERROR);
+ } else {
+ return createNDEFError(null);
+ }
+ }
+
+ async cancelMakeReadOnly() {
+ this.cancelPendingMakeReadOnlyOperation();
+ return createNDEFError(null);
+ }
+
+ setClient(client) {
+ this.client_ = client;
+ }
+
+ async watch(id) {
+ assert_true(id > 0);
+ const error = this.getHWError();
+ if (error) {
+ return error;
+ }
+
+ this.watchers_.push({id: id});
+ // Ignores reading if NFC operation is suspended
+ // or the NFC tag does not expose NDEF technology.
+ if (!this.operations_suspended_) {
+ // Triggers onWatch if the new watcher matches existing messages.
+ for (let message of this.reading_messages_) {
+ this.client_.onWatch(
+ [id], fake_tag_serial_number, toMojoNDEFMessage(message));
+ }
+ }
+
+ return createNDEFError(null);
+ }
+
+ cancelWatch(id) {
+ let index = this.watchers_.findIndex(value => value.id === id);
+ if (index !== -1) {
+ this.watchers_.splice(index, 1);
+ }
+ }
+
+ getHWError() {
+ if (this.hw_status_ === NFCHWStatus.DISABLED)
+ return createNDEFError(NDEFErrorType.NOT_READABLE);
+ if (this.hw_status_ === NFCHWStatus.NOT_SUPPORTED)
+ return createNDEFError(NDEFErrorType.NOT_SUPPORTED);
+ return null;
+ }
+
+ setHWStatus(status) {
+ this.hw_status_ = status;
+ }
+
+ pushedMessage() {
+ return this.pushed_message_;
+ }
+
+ writeOptions() {
+ return this.pending_write_options_;
+ }
+
+ watchOptions() {
+ assert_not_equals(this.watchers_.length, 0);
+ return this.watchers_[this.watchers_.length - 1].options;
+ }
+
+ setPendingPushCompleted(result) {
+ this.push_completed_ = result;
+ }
+
+ setPendingMakeReadOnlyCompleted(result) {
+ this.make_read_only_completed_ = result;
+ }
+
+ reset() {
+ this.hw_status_ = NFCHWStatus.ENABLED;
+ this.watchers_ = [];
+ this.reading_messages_ = [];
+ this.operations_suspended_ = false;
+ this.cancelPendingPushOperation();
+ this.cancelPendingMakeReadOnlyOperation();
+ this.is_formatted_tag_ = false;
+ this.data_transfer_failed_ = false;
+ this.should_close_pipe_on_request_ = false;
+ }
+
+ cancelPendingPushOperation() {
+ if (this.pending_push_promise_func_) {
+ this.pending_push_promise_func_(
+ createNDEFError(NDEFErrorType.OPERATION_CANCELLED));
+ this.pending_push_promise_func_ = null;
+ }
+
+ this.pushed_message_ = null;
+ this.pending_write_options_ = null;
+ this.push_completed_ = true;
+ }
+
+ cancelPendingMakeReadOnlyOperation() {
+ if (this.pending_make_read_only_promise_func_) {
+ this.pending_make_read_only_promise_func_(
+ createNDEFError(NDEFErrorType.OPERATION_CANCELLED));
+ this.pending_make_read_only_promise_func_ = null;
+ }
+
+ this.make_read_only_completed_ = true;
+ }
+
+ // Sets message that is used to deliver NFC reading updates.
+ setReadingMessage(message) {
+ this.reading_messages_.push(message);
+ // Ignores reading if NFC operation is suspended.
+ if(this.operations_suspended_) return;
+ // when overwrite is false, the write algorithm will read the NFC tag
+ // to determine if it has NDEF records on it.
+ if (this.pending_write_options_ && this.pending_write_options_.overwrite)
+ return;
+ // Triggers onWatch if the new message matches existing watchers.
+ for (let watcher of this.watchers_) {
+ this.client_.onWatch(
+ [watcher.id], fake_tag_serial_number,
+ toMojoNDEFMessage(message));
+ }
+ }
+
+ // Suspends all pending NFC operations. Could be used when web page
+ // visibility is lost.
+ suspendNFCOperations() {
+ this.operations_suspended_ = true;
+ }
+
+ // Resumes all suspended NFC operations.
+ resumeNFCOperations() {
+ this.operations_suspended_ = false;
+ // Resumes pending NFC reading.
+ for (let watcher of this.watchers_) {
+ for (let message of this.reading_messages_) {
+ this.client_.onWatch(
+ [watcher.id], fake_tag_serial_number,
+ toMojoNDEFMessage(message));
+ }
+ }
+ // Resumes pending push operation.
+ if (this.pending_push_promise_func_ && this.push_completed_) {
+ this.pending_push_promise_func_(createNDEFError(null));
+ this.pending_push_promise_func_ = null;
+ }
+ // Resumes pending makeReadOnly operation.
+ if (this.pending_make_read_only_promise_func_ &&
+ this.make_read_only_completed_) {
+ this.pending_make_read_only_promise_func_(createNDEFError(null));
+ this.pending_make_read_only_promise_func_ = null;
+ }
+ }
+
+ // Simulates the device coming in proximity does not expose NDEF technology.
+ simulateNonNDEFTagDiscovered() {
+ // Notify NotSupportedError to all active readers.
+ if (this.watchers_.length != 0) {
+ this.client_.onError({
+ errorType: NDEFErrorType.NOT_SUPPORTED,
+ errorMessage: ''
+ });
+ }
+ // Reject the pending push with NotSupportedError.
+ if (this.pending_push_promise_func_) {
+ this.pending_push_promise_func_(
+ createNDEFError(NDEFErrorType.NOT_SUPPORTED));
+ this.pending_push_promise_func_ = null;
+ }
+ // Reject the pending makeReadOnly with NotSupportedError.
+ if (this.pending_make_read_only_promise_func_) {
+ this.pending_make_read_only_promise_func_(
+ createNDEFError(NDEFErrorType.NOT_SUPPORTED));
+ this.pending_make_read_only_promise_func_ = null;
+ }
+ }
+
+ setIsFormattedTag(isFormatted) {
+ this.is_formatted_tag_ = isFormatted;
+ }
+
+ simulateDataTransferFails() {
+ this.data_transfer_failed_ = true;
+ }
+
+ simulateClosedPipe() {
+ this.should_close_pipe_on_request_ = true;
+ }
+ }
+
+ let testInternal = {
+ initialized: false,
+ mockNFC: null
+ }
+
+ class NFCTestChromium {
+ constructor() {
+ Object.freeze(this); // Makes it immutable.
+ }
+
+ async initialize() {
+ if (testInternal.initialized)
+ throw new Error('Call reset() before initialize().');
+
+ // Grant nfc permissions for Chromium testdriver.
+ await test_driver.set_permission({ name: 'nfc' }, 'granted');
+
+ if (testInternal.mockNFC == null) {
+ testInternal.mockNFC = new MockNFC();
+ }
+ testInternal.initialized = true;
+ }
+
+ // Reuses the nfc mock but resets its state between test runs.
+ async reset() {
+ if (!testInternal.initialized)
+ throw new Error('Call initialize() before reset().');
+ testInternal.mockNFC.reset();
+ testInternal.initialized = false;
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ getMockNFC() {
+ return testInternal.mockNFC;
+ }
+ }
+
+ return NFCTestChromium;
+})();
diff --git a/test/fixtures/wpt/resources/chromium/web-bluetooth-test.js b/test/fixtures/wpt/resources/chromium/web-bluetooth-test.js
new file mode 100644
index 00000000000..67c3c4ee1b4
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/web-bluetooth-test.js
@@ -0,0 +1,629 @@
+'use strict';
+
+const content = {};
+const bluetooth = {};
+const MOJO_CHOOSER_EVENT_TYPE_MAP = {};
+
+function toMojoCentralState(state) {
+ switch (state) {
+ case 'absent':
+ return bluetooth.mojom.CentralState.ABSENT;
+ case 'powered-off':
+ return bluetooth.mojom.CentralState.POWERED_OFF;
+ case 'powered-on':
+ return bluetooth.mojom.CentralState.POWERED_ON;
+ default:
+ throw `Unsupported value ${state} for state.`;
+ }
+}
+
+// Converts bluetooth.mojom.WriteType to a string. If |writeType| is
+// invalid, this method will throw.
+function writeTypeToString(writeType) {
+ switch (writeType) {
+ case bluetooth.mojom.WriteType.kNone:
+ return 'none';
+ case bluetooth.mojom.WriteType.kWriteDefaultDeprecated:
+ return 'default-deprecated';
+ case bluetooth.mojom.WriteType.kWriteWithResponse:
+ return 'with-response';
+ case bluetooth.mojom.WriteType.kWriteWithoutResponse:
+ return 'without-response';
+ default:
+ throw `Unknown bluetooth.mojom.WriteType: ${writeType}`;
+ }
+}
+
+// Canonicalizes UUIDs and converts them to Mojo UUIDs.
+function canonicalizeAndConvertToMojoUUID(uuids) {
+ let canonicalUUIDs = uuids.map(val => ({uuid: BluetoothUUID.getService(val)}));
+ return canonicalUUIDs;
+}
+
+// Converts WebIDL a record to a map> to
+// use for Mojo, where the value for K is calculated using keyFn.
+function convertToMojoMap(record, keyFn, isNumberKey = false) {
+ let map = new Map();
+ for (const [key, value] of Object.entries(record)) {
+ let buffer = ArrayBuffer.isView(value) ? value.buffer : value;
+ if (isNumberKey) {
+ let numberKey = parseInt(key);
+ if (Number.isNaN(numberKey))
+ throw `Map key ${key} is not a number`;
+ map.set(keyFn(numberKey), Array.from(new Uint8Array(buffer)));
+ continue;
+ }
+ map.set(keyFn(key), Array.from(new Uint8Array(buffer)));
+ }
+ return map;
+}
+
+function ArrayToMojoCharacteristicProperties(arr) {
+ const struct = {};
+ arr.forEach(property => { struct[property] = true; });
+ return struct;
+}
+
+class FakeBluetooth {
+ constructor() {
+ this.fake_bluetooth_ptr_ = new bluetooth.mojom.FakeBluetoothRemote();
+ this.fake_bluetooth_ptr_.$.bindNewPipeAndPassReceiver().bindInBrowser('process');
+ this.fake_central_ = null;
+ }
+
+ // Set it to indicate whether the platform supports BLE. For example,
+ // Windows 7 is a platform that doesn't support Low Energy. On the other
+ // hand Windows 10 is a platform that does support LE, even if there is no
+ // Bluetooth radio present.
+ async setLESupported(supported) {
+ if (typeof supported !== 'boolean') throw 'Type Not Supported';
+ await this.fake_bluetooth_ptr_.setLESupported(supported);
+ }
+
+ // Returns a promise that resolves with a FakeCentral that clients can use
+ // to simulate events that a device in the Central/Observer role would
+ // receive as well as monitor the operations performed by the device in the
+ // Central/Observer role.
+ // Calls sets LE as supported.
+ //
+ // A "Central" object would allow its clients to receive advertising events
+ // and initiate connections to peripherals i.e. operations of two roles
+ // defined by the Bluetooth Spec: Observer and Central.
+ // See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an
+ // LE Physical Transport".
+ async simulateCentral({state}) {
+ if (this.fake_central_)
+ throw 'simulateCentral() should only be called once';
+
+ await this.setLESupported(true);
+
+ let {fakeCentral: fake_central_ptr} =
+ await this.fake_bluetooth_ptr_.simulateCentral(
+ toMojoCentralState(state));
+ this.fake_central_ = new FakeCentral(fake_central_ptr);
+ return this.fake_central_;
+ }
+
+ // Returns true if there are no pending responses.
+ async allResponsesConsumed() {
+ let {consumed} = await this.fake_bluetooth_ptr_.allResponsesConsumed();
+ return consumed;
+ }
+
+ // Returns a promise that resolves with a FakeChooser that clients can use to
+ // simulate chooser events.
+ async getManualChooser() {
+ if (typeof this.fake_chooser_ === 'undefined') {
+ this.fake_chooser_ = new FakeChooser();
+ }
+ return this.fake_chooser_;
+ }
+}
+
+// FakeCentral allows clients to simulate events that a device in the
+// Central/Observer role would receive as well as monitor the operations
+// performed by the device in the Central/Observer role.
+class FakeCentral {
+ constructor(fake_central_ptr) {
+ this.fake_central_ptr_ = fake_central_ptr;
+ this.peripherals_ = new Map();
+ }
+
+ // Simulates a peripheral with |address|, |name|, |manufacturerData| and
+ // |known_service_uuids| that has already been connected to the system. If the
+ // peripheral existed already it updates its name, manufacturer data, and
+ // known UUIDs. |known_service_uuids| should be an array of
+ // BluetoothServiceUUIDs
+ // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
+ //
+ // Platforms offer methods to retrieve devices that have already been
+ // connected to the system or weren't connected through the UA e.g. a user
+ // connected a peripheral through the system's settings. This method is
+ // intended to simulate peripherals that those methods would return.
+ async simulatePreconnectedPeripheral(
+ {address, name, manufacturerData = {}, knownServiceUUIDs = []}) {
+ await this.fake_central_ptr_.simulatePreconnectedPeripheral(
+ address, name,
+ convertToMojoMap(manufacturerData, Number, true /* isNumberKey */),
+ canonicalizeAndConvertToMojoUUID(knownServiceUUIDs));
+
+ return this.fetchOrCreatePeripheral_(address);
+ }
+
+ // Simulates an advertisement packet described by |scanResult| being received
+ // from a device. If central is currently scanning, the device will appear on
+ // the list of discovered devices.
+ async simulateAdvertisementReceived(scanResult) {
+ // Create a deep-copy to prevent the original |scanResult| from being
+ // modified when the UUIDs, manufacturer, and service data are converted.
+ let clonedScanResult = JSON.parse(JSON.stringify(scanResult));
+
+ if ('uuids' in scanResult.scanRecord) {
+ clonedScanResult.scanRecord.uuids =
+ canonicalizeAndConvertToMojoUUID(scanResult.scanRecord.uuids);
+ }
+
+ // Convert the optional appearance and txPower fields to the corresponding
+ // Mojo structures, since Mojo does not support optional interger values. If
+ // the fields are undefined, set the hasValue field as false and value as 0.
+ // Otherwise, set the hasValue field as true and value with the field value.
+ const has_appearance = 'appearance' in scanResult.scanRecord;
+ clonedScanResult.scanRecord.appearance = {
+ hasValue: has_appearance,
+ value: (has_appearance ? scanResult.scanRecord.appearance : 0)
+ }
+
+ const has_tx_power = 'txPower' in scanResult.scanRecord;
+ clonedScanResult.scanRecord.txPower = {
+ hasValue: has_tx_power,
+ value: (has_tx_power ? scanResult.scanRecord.txPower : 0)
+ }
+
+ // Convert manufacturerData from a record into a
+ // map> for Mojo.
+ if ('manufacturerData' in scanResult.scanRecord) {
+ clonedScanResult.scanRecord.manufacturerData = convertToMojoMap(
+ scanResult.scanRecord.manufacturerData, Number,
+ true /* isNumberKey */);
+ }
+
+ // Convert serviceData from a record into a
+ // map> for Mojo.
+ if ('serviceData' in scanResult.scanRecord) {
+ clonedScanResult.scanRecord.serviceData.serviceData = convertToMojoMap(
+ scanResult.scanRecord.serviceData, BluetoothUUID.getService,
+ false /* isNumberKey */);
+ }
+
+ await this.fake_central_ptr_.simulateAdvertisementReceived(
+ clonedScanResult);
+
+ return this.fetchOrCreatePeripheral_(clonedScanResult.deviceAddress);
+ }
+
+ // Simulates a change in the central device described by |state|. For example,
+ // setState('powered-off') can be used to simulate the central device powering
+ // off.
+ //
+ // This method should be used for any central state changes after
+ // simulateCentral() has been called to create a FakeCentral object.
+ async setState(state) {
+ await this.fake_central_ptr_.setState(toMojoCentralState(state));
+ }
+
+ // Create a fake_peripheral object from the given address.
+ fetchOrCreatePeripheral_(address) {
+ let peripheral = this.peripherals_.get(address);
+ if (peripheral === undefined) {
+ peripheral = new FakePeripheral(address, this.fake_central_ptr_);
+ this.peripherals_.set(address, peripheral);
+ }
+ return peripheral;
+ }
+}
+
+class FakePeripheral {
+ constructor(address, fake_central_ptr) {
+ this.address = address;
+ this.fake_central_ptr_ = fake_central_ptr;
+ }
+
+ // Adds a fake GATT Service with |uuid| to be discovered when discovering
+ // the peripheral's GATT Attributes. Returns a FakeRemoteGATTService
+ // corresponding to this service. |uuid| should be a BluetoothServiceUUIDs
+ // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
+ async addFakeService({uuid}) {
+ let {serviceId: service_id} = await this.fake_central_ptr_.addFakeService(
+ this.address, {uuid: BluetoothUUID.getService(uuid)});
+
+ if (service_id === null) throw 'addFakeService failed';
+
+ return new FakeRemoteGATTService(
+ service_id, this.address, this.fake_central_ptr_);
+ }
+
+ // Sets the next GATT Connection request response to |code|. |code| could be
+ // an HCI Error Code from BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a
+ // number outside that range returned by specific platforms e.g. Android
+ // returns 0x101 to signal a GATT failure
+ // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+ async setNextGATTConnectionResponse({code}) {
+ let {success} =
+ await this.fake_central_ptr_.setNextGATTConnectionResponse(
+ this.address, code);
+
+ if (success !== true) throw 'setNextGATTConnectionResponse failed.';
+ }
+
+ // Sets the next GATT Discovery request response for peripheral with
+ // |address| to |code|. |code| could be an HCI Error Code from
+ // BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a number outside that
+ // range returned by specific platforms e.g. Android returns 0x101 to signal
+ // a GATT failure
+ // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+ //
+ // The following procedures defined at BT 4.2 Vol 3 Part G Section 4.
+ // "GATT Feature Requirements" are used to discover attributes of the
+ // GATT Server:
+ // - Primary Service Discovery
+ // - Relationship Discovery
+ // - Characteristic Discovery
+ // - Characteristic Descriptor Discovery
+ // This method aims to simulate the response once all of these procedures
+ // have completed or if there was an error during any of them.
+ async setNextGATTDiscoveryResponse({code}) {
+ let {success} =
+ await this.fake_central_ptr_.setNextGATTDiscoveryResponse(
+ this.address, code);
+
+ if (success !== true) throw 'setNextGATTDiscoveryResponse failed.';
+ }
+
+ // Simulates a GATT disconnection from the peripheral with |address|.
+ async simulateGATTDisconnection() {
+ let {success} =
+ await this.fake_central_ptr_.simulateGATTDisconnection(this.address);
+
+ if (success !== true) throw 'simulateGATTDisconnection failed.';
+ }
+
+ // Simulates an Indication from the peripheral's GATT `Service Changed`
+ // Characteristic from BT 4.2 Vol 3 Part G 7.1. This Indication is signaled
+ // when services, characteristics, or descriptors are changed, added, or
+ // removed.
+ //
+ // The value for `Service Changed` is a range of attribute handles that have
+ // changed. However, this testing specification works at an abstracted
+ // level and does not expose setting attribute handles when adding
+ // attributes. Consequently, this simulate method should include the full
+ // range of all the peripheral's attribute handle values.
+ async simulateGATTServicesChanged() {
+ let {success} =
+ await this.fake_central_ptr_.simulateGATTServicesChanged(this.address);
+
+ if (success !== true) throw 'simulateGATTServicesChanged failed.';
+ }
+}
+
+class FakeRemoteGATTService {
+ constructor(service_id, peripheral_address, fake_central_ptr) {
+ this.service_id_ = service_id;
+ this.peripheral_address_ = peripheral_address;
+ this.fake_central_ptr_ = fake_central_ptr;
+ }
+
+ // Adds a fake GATT Characteristic with |uuid| and |properties|
+ // to this fake service. The characteristic will be found when discovering
+ // the peripheral's GATT Attributes. Returns a FakeRemoteGATTCharacteristic
+ // corresponding to the added characteristic.
+ async addFakeCharacteristic({uuid, properties}) {
+ let {characteristicId: characteristic_id} =
+ await this.fake_central_ptr_.addFakeCharacteristic(
+ {uuid: BluetoothUUID.getCharacteristic(uuid)},
+ ArrayToMojoCharacteristicProperties(properties),
+ this.service_id_,
+ this.peripheral_address_);
+
+ if (characteristic_id === null) throw 'addFakeCharacteristic failed';
+
+ return new FakeRemoteGATTCharacteristic(
+ characteristic_id, this.service_id_,
+ this.peripheral_address_, this.fake_central_ptr_);
+ }
+
+ // Removes the fake GATT service from its fake peripheral.
+ async remove() {
+ let {success} =
+ await this.fake_central_ptr_.removeFakeService(
+ this.service_id_,
+ this.peripheral_address_);
+
+ if (!success) throw 'remove failed';
+ }
+}
+
+class FakeRemoteGATTCharacteristic {
+ constructor(characteristic_id, service_id, peripheral_address,
+ fake_central_ptr) {
+ this.ids_ = [characteristic_id, service_id, peripheral_address];
+ this.descriptors_ = [];
+ this.fake_central_ptr_ = fake_central_ptr;
+ }
+
+ // Adds a fake GATT Descriptor with |uuid| to be discovered when
+ // discovering the peripheral's GATT Attributes. Returns a
+ // FakeRemoteGATTDescriptor corresponding to this descriptor. |uuid| should
+ // be a BluetoothDescriptorUUID
+ // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothdescriptoruuid
+ async addFakeDescriptor({uuid}) {
+ let {descriptorId: descriptor_id} =
+ await this.fake_central_ptr_.addFakeDescriptor(
+ {uuid: BluetoothUUID.getDescriptor(uuid)}, ...this.ids_);
+
+ if (descriptor_id === null) throw 'addFakeDescriptor failed';
+
+ let fake_descriptor = new FakeRemoteGATTDescriptor(
+ descriptor_id, ...this.ids_, this.fake_central_ptr_);
+ this.descriptors_.push(fake_descriptor);
+
+ return fake_descriptor;
+ }
+
+ // Sets the next read response for characteristic to |code| and |value|.
+ // |code| could be a GATT Error Response from
+ // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
+ // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
+ // failure.
+ // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+ async setNextReadResponse(gatt_code, value=null) {
+ if (gatt_code === 0 && value === null) {
+ throw '|value| can\'t be null if read should success.';
+ }
+ if (gatt_code !== 0 && value !== null) {
+ throw '|value| must be null if read should fail.';
+ }
+
+ let {success} =
+ await this.fake_central_ptr_.setNextReadCharacteristicResponse(
+ gatt_code, value, ...this.ids_);
+
+ if (!success) throw 'setNextReadCharacteristicResponse failed';
+ }
+
+ // Sets the next write response for this characteristic to |code|. If
+ // writing to a characteristic that only supports 'write_without_response'
+ // the set response will be ignored.
+ // |code| could be a GATT Error Response from
+ // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
+ // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
+ // failure.
+ async setNextWriteResponse(gatt_code) {
+ let {success} =
+ await this.fake_central_ptr_.setNextWriteCharacteristicResponse(
+ gatt_code, ...this.ids_);
+
+ if (!success) throw 'setNextWriteCharacteristicResponse failed';
+ }
+
+ // Sets the next subscribe to notifications response for characteristic with
+ // |characteristic_id| in |service_id| and in |peripheral_address| to
+ // |code|. |code| could be a GATT Error Response from BT 4.2 Vol 3 Part F
+ // 3.4.1.1 Error Response or a number outside that range returned by
+ // specific platforms e.g. Android returns 0x101 to signal a GATT failure.
+ async setNextSubscribeToNotificationsResponse(gatt_code) {
+ let {success} =
+ await this.fake_central_ptr_.setNextSubscribeToNotificationsResponse(
+ gatt_code, ...this.ids_);
+
+ if (!success) throw 'setNextSubscribeToNotificationsResponse failed';
+ }
+
+ // Sets the next unsubscribe to notifications response for characteristic with
+ // |characteristic_id| in |service_id| and in |peripheral_address| to
+ // |code|. |code| could be a GATT Error Response from BT 4.2 Vol 3 Part F
+ // 3.4.1.1 Error Response or a number outside that range returned by
+ // specific platforms e.g. Android returns 0x101 to signal a GATT failure.
+ async setNextUnsubscribeFromNotificationsResponse(gatt_code) {
+ let {success} =
+ await this.fake_central_ptr_.setNextUnsubscribeFromNotificationsResponse(
+ gatt_code, ...this.ids_);
+
+ if (!success) throw 'setNextUnsubscribeToNotificationsResponse failed';
+ }
+
+ // Returns true if notifications from the characteristic have been subscribed
+ // to.
+ async isNotifying() {
+ let {success, isNotifying} =
+ await this.fake_central_ptr_.isNotifying(...this.ids_);
+
+ if (!success) throw 'isNotifying failed';
+
+ return isNotifying;
+ }
+
+ // Gets the last successfully written value to the characteristic and its
+ // write type. Write type is one of 'none', 'default-deprecated',
+ // 'with-response', 'without-response'. Returns {lastValue: null,
+ // lastWriteType: 'none'} if no value has yet been written to the
+ // characteristic.
+ async getLastWrittenValue() {
+ let {success, value, writeType} =
+ await this.fake_central_ptr_.getLastWrittenCharacteristicValue(
+ ...this.ids_);
+
+ if (!success) throw 'getLastWrittenCharacteristicValue failed';
+
+ return {lastValue: value, lastWriteType: writeTypeToString(writeType)};
+ }
+
+ // Removes the fake GATT Characteristic from its fake service.
+ async remove() {
+ let {success} =
+ await this.fake_central_ptr_.removeFakeCharacteristic(...this.ids_);
+
+ if (!success) throw 'remove failed';
+ }
+}
+
+class FakeRemoteGATTDescriptor {
+ constructor(descriptor_id,
+ characteristic_id,
+ service_id,
+ peripheral_address,
+ fake_central_ptr) {
+ this.ids_ = [
+ descriptor_id, characteristic_id, service_id, peripheral_address];
+ this.fake_central_ptr_ = fake_central_ptr;
+ }
+
+ // Sets the next read response for descriptor to |code| and |value|.
+ // |code| could be a GATT Error Response from
+ // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
+ // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
+ // failure.
+ // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+ async setNextReadResponse(gatt_code, value=null) {
+ if (gatt_code === 0 && value === null) {
+ throw '|value| cannot be null if read should succeed.';
+ }
+ if (gatt_code !== 0 && value !== null) {
+ throw '|value| must be null if read should fail.';
+ }
+
+ let {success} =
+ await this.fake_central_ptr_.setNextReadDescriptorResponse(
+ gatt_code, value, ...this.ids_);
+
+ if (!success) throw 'setNextReadDescriptorResponse failed';
+ }
+
+ // Sets the next write response for this descriptor to |code|.
+ // |code| could be a GATT Error Response from
+ // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
+ // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
+ // failure.
+ async setNextWriteResponse(gatt_code) {
+ let {success} =
+ await this.fake_central_ptr_.setNextWriteDescriptorResponse(
+ gatt_code, ...this.ids_);
+
+ if (!success) throw 'setNextWriteDescriptorResponse failed';
+ }
+
+ // Gets the last successfully written value to the descriptor.
+ // Returns null if no value has yet been written to the descriptor.
+ async getLastWrittenValue() {
+ let {success, value} =
+ await this.fake_central_ptr_.getLastWrittenDescriptorValue(
+ ...this.ids_);
+
+ if (!success) throw 'getLastWrittenDescriptorValue failed';
+
+ return value;
+ }
+
+ // Removes the fake GATT Descriptor from its fake characteristic.
+ async remove() {
+ let {success} =
+ await this.fake_central_ptr_.removeFakeDescriptor(...this.ids_);
+
+ if (!success) throw 'remove failed';
+ }
+}
+
+// FakeChooser allows clients to simulate user actions on a Bluetooth chooser,
+// and records the events produced by the Bluetooth chooser.
+class FakeChooser {
+ constructor() {
+ let fakeBluetoothChooserFactoryRemote =
+ new content.mojom.FakeBluetoothChooserFactoryRemote();
+ fakeBluetoothChooserFactoryRemote.$.bindNewPipeAndPassReceiver().bindInBrowser('process');
+
+ this.fake_bluetooth_chooser_ptr_ =
+ new content.mojom.FakeBluetoothChooserRemote();
+ this.fake_bluetooth_chooser_client_receiver_ =
+ new content.mojom.FakeBluetoothChooserClientReceiver(this);
+ fakeBluetoothChooserFactoryRemote.createFakeBluetoothChooser(
+ this.fake_bluetooth_chooser_ptr_.$.bindNewPipeAndPassReceiver(),
+ this.fake_bluetooth_chooser_client_receiver_.$.associateAndPassRemote());
+
+ this.events_ = new Array();
+ this.event_listener_ = null;
+ }
+
+ // If the chooser has received more events than |numOfEvents| this function
+ // will reject the promise, else it will wait until |numOfEvents| events are
+ // received before resolving with an array of |FakeBluetoothChooserEvent|
+ // objects.
+ async waitForEvents(numOfEvents) {
+ return new Promise(resolve => {
+ if (this.events_.length > numOfEvents) {
+ throw `Asked for ${numOfEvents} event(s), but received ` +
+ `${this.events_.length}.`;
+ }
+
+ this.event_listener_ = () => {
+ if (this.events_.length === numOfEvents) {
+ let result = Array.from(this.events_);
+ this.event_listener_ = null;
+ this.events_ = [];
+ resolve(result);
+ }
+ };
+ this.event_listener_();
+ });
+ }
+
+ async selectPeripheral(peripheral) {
+ if (!(peripheral instanceof FakePeripheral)) {
+ throw '|peripheral| must be an instance of FakePeripheral';
+ }
+ await this.fake_bluetooth_chooser_ptr_.selectPeripheral(peripheral.address);
+ }
+
+ async cancel() {
+ await this.fake_bluetooth_chooser_ptr_.cancel();
+ }
+
+ async rescan() {
+ await this.fake_bluetooth_chooser_ptr_.rescan();
+ }
+
+ onEvent(chooserEvent) {
+ chooserEvent.type = MOJO_CHOOSER_EVENT_TYPE_MAP[chooserEvent.type];
+ this.events_.push(chooserEvent);
+ if (this.event_listener_ !== null) {
+ this.event_listener_();
+ }
+ }
+}
+
+async function initializeChromiumResources() {
+ content.mojom = await import(
+ '/gen/content/web_test/common/fake_bluetooth_chooser.mojom.m.js');
+ bluetooth.mojom = await import(
+ '/gen/device/bluetooth/public/mojom/emulation/fake_bluetooth.mojom.m.js');
+
+ const map = MOJO_CHOOSER_EVENT_TYPE_MAP;
+ const types = content.mojom.ChooserEventType;
+ map[types.CHOOSER_OPENED] = 'chooser-opened';
+ map[types.CHOOSER_CLOSED] = 'chooser-closed';
+ map[types.ADAPTER_REMOVED] = 'adapter-removed';
+ map[types.ADAPTER_DISABLED] = 'adapter-disabled';
+ map[types.ADAPTER_ENABLED] = 'adapter-enabled';
+ map[types.DISCOVERY_FAILED_TO_START] = 'discovery-failed-to-start';
+ map[types.DISCOVERING] = 'discovering';
+ map[types.DISCOVERY_IDLE] = 'discovery-idle';
+ map[types.ADD_OR_UPDATE_DEVICE] = 'add-or-update-device';
+
+ // If this line fails, it means that current environment does not support the
+ // Web Bluetooth Test API.
+ try {
+ navigator.bluetooth.test = new FakeBluetooth();
+ } catch {
+ throw 'Web Bluetooth Test API is not implemented on this ' +
+ 'environment. See the bluetooth README at ' +
+ 'https://github.com/web-platform-tests/wpt/blob/master/bluetooth/README.md#web-bluetooth-testing';
+ }
+}
diff --git a/test/fixtures/wpt/resources/chromium/web-bluetooth-test.js.headers b/test/fixtures/wpt/resources/chromium/web-bluetooth-test.js.headers
new file mode 100644
index 00000000000..6805c323df5
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/web-bluetooth-test.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/wpt/resources/chromium/webusb-child-test.js b/test/fixtures/wpt/resources/chromium/webusb-child-test.js
new file mode 100644
index 00000000000..21412f66b0a
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/webusb-child-test.js
@@ -0,0 +1,47 @@
+'use strict';
+
+// This polyfill prepares a child context to be attached to a parent context.
+// The parent must call navigator.usb.test.attachToContext() to attach to the
+// child context.
+(() => {
+ if (this.constructor.name === 'DedicatedWorkerGlobalScope' ||
+ this !== window.top) {
+
+ // Run Chromium specific set up code.
+ if (typeof MojoInterfaceInterceptor !== 'undefined') {
+ let messageChannel = new MessageChannel();
+ messageChannel.port1.onmessage = async (messageEvent) => {
+ if (messageEvent.data.type === 'Attach') {
+ messageEvent.data.interfaces.forEach(interfaceName => {
+ let interfaceInterceptor =
+ new MojoInterfaceInterceptor(interfaceName);
+ interfaceInterceptor.oninterfacerequest =
+ e => messageChannel.port1.postMessage({
+ type: interfaceName,
+ handle: e.handle
+ }, [e.handle]);
+ interfaceInterceptor.start();
+ });
+
+ // Wait for a call to GetDevices() to ensure that the interface
+ // handles are forwarded to the parent context.
+ try {
+ await navigator.usb.getDevices();
+ } catch (e) {
+ // This can happen in case of, for example, testing usb disallowed
+ // iframe.
+ console.error(`getDevices() throws error: ${e.name}: ${e.message}`);
+ }
+
+ messageChannel.port1.postMessage({ type: 'Complete' });
+ }
+ };
+
+ let message = { type: 'ReadyForAttachment', port: messageChannel.port2 };
+ if (typeof Window !== 'undefined')
+ parent.postMessage(message, '*', [messageChannel.port2]);
+ else
+ postMessage(message, [messageChannel.port2]);
+ }
+ }
+})();
diff --git a/test/fixtures/wpt/resources/chromium/webusb-child-test.js.headers b/test/fixtures/wpt/resources/chromium/webusb-child-test.js.headers
new file mode 100644
index 00000000000..6805c323df5
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/webusb-child-test.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/wpt/resources/chromium/webusb-test.js b/test/fixtures/wpt/resources/chromium/webusb-test.js
new file mode 100644
index 00000000000..7cca63d9196
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/webusb-test.js
@@ -0,0 +1,583 @@
+'use strict';
+
+// This polyfill library implements the WebUSB Test API as specified here:
+// https://wicg.github.io/webusb/test/
+
+(() => {
+
+// These variables are logically members of the USBTest class but are defined
+// here to hide them from being visible as fields of navigator.usb.test.
+let internal = {
+ intialized: false,
+
+ webUsbService: null,
+ webUsbServiceInterceptor: null,
+
+ messagePort: null,
+};
+
+let mojom = {};
+
+async function loadMojomDefinitions() {
+ const deviceMojom =
+ await import('/gen/services/device/public/mojom/usb_device.mojom.m.js');
+ const serviceMojom = await import(
+ '/gen/third_party/blink/public/mojom/usb/web_usb_service.mojom.m.js');
+ return {
+ ...deviceMojom,
+ ...serviceMojom,
+ };
+}
+
+function getMessagePort(target) {
+ return new Promise(resolve => {
+ target.addEventListener('message', messageEvent => {
+ if (messageEvent.data.type === 'ReadyForAttachment') {
+ if (internal.messagePort === null) {
+ internal.messagePort = messageEvent.data.port;
+ }
+ resolve();
+ }
+ }, {once: true});
+ });
+}
+
+// Converts an ECMAScript String object to an instance of
+// mojo_base.mojom.String16.
+function mojoString16ToString(string16) {
+ return String.fromCharCode.apply(null, string16.data);
+}
+
+// Converts an instance of mojo_base.mojom.String16 to an ECMAScript String.
+function stringToMojoString16(string) {
+ let array = new Array(string.length);
+ for (var i = 0; i < string.length; ++i) {
+ array[i] = string.charCodeAt(i);
+ }
+ return { data: array }
+}
+
+function fakeDeviceInitToDeviceInfo(guid, init) {
+ let deviceInfo = {
+ guid: guid + "",
+ usbVersionMajor: init.usbVersionMajor,
+ usbVersionMinor: init.usbVersionMinor,
+ usbVersionSubminor: init.usbVersionSubminor,
+ classCode: init.deviceClass,
+ subclassCode: init.deviceSubclass,
+ protocolCode: init.deviceProtocol,
+ vendorId: init.vendorId,
+ productId: init.productId,
+ deviceVersionMajor: init.deviceVersionMajor,
+ deviceVersionMinor: init.deviceVersionMinor,
+ deviceVersionSubminor: init.deviceVersionSubminor,
+ manufacturerName: stringToMojoString16(init.manufacturerName),
+ productName: stringToMojoString16(init.productName),
+ serialNumber: stringToMojoString16(init.serialNumber),
+ activeConfiguration: init.activeConfigurationValue,
+ configurations: []
+ };
+ init.configurations.forEach(config => {
+ var configInfo = {
+ configurationValue: config.configurationValue,
+ configurationName: stringToMojoString16(config.configurationName),
+ selfPowered: false,
+ remoteWakeup: false,
+ maximumPower: 0,
+ interfaces: [],
+ extraData: new Uint8Array()
+ };
+ config.interfaces.forEach(iface => {
+ var interfaceInfo = {
+ interfaceNumber: iface.interfaceNumber,
+ alternates: []
+ };
+ iface.alternates.forEach(alternate => {
+ var alternateInfo = {
+ alternateSetting: alternate.alternateSetting,
+ classCode: alternate.interfaceClass,
+ subclassCode: alternate.interfaceSubclass,
+ protocolCode: alternate.interfaceProtocol,
+ interfaceName: stringToMojoString16(alternate.interfaceName),
+ endpoints: [],
+ extraData: new Uint8Array()
+ };
+ alternate.endpoints.forEach(endpoint => {
+ var endpointInfo = {
+ endpointNumber: endpoint.endpointNumber,
+ packetSize: endpoint.packetSize,
+ synchronizationType: mojom.UsbSynchronizationType.NONE,
+ usageType: mojom.UsbUsageType.DATA,
+ pollingInterval: 0,
+ extraData: new Uint8Array()
+ };
+ switch (endpoint.direction) {
+ case "in":
+ endpointInfo.direction = mojom.UsbTransferDirection.INBOUND;
+ break;
+ case "out":
+ endpointInfo.direction = mojom.UsbTransferDirection.OUTBOUND;
+ break;
+ }
+ switch (endpoint.type) {
+ case "bulk":
+ endpointInfo.type = mojom.UsbTransferType.BULK;
+ break;
+ case "interrupt":
+ endpointInfo.type = mojom.UsbTransferType.INTERRUPT;
+ break;
+ case "isochronous":
+ endpointInfo.type = mojom.UsbTransferType.ISOCHRONOUS;
+ break;
+ }
+ alternateInfo.endpoints.push(endpointInfo);
+ });
+ interfaceInfo.alternates.push(alternateInfo);
+ });
+ configInfo.interfaces.push(interfaceInfo);
+ });
+ deviceInfo.configurations.push(configInfo);
+ });
+ return deviceInfo;
+}
+
+function convertMojoDeviceFilters(input) {
+ let output = [];
+ input.forEach(filter => {
+ output.push(convertMojoDeviceFilter(filter));
+ });
+ return output;
+}
+
+function convertMojoDeviceFilter(input) {
+ let output = {};
+ if (input.hasVendorId)
+ output.vendorId = input.vendorId;
+ if (input.hasProductId)
+ output.productId = input.productId;
+ if (input.hasClassCode)
+ output.classCode = input.classCode;
+ if (input.hasSubclassCode)
+ output.subclassCode = input.subclassCode;
+ if (input.hasProtocolCode)
+ output.protocolCode = input.protocolCode;
+ if (input.serialNumber)
+ output.serialNumber = mojoString16ToString(input.serialNumber);
+ return output;
+}
+
+class FakeDevice {
+ constructor(deviceInit) {
+ this.info_ = deviceInit;
+ this.opened_ = false;
+ this.currentConfiguration_ = null;
+ this.claimedInterfaces_ = new Map();
+ }
+
+ getConfiguration() {
+ if (this.currentConfiguration_) {
+ return Promise.resolve({
+ value: this.currentConfiguration_.configurationValue });
+ } else {
+ return Promise.resolve({ value: 0 });
+ }
+ }
+
+ open() {
+ assert_false(this.opened_);
+ this.opened_ = true;
+ return Promise.resolve({result: {success: mojom.UsbOpenDeviceSuccess.OK}});
+ }
+
+ close() {
+ assert_true(this.opened_);
+ this.opened_ = false;
+ return Promise.resolve();
+ }
+
+ setConfiguration(value) {
+ assert_true(this.opened_);
+
+ let selectedConfiguration = this.info_.configurations.find(
+ configuration => configuration.configurationValue == value);
+ // Blink should never request an invalid configuration.
+ assert_not_equals(selectedConfiguration, undefined);
+ this.currentConfiguration_ = selectedConfiguration;
+ return Promise.resolve({ success: true });
+ }
+
+ async claimInterface(interfaceNumber) {
+ assert_true(this.opened_);
+ assert_false(this.currentConfiguration_ == null, 'device configured');
+ assert_false(this.claimedInterfaces_.has(interfaceNumber),
+ 'interface already claimed');
+
+ const protectedInterfaces = new Set([
+ mojom.USB_AUDIO_CLASS,
+ mojom.USB_HID_CLASS,
+ mojom.USB_MASS_STORAGE_CLASS,
+ mojom.USB_SMART_CARD_CLASS,
+ mojom.USB_VIDEO_CLASS,
+ mojom.USB_AUDIO_VIDEO_CLASS,
+ mojom.USB_WIRELESS_CLASS,
+ ]);
+
+ let iface = this.currentConfiguration_.interfaces.find(
+ iface => iface.interfaceNumber == interfaceNumber);
+ // Blink should never request an invalid interface or alternate.
+ assert_false(iface == undefined);
+ if (iface.alternates.some(
+ alt => protectedInterfaces.has(alt.interfaceClass))) {
+ return {result: mojom.UsbClaimInterfaceResult.kProtectedClass};
+ }
+
+ this.claimedInterfaces_.set(interfaceNumber, 0);
+ return {result: mojom.UsbClaimInterfaceResult.kSuccess};
+ }
+
+ releaseInterface(interfaceNumber) {
+ assert_true(this.opened_);
+ assert_false(this.currentConfiguration_ == null, 'device configured');
+ assert_true(this.claimedInterfaces_.has(interfaceNumber));
+ this.claimedInterfaces_.delete(interfaceNumber);
+ return Promise.resolve({ success: true });
+ }
+
+ setInterfaceAlternateSetting(interfaceNumber, alternateSetting) {
+ assert_true(this.opened_);
+ assert_false(this.currentConfiguration_ == null, 'device configured');
+ assert_true(this.claimedInterfaces_.has(interfaceNumber));
+
+ let iface = this.currentConfiguration_.interfaces.find(
+ iface => iface.interfaceNumber == interfaceNumber);
+ // Blink should never request an invalid interface or alternate.
+ assert_false(iface == undefined);
+ assert_true(iface.alternates.some(
+ x => x.alternateSetting == alternateSetting));
+ this.claimedInterfaces_.set(interfaceNumber, alternateSetting);
+ return Promise.resolve({ success: true });
+ }
+
+ reset() {
+ assert_true(this.opened_);
+ return Promise.resolve({ success: true });
+ }
+
+ clearHalt(endpoint) {
+ assert_true(this.opened_);
+ assert_false(this.currentConfiguration_ == null, 'device configured');
+ // TODO(reillyg): Assert that endpoint is valid.
+ return Promise.resolve({ success: true });
+ }
+
+ async controlTransferIn(params, length, timeout) {
+ assert_true(this.opened_);
+
+ if ((params.recipient == mojom.UsbControlTransferRecipient.INTERFACE ||
+ params.recipient == mojom.UsbControlTransferRecipient.ENDPOINT) &&
+ this.currentConfiguration_ == null) {
+ return {
+ status: mojom.UsbTransferStatus.PERMISSION_DENIED,
+ };
+ }
+
+ return {
+ status: mojom.UsbTransferStatus.OK,
+ data: {
+ buffer: [
+ length >> 8, length & 0xff, params.request, params.value >> 8,
+ params.value & 0xff, params.index >> 8, params.index & 0xff
+ ]
+ }
+ };
+ }
+
+ async controlTransferOut(params, data, timeout) {
+ assert_true(this.opened_);
+
+ if ((params.recipient == mojom.UsbControlTransferRecipient.INTERFACE ||
+ params.recipient == mojom.UsbControlTransferRecipient.ENDPOINT) &&
+ this.currentConfiguration_ == null) {
+ return {
+ status: mojom.UsbTransferStatus.PERMISSION_DENIED,
+ };
+ }
+
+ return {status: mojom.UsbTransferStatus.OK, bytesWritten: data.byteLength};
+ }
+
+ genericTransferIn(endpointNumber, length, timeout) {
+ assert_true(this.opened_);
+ assert_false(this.currentConfiguration_ == null, 'device configured');
+ // TODO(reillyg): Assert that endpoint is valid.
+ let data = new Array(length);
+ for (let i = 0; i < length; ++i)
+ data[i] = i & 0xff;
+ return Promise.resolve(
+ {status: mojom.UsbTransferStatus.OK, data: {buffer: data}});
+ }
+
+ genericTransferOut(endpointNumber, data, timeout) {
+ assert_true(this.opened_);
+ assert_false(this.currentConfiguration_ == null, 'device configured');
+ // TODO(reillyg): Assert that endpoint is valid.
+ return Promise.resolve(
+ {status: mojom.UsbTransferStatus.OK, bytesWritten: data.byteLength});
+ }
+
+ isochronousTransferIn(endpointNumber, packetLengths, timeout) {
+ assert_true(this.opened_);
+ assert_false(this.currentConfiguration_ == null, 'device configured');
+ // TODO(reillyg): Assert that endpoint is valid.
+ let data = new Array(packetLengths.reduce((a, b) => a + b, 0));
+ let dataOffset = 0;
+ let packets = new Array(packetLengths.length);
+ for (let i = 0; i < packetLengths.length; ++i) {
+ for (let j = 0; j < packetLengths[i]; ++j)
+ data[dataOffset++] = j & 0xff;
+ packets[i] = {
+ length: packetLengths[i],
+ transferredLength: packetLengths[i],
+ status: mojom.UsbTransferStatus.OK
+ };
+ }
+ return Promise.resolve({data: {buffer: data}, packets: packets});
+ }
+
+ isochronousTransferOut(endpointNumber, data, packetLengths, timeout) {
+ assert_true(this.opened_);
+ assert_false(this.currentConfiguration_ == null, 'device configured');
+ // TODO(reillyg): Assert that endpoint is valid.
+ let packets = new Array(packetLengths.length);
+ for (let i = 0; i < packetLengths.length; ++i) {
+ packets[i] = {
+ length: packetLengths[i],
+ transferredLength: packetLengths[i],
+ status: mojom.UsbTransferStatus.OK
+ };
+ }
+ return Promise.resolve({ packets: packets });
+ }
+}
+
+class FakeWebUsbService {
+ constructor() {
+ this.receiver_ = new mojom.WebUsbServiceReceiver(this);
+ this.devices_ = new Map();
+ this.devicesByGuid_ = new Map();
+ this.client_ = null;
+ this.nextGuid_ = 0;
+ }
+
+ addBinding(handle) {
+ this.receiver_.$.bindHandle(handle);
+ }
+
+ addDevice(fakeDevice, info) {
+ let device = {
+ fakeDevice: fakeDevice,
+ guid: (this.nextGuid_++).toString(),
+ info: info,
+ receivers: [],
+ };
+ this.devices_.set(fakeDevice, device);
+ this.devicesByGuid_.set(device.guid, device);
+ if (this.client_)
+ this.client_.onDeviceAdded(fakeDeviceInitToDeviceInfo(device.guid, info));
+ }
+
+ async forgetDevice(guid) {
+ // Permissions are currently untestable through WPT.
+ }
+
+ removeDevice(fakeDevice) {
+ let device = this.devices_.get(fakeDevice);
+ if (!device)
+ throw new Error('Cannot remove unknown device.');
+
+ for (const receiver of device.receivers)
+ receiver.$.close();
+ this.devices_.delete(device.fakeDevice);
+ this.devicesByGuid_.delete(device.guid);
+ if (this.client_) {
+ this.client_.onDeviceRemoved(
+ fakeDeviceInitToDeviceInfo(device.guid, device.info));
+ }
+ }
+
+ removeAllDevices() {
+ this.devices_.forEach(device => {
+ for (const receiver of device.receivers)
+ receiver.$.close();
+ this.client_.onDeviceRemoved(
+ fakeDeviceInitToDeviceInfo(device.guid, device.info));
+ });
+ this.devices_.clear();
+ this.devicesByGuid_.clear();
+ }
+
+ getDevices() {
+ let devices = [];
+ this.devices_.forEach(device => {
+ devices.push(fakeDeviceInitToDeviceInfo(device.guid, device.info));
+ });
+ return Promise.resolve({ results: devices });
+ }
+
+ getDevice(guid, request) {
+ let retrievedDevice = this.devicesByGuid_.get(guid);
+ if (retrievedDevice) {
+ const receiver =
+ new mojom.UsbDeviceReceiver(new FakeDevice(retrievedDevice.info));
+ receiver.$.bindHandle(request.handle);
+ receiver.onConnectionError.addListener(() => {
+ if (retrievedDevice.fakeDevice.onclose)
+ retrievedDevice.fakeDevice.onclose();
+ });
+ retrievedDevice.receivers.push(receiver);
+ } else {
+ request.handle.close();
+ }
+ }
+
+ getPermission(options) {
+ return new Promise(resolve => {
+ if (navigator.usb.test.onrequestdevice) {
+ navigator.usb.test.onrequestdevice(
+ new USBDeviceRequestEvent(options, resolve));
+ } else {
+ resolve({ result: null });
+ }
+ });
+ }
+
+ setClient(client) {
+ this.client_ = client;
+ }
+}
+
+class USBDeviceRequestEvent {
+ constructor(options, resolve) {
+ this.filters = convertMojoDeviceFilters(options.filters);
+ this.exclusionFilters = convertMojoDeviceFilters(options.exclusionFilters);
+ this.resolveFunc_ = resolve;
+ }
+
+ respondWith(value) {
+ // Wait until |value| resolves (if it is a Promise). This function returns
+ // no value.
+ Promise.resolve(value).then(fakeDevice => {
+ let device = internal.webUsbService.devices_.get(fakeDevice);
+ let result = null;
+ if (device) {
+ result = fakeDeviceInitToDeviceInfo(device.guid, device.info);
+ }
+ this.resolveFunc_({ result: result });
+ }, () => {
+ this.resolveFunc_({ result: null });
+ });
+ }
+}
+
+// Unlike FakeDevice this class is exported to callers of USBTest.addFakeDevice.
+class FakeUSBDevice {
+ constructor() {
+ this.onclose = null;
+ }
+
+ disconnect() {
+ setTimeout(() => internal.webUsbService.removeDevice(this), 0);
+ }
+}
+
+class USBTest {
+ constructor() {
+ this.onrequestdevice = undefined;
+ }
+
+ async initialize() {
+ if (internal.initialized)
+ return;
+
+ // Be ready to handle 'ReadyForAttachment' message from child iframes.
+ if ('window' in self) {
+ getMessagePort(window);
+ }
+
+ mojom = await loadMojomDefinitions();
+ internal.webUsbService = new FakeWebUsbService();
+ internal.webUsbServiceInterceptor =
+ new MojoInterfaceInterceptor(mojom.WebUsbService.$interfaceName);
+ internal.webUsbServiceInterceptor.oninterfacerequest =
+ e => internal.webUsbService.addBinding(e.handle);
+ internal.webUsbServiceInterceptor.start();
+
+ // Wait for a call to GetDevices() to pass between the renderer and the
+ // mock in order to establish that everything is set up.
+ await navigator.usb.getDevices();
+ internal.initialized = true;
+ }
+
+ // Returns a promise that is resolved when the implementation of |usb| in the
+ // global scope for |context| is controlled by the current context.
+ attachToContext(context) {
+ if (!internal.initialized)
+ throw new Error('Call initialize() before attachToContext()');
+
+ let target = context.constructor.name === 'Worker' ? context : window;
+ return getMessagePort(target).then(() => {
+ return new Promise(resolve => {
+ internal.messagePort.onmessage = channelEvent => {
+ switch (channelEvent.data.type) {
+ case mojom.WebUsbService.$interfaceName:
+ internal.webUsbService.addBinding(channelEvent.data.handle);
+ break;
+ case 'Complete':
+ resolve();
+ break;
+ }
+ };
+ internal.messagePort.postMessage({
+ type: 'Attach',
+ interfaces: [
+ mojom.WebUsbService.$interfaceName,
+ ]
+ });
+ });
+ });
+ }
+
+ addFakeDevice(deviceInit) {
+ if (!internal.initialized)
+ throw new Error('Call initialize() before addFakeDevice().');
+
+ // |addDevice| and |removeDevice| are called in a setTimeout callback so
+ // that tests do not rely on the device being immediately available which
+ // may not be true for all implementations of this test API.
+ let fakeDevice = new FakeUSBDevice();
+ setTimeout(
+ () => internal.webUsbService.addDevice(fakeDevice, deviceInit), 0);
+ return fakeDevice;
+ }
+
+ reset() {
+ if (!internal.initialized)
+ throw new Error('Call initialize() before reset().');
+
+ // Reset the mocks in a setTimeout callback so that tests do not rely on
+ // the fact that this polyfill can do this synchronously.
+ return new Promise(resolve => {
+ setTimeout(() => {
+ if (internal.messagePort !== null)
+ internal.messagePort.close();
+ internal.messagePort = null;
+ internal.webUsbService.removeAllDevices();
+ resolve();
+ }, 0);
+ });
+ }
+}
+
+navigator.usb.test = new USBTest();
+
+})();
diff --git a/test/fixtures/wpt/resources/chromium/webusb-test.js.headers b/test/fixtures/wpt/resources/chromium/webusb-test.js.headers
new file mode 100644
index 00000000000..6805c323df5
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/webusb-test.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/wpt/resources/chromium/webxr-test-math-helper.js b/test/fixtures/wpt/resources/chromium/webxr-test-math-helper.js
new file mode 100644
index 00000000000..22c6c12d08f
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/webxr-test-math-helper.js
@@ -0,0 +1,298 @@
+'use strict';
+
+// Math helper - used mainly in hit test implementation done by webxr-test.js
+class XRMathHelper {
+ static toString(p) {
+ return "[" + p.x + "," + p.y + "," + p.z + "," + p.w + "]";
+ }
+
+ static transform_by_matrix(matrix, point) {
+ return {
+ x : matrix[0] * point.x + matrix[4] * point.y + matrix[8] * point.z + matrix[12] * point.w,
+ y : matrix[1] * point.x + matrix[5] * point.y + matrix[9] * point.z + matrix[13] * point.w,
+ z : matrix[2] * point.x + matrix[6] * point.y + matrix[10] * point.z + matrix[14] * point.w,
+ w : matrix[3] * point.x + matrix[7] * point.y + matrix[11] * point.z + matrix[15] * point.w,
+ };
+ }
+
+ static neg(p) {
+ return {x : -p.x, y : -p.y, z : -p.z, w : p.w};
+ }
+
+ static sub(lhs, rhs) {
+ // .w is treated here like an entity type, 1 signifies points, 0 signifies vectors.
+ // point - point, point - vector, vector - vector are ok, vector - point is not.
+ if (lhs.w != rhs.w && lhs.w == 0.0) {
+ throw new Error("vector - point not allowed: " + toString(lhs) + "-" + toString(rhs));
+ }
+
+ return {x : lhs.x - rhs.x, y : lhs.y - rhs.y, z : lhs.z - rhs.z, w : lhs.w - rhs.w};
+ }
+
+ static add(lhs, rhs) {
+ if (lhs.w == rhs.w && lhs.w == 1.0) {
+ throw new Error("point + point not allowed", p1, p2);
+ }
+
+ return {x : lhs.x + rhs.x, y : lhs.y + rhs.y, z : lhs.z + rhs.z, w : lhs.w + rhs.w};
+ }
+
+ static cross(lhs, rhs) {
+ if (lhs.w != 0.0 || rhs.w != 0.0) {
+ throw new Error("cross product not allowed: " + toString(lhs) + "x" + toString(rhs));
+ }
+
+ return {
+ x : lhs.y * rhs.z - lhs.z * rhs.y,
+ y : lhs.z * rhs.x - lhs.x * rhs.z,
+ z : lhs.x * rhs.y - lhs.y * rhs.x,
+ w : 0
+ };
+ }
+
+ static dot(lhs, rhs) {
+ if (lhs.w != 0 || rhs.w != 0) {
+ throw new Error("dot product not allowed: " + toString(lhs) + "x" + toString(rhs));
+ }
+
+ return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
+ }
+
+ static mul(scalar, vector) {
+ if (vector.w != 0) {
+ throw new Error("scalar * vector not allowed", scalar, vector);
+ }
+
+ return {x : vector.x * scalar, y : vector.y * scalar, z : vector.z * scalar, w : vector.w};
+ }
+
+ static length(vector) {
+ return Math.sqrt(XRMathHelper.dot(vector, vector));
+ }
+
+ static normalize(vector) {
+ const l = XRMathHelper.length(vector);
+ return XRMathHelper.mul(1.0/l, vector);
+ }
+
+ // All |face|'s points and |point| must be co-planar.
+ static pointInFace(point, face) {
+ const normalize = XRMathHelper.normalize;
+ const sub = XRMathHelper.sub;
+ const length = XRMathHelper.length;
+ const cross = XRMathHelper.cross;
+
+ let onTheRight = null;
+ let previous_point = face[face.length - 1];
+
+ // |point| is in |face| if it's on the same side of all the edges.
+ for (let i = 0; i < face.length; ++i) {
+ const current_point = face[i];
+
+ const edge_direction = normalize(sub(current_point, previous_point));
+ const turn_direction = normalize(sub(point, current_point));
+
+ const sin_turn_angle = length(cross(edge_direction, turn_direction));
+
+ if (onTheRight == null) {
+ onTheRight = sin_turn_angle >= 0;
+ } else {
+ if (onTheRight && sin_turn_angle < 0) return false;
+ if (!onTheRight && sin_turn_angle > 0) return false;
+ }
+
+ previous_point = current_point;
+ }
+
+ return true;
+ }
+
+ static det2x2(m00, m01, m10, m11) {
+ return m00 * m11 - m01 * m10;
+ }
+
+ static det3x3(
+ m00, m01, m02,
+ m10, m11, m12,
+ m20, m21, m22
+ ){
+ const det2x2 = XRMathHelper.det2x2;
+
+ return m00 * det2x2(m11, m12, m21, m22)
+ - m01 * det2x2(m10, m12, m20, m22)
+ + m02 * det2x2(m10, m11, m20, m21);
+ }
+
+ static det4x4(
+ m00, m01, m02, m03,
+ m10, m11, m12, m13,
+ m20, m21, m22, m23,
+ m30, m31, m32, m33
+ ) {
+ const det3x3 = XRMathHelper.det3x3;
+
+ return m00 * det3x3(m11, m12, m13,
+ m21, m22, m23,
+ m31, m32, m33)
+ - m01 * det3x3(m10, m12, m13,
+ m20, m22, m23,
+ m30, m32, m33)
+ + m02 * det3x3(m10, m11, m13,
+ m20, m21, m23,
+ m30, m31, m33)
+ - m03 * det3x3(m10, m11, m12,
+ m20, m21, m22,
+ m30, m31, m32);
+ }
+
+ static inv2(m) {
+ // mij - i-th column, j-th row
+ const m00 = m[0], m01 = m[1], m02 = m[2], m03 = m[3];
+ const m10 = m[4], m11 = m[5], m12 = m[6], m13 = m[7];
+ const m20 = m[8], m21 = m[9], m22 = m[10], m23 = m[11];
+ const m30 = m[12], m31 = m[13], m32 = m[14], m33 = m[15];
+
+ const det = det4x4(
+ m00, m01, m02, m03,
+ m10, m11, m12, m13,
+ m20, m21, m22, m23,
+ m30, m31, m32, m33
+ );
+ }
+
+ static transpose(m) {
+ const result = Array(16);
+ for (let i = 0; i < 4; i++) {
+ for (let j = 0; j < 4; j++) {
+ result[i * 4 + j] = m[j * 4 + i];
+ }
+ }
+ return result;
+ }
+
+ // Inverts the matrix, ported from transformation_matrix.cc.
+ static inverse(m) {
+ const det3x3 = XRMathHelper.det3x3;
+
+ // mij - i-th column, j-th row
+ const m00 = m[0], m01 = m[1], m02 = m[2], m03 = m[3];
+ const m10 = m[4], m11 = m[5], m12 = m[6], m13 = m[7];
+ const m20 = m[8], m21 = m[9], m22 = m[10], m23 = m[11];
+ const m30 = m[12], m31 = m[13], m32 = m[14], m33 = m[15];
+
+ const det = XRMathHelper.det4x4(
+ m00, m01, m02, m03,
+ m10, m11, m12, m13,
+ m20, m21, m22, m23,
+ m30, m31, m32, m33
+ );
+
+ if (Math.abs(det) < 0.0001) {
+ return null;
+ }
+
+ const invDet = 1.0 / det;
+ // Calculate `comatrix * 1/det`:
+ const result2 = [
+ // First column (m0r):
+ invDet * det3x3(m11, m12, m13, m21, m22, m23, m32, m32, m33),
+ -invDet * det3x3(m10, m12, m13, m20, m22, m23, m30, m32, m33),
+ invDet * det3x3(m10, m11, m13, m20, m21, m23, m30, m31, m33),
+ -invDet * det3x3(m10, m11, m12, m20, m21, m22, m30, m31, m32),
+ // Second column (m1r):
+ -invDet * det3x3(m01, m02, m03, m21, m22, m23, m32, m32, m33),
+ invDet * det3x3(m00, m02, m03, m20, m22, m23, m30, m32, m33),
+ -invDet * det3x3(m00, m01, m03, m20, m21, m23, m30, m31, m33),
+ invDet * det3x3(m00, m01, m02, m20, m21, m22, m30, m31, m32),
+ // Third column (m2r):
+ invDet * det3x3(m01, m02, m03, m11, m12, m13, m31, m32, m33),
+ -invDet * det3x3(m00, m02, m03, m10, m12, m13, m30, m32, m33),
+ invDet * det3x3(m00, m01, m03, m10, m11, m13, m30, m31, m33),
+ -invDet * det3x3(m00, m01, m02, m10, m11, m12, m30, m31, m32),
+ // Fourth column (m3r):
+ -invDet * det3x3(m01, m02, m03, m11, m12, m13, m21, m22, m23),
+ invDet * det3x3(m00, m02, m03, m10, m12, m13, m20, m22, m23),
+ -invDet * det3x3(m00, m01, m03, m10, m11, m13, m20, m21, m23),
+ invDet * det3x3(m00, m01, m02, m10, m11, m12, m20, m21, m22),
+ ];
+
+ // Actual inverse is `1/det * transposed(comatrix)`:
+ return XRMathHelper.transpose(result2);
+ }
+
+ static mul4x4(m1, m2) {
+ if (m1 == null || m2 == null) {
+ return null;
+ }
+
+ const result = Array(16);
+
+ for (let row = 0; row < 4; row++) {
+ for (let col = 0; col < 4; col++) {
+ result[4 * col + row] = 0;
+ for(let i = 0; i < 4; i++) {
+ result[4 * col + row] += m1[4 * i + row] * m2[4 * col + i];
+ }
+ }
+ }
+
+ return result;
+ }
+
+ // Decomposes a matrix, with the assumption that the passed in matrix is
+ // a rigid transformation (i.e. position and rotation *only*!).
+ // The result is an object with `position` and `orientation` keys, which should
+ // be compatible with FakeXRRigidTransformInit.
+ // The implementation should match the behavior of gfx::Transform, but assumes
+ // that scale, skew & perspective are not present in the matrix so it could be
+ // simplified.
+ static decomposeRigidTransform(m) {
+ const m00 = m[0], m01 = m[1], m02 = m[2], m03 = m[3];
+ const m10 = m[4], m11 = m[5], m12 = m[6], m13 = m[7];
+ const m20 = m[8], m21 = m[9], m22 = m[10], m23 = m[11];
+ const m30 = m[12], m31 = m[13], m32 = m[14], m33 = m[15];
+
+ const position = [m30, m31, m32];
+ const orientation = [0, 0, 0, 0];
+
+ const trace = m00 + m11 + m22;
+ if (trace > 0) {
+ const S = Math.sqrt(trace + 1) * 2;
+ orientation[3] = 0.25 * S;
+ orientation[0] = (m12 - m21) / S;
+ orientation[1] = (m20 - m02) / S;
+ orientation[2] = (m01 - m10) / S;
+ } else if (m00 > m11 && m00 > m22) {
+ const S = Math.sqrt(1.0 + m00 - m11 - m22) * 2;
+ orientation[3] = (m12 - m21) / S;
+ orientation[0] = 0.25 * S;
+ orientation[1] = (m01 + m10) / S;
+ orientation[2] = (m20 + m02) / S;
+ } else if (m11 > m22) {
+ const S = Math.sqrt(1.0 + m11 - m00 - m22) * 2;
+ orientation[3] = (m20 - m02) / S;
+ orientation[0] = (m01 + m10) / S;
+ orientation[1] = 0.25 * S;
+ orientation[2] = (m12 + m21) / S;
+ } else {
+ const S = Math.sqrt(1.0 + m22 - m00 - m11) * 2;
+ orientation[3] = (m01 - m10) / S;
+ orientation[0] = (m20 + m02) / S;
+ orientation[1] = (m12 + m21) / S;
+ orientation[2] = 0.25 * S;
+ }
+
+ return { position, orientation };
+ }
+
+ static identity() {
+ return [
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+ ];
+ };
+}
+
+XRMathHelper.EPSILON = 0.001;
diff --git a/test/fixtures/wpt/resources/chromium/webxr-test-math-helper.js.headers b/test/fixtures/wpt/resources/chromium/webxr-test-math-helper.js.headers
new file mode 100644
index 00000000000..6805c323df5
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/webxr-test-math-helper.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/wpt/resources/chromium/webxr-test.js b/test/fixtures/wpt/resources/chromium/webxr-test.js
new file mode 100644
index 00000000000..49938dc3ec1
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/webxr-test.js
@@ -0,0 +1,2150 @@
+import * as vrMojom from '/gen/device/vr/public/mojom/vr_service.mojom.m.js';
+import * as xrSessionMojom from '/gen/device/vr/public/mojom/xr_session.mojom.m.js';
+import {GamepadHand, GamepadMapping} from '/gen/device/gamepad/public/mojom/gamepad.mojom.m.js';
+
+// This polyfill library implements the WebXR Test API as specified here:
+// https://github.com/immersive-web/webxr-test-api
+
+const defaultMojoFromFloor = {
+ matrix: [1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, -1.65, 0, 1]
+};
+const default_stage_parameters = {
+ mojoFromFloor: defaultMojoFromFloor,
+ bounds: null
+};
+
+const default_framebuffer_scale = 0.7;
+
+function getMatrixFromTransform(transform) {
+ const x = transform.orientation[0];
+ const y = transform.orientation[1];
+ const z = transform.orientation[2];
+ const w = transform.orientation[3];
+
+ const m11 = 1.0 - 2.0 * (y * y + z * z);
+ const m21 = 2.0 * (x * y + z * w);
+ const m31 = 2.0 * (x * z - y * w);
+
+ const m12 = 2.0 * (x * y - z * w);
+ const m22 = 1.0 - 2.0 * (x * x + z * z);
+ const m32 = 2.0 * (y * z + x * w);
+
+ const m13 = 2.0 * (x * z + y * w);
+ const m23 = 2.0 * (y * z - x * w);
+ const m33 = 1.0 - 2.0 * (x * x + y * y);
+
+ const m14 = transform.position[0];
+ const m24 = transform.position[1];
+ const m34 = transform.position[2];
+
+ // Column-major linearized order is expected.
+ return [m11, m21, m31, 0,
+ m12, m22, m32, 0,
+ m13, m23, m33, 0,
+ m14, m24, m34, 1];
+}
+
+function getPoseFromTransform(transform) {
+ const [px, py, pz] = transform.position;
+ const [ox, oy, oz, ow] = transform.orientation;
+ return {
+ position: {x: px, y: py, z: pz},
+ orientation: {x: ox, y: oy, z: oz, w: ow},
+ };
+}
+
+function composeGFXTransform(fakeTransformInit) {
+ return {matrix: getMatrixFromTransform(fakeTransformInit)};
+}
+
+// Value equality for camera image init objects - they must contain `width` &
+// `height` properties and may contain `pixels` property.
+function isSameCameraImageInit(rhs, lhs) {
+ return lhs.width === rhs.width && lhs.height === rhs.height && lhs.pixels === rhs.pixels;
+}
+
+class ChromeXRTest {
+ constructor() {
+ this.mockVRService_ = new MockVRService();
+ }
+
+ // WebXR Test API
+ simulateDeviceConnection(init_params) {
+ return Promise.resolve(this.mockVRService_._addRuntime(init_params));
+ }
+
+ disconnectAllDevices() {
+ this.mockVRService_._removeAllRuntimes();
+ return Promise.resolve();
+ }
+
+ simulateUserActivation(callback) {
+ if (window.top !== window) {
+ // test_driver.click only works for the toplevel frame. This alternate
+ // Chrome-specific method is sufficient for starting an XR session in an
+ // iframe, and is used in platform-specific tests.
+ //
+ // TODO(https://github.com/web-platform-tests/wpt/issues/20282): use
+ // a cross-platform method if available.
+ xr_debug('simulateUserActivation', 'use eventSender');
+ document.addEventListener('click', callback);
+ eventSender.mouseMoveTo(0, 0);
+ eventSender.mouseDown();
+ eventSender.mouseUp();
+ document.removeEventListener('click', callback);
+ return;
+ }
+ const button = document.createElement('button');
+ button.textContent = 'click to continue test';
+ button.style.display = 'block';
+ button.style.fontSize = '20px';
+ button.style.padding = '10px';
+ button.onclick = () => {
+ callback();
+ document.body.removeChild(button);
+ };
+ document.body.appendChild(button);
+ test_driver.click(button);
+ }
+
+ // Helper method leveraged by chrome-specific setups.
+ Debug(name, msg) {
+ console.log(new Date().toISOString() + ' DEBUG[' + name + '] ' + msg);
+ }
+}
+
+// Mocking class definitions
+
+// Mock service implements the VRService mojo interface.
+class MockVRService {
+ constructor() {
+ this.receiver_ = new vrMojom.VRServiceReceiver(this);
+ this.runtimes_ = [];
+
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(vrMojom.VRService.$interfaceName);
+ this.interceptor_.oninterfacerequest =
+ e => this.receiver_.$.bindHandle(e.handle);
+ this.interceptor_.start();
+ }
+
+ // WebXR Test API Implementation Helpers
+ _addRuntime(fakeDeviceInit) {
+ const runtime = new MockRuntime(fakeDeviceInit, this);
+ this.runtimes_.push(runtime);
+
+ if (this.client_) {
+ this.client_.onDeviceChanged();
+ }
+
+ return runtime;
+ }
+
+ _removeAllRuntimes() {
+ if (this.client_) {
+ this.client_.onDeviceChanged();
+ }
+
+ this.runtimes_ = [];
+ }
+
+ _removeRuntime(device) {
+ const index = this.runtimes_.indexOf(device);
+ if (index >= 0) {
+ this.runtimes_.splice(index, 1);
+ if (this.client_) {
+ this.client_.onDeviceChanged();
+ }
+ }
+ }
+
+ // VRService overrides
+ setClient(client) {
+ if (this.client_) {
+ throw new Error("setClient should only be called once");
+ }
+
+ this.client_ = client;
+ }
+
+ requestSession(sessionOptions) {
+ const requests = [];
+ // Request a session from all the runtimes.
+ for (let i = 0; i < this.runtimes_.length; i++) {
+ requests[i] = this.runtimes_[i]._requestRuntimeSession(sessionOptions);
+ }
+
+ return Promise.all(requests).then((results) => {
+ // Find and return the first successful result.
+ for (let i = 0; i < results.length; i++) {
+ if (results[i].session) {
+ // Construct a dummy metrics recorder
+ const metricsRecorderPtr = new vrMojom.XRSessionMetricsRecorderRemote();
+ metricsRecorderPtr.$.bindNewPipeAndPassReceiver().handle.close();
+
+ const success = {
+ session: results[i].session,
+ metricsRecorder: metricsRecorderPtr,
+ };
+
+ return {result: {success}};
+ }
+ }
+
+ // If there were no successful results, returns a null session.
+ return {
+ result: {failureReason: xrSessionMojom.RequestSessionError.NO_RUNTIME_FOUND}
+ };
+ });
+ }
+
+ supportsSession(sessionOptions) {
+ const requests = [];
+ // Check supports on all the runtimes.
+ for (let i = 0; i < this.runtimes_.length; i++) {
+ requests[i] = this.runtimes_[i]._runtimeSupportsSession(sessionOptions);
+ }
+
+ return Promise.all(requests).then((results) => {
+ // Find and return the first successful result.
+ for (let i = 0; i < results.length; i++) {
+ if (results[i].supportsSession) {
+ return results[i];
+ }
+ }
+
+ // If there were no successful results, returns false.
+ return {supportsSession: false};
+ });
+ }
+
+ exitPresent() {
+ return Promise.resolve();
+ }
+
+ setFramesThrottled(throttled) {
+ this.setFramesThrottledImpl(throttled);
+ }
+
+ // We cannot override the mojom interceptors via the prototype; so this method
+ // and the above indirection exist to allow overrides by internal code.
+ setFramesThrottledImpl(throttled) {}
+
+ // Only handles asynchronous calls to makeXrCompatible. Synchronous calls are
+ // not supported in Javascript.
+ makeXrCompatible() {
+ if (this.runtimes_.length == 0) {
+ return {
+ xrCompatibleResult: vrMojom.XrCompatibleResult.kNoDeviceAvailable
+ };
+ }
+ return {xrCompatibleResult: vrMojom.XrCompatibleResult.kAlreadyCompatible};
+ }
+}
+
+class FakeXRAnchorController {
+ constructor() {
+ // Private properties.
+ this.device_ = null;
+ this.id_ = null;
+ this.dirty_ = true;
+
+ // Properties backing up public attributes / methods.
+ this.deleted_ = false;
+ this.paused_ = false;
+ this.anchorOrigin_ = XRMathHelper.identity();
+ }
+
+ // WebXR Test API (Anchors Extension)
+ get deleted() {
+ return this.deleted_;
+ }
+
+ pauseTracking() {
+ if(!this.paused_) {
+ this.paused_ = true;
+ this.dirty_ = true;
+ }
+ }
+
+ resumeTracking() {
+ if(this.paused_) {
+ this.paused_ = false;
+ this.dirty_ = true;
+ }
+ }
+
+ stopTracking() {
+ if(!this.deleted_) {
+ this.device_._deleteAnchorController(this.id_);
+
+ this.deleted_ = true;
+ this.dirty_ = true;
+ }
+ }
+
+ setAnchorOrigin(anchorOrigin) {
+ this.anchorOrigin_ = getMatrixFromTransform(anchorOrigin);
+ this.dirty_ = true;
+ }
+
+ // Internal implementation:
+ set id(value) {
+ this.id_ = value;
+ }
+
+ set device(value) {
+ this.device_ = value;
+ }
+
+ get dirty() {
+ return this.dirty_;
+ }
+
+ get paused() {
+ return this.paused_;
+ }
+
+ _markProcessed() {
+ this.dirty_ = false;
+ }
+
+ _getAnchorOrigin() {
+ return this.anchorOrigin_;
+ }
+}
+
+// Implements XRFrameDataProvider and XRPresentationProvider. Maintains a mock
+// for XRPresentationProvider. Implements FakeXRDevice test API.
+class MockRuntime {
+ // Mapping from string feature names to the corresponding mojo types.
+ // This is exposed as a member for extensibility.
+ static _featureToMojoMap = {
+ 'viewer': xrSessionMojom.XRSessionFeature.REF_SPACE_VIEWER,
+ 'local': xrSessionMojom.XRSessionFeature.REF_SPACE_LOCAL,
+ 'local-floor': xrSessionMojom.XRSessionFeature.REF_SPACE_LOCAL_FLOOR,
+ 'bounded-floor': xrSessionMojom.XRSessionFeature.REF_SPACE_BOUNDED_FLOOR,
+ 'unbounded': xrSessionMojom.XRSessionFeature.REF_SPACE_UNBOUNDED,
+ 'hit-test': xrSessionMojom.XRSessionFeature.HIT_TEST,
+ 'dom-overlay': xrSessionMojom.XRSessionFeature.DOM_OVERLAY,
+ 'light-estimation': xrSessionMojom.XRSessionFeature.LIGHT_ESTIMATION,
+ 'anchors': xrSessionMojom.XRSessionFeature.ANCHORS,
+ 'depth-sensing': xrSessionMojom.XRSessionFeature.DEPTH,
+ 'secondary-views': xrSessionMojom.XRSessionFeature.SECONDARY_VIEWS,
+ 'camera-access': xrSessionMojom.XRSessionFeature.CAMERA_ACCESS,
+ 'layers': xrSessionMojom.XRSessionFeature.LAYERS,
+ };
+
+ static _sessionModeToMojoMap = {
+ "inline": xrSessionMojom.XRSessionMode.kInline,
+ "immersive-vr": xrSessionMojom.XRSessionMode.kImmersiveVr,
+ "immersive-ar": xrSessionMojom.XRSessionMode.kImmersiveAr,
+ };
+
+ static _environmentBlendModeToMojoMap = {
+ "opaque": vrMojom.XREnvironmentBlendMode.kOpaque,
+ "alpha-blend": vrMojom.XREnvironmentBlendMode.kAlphaBlend,
+ "additive": vrMojom.XREnvironmentBlendMode.kAdditive,
+ };
+
+ static _interactionModeToMojoMap = {
+ "screen-space": vrMojom.XRInteractionMode.kScreenSpace,
+ "world-space": vrMojom.XRInteractionMode.kWorldSpace,
+ };
+
+ constructor(fakeDeviceInit, service) {
+ this.sessionClient_ = null;
+ this.presentation_provider_ = new MockXRPresentationProvider();
+
+ this.pose_ = null;
+ this.next_frame_id_ = 0;
+ this.bounds_ = null;
+ this.send_mojo_space_reset_ = false;
+ this.stageParameters_ = null;
+ this.stageParametersId_ = 1;
+
+ this.service_ = service;
+
+ this.framesOfReference = {};
+
+ this.input_sources_ = new Map();
+ this.next_input_source_index_ = 1;
+
+ // Currently active hit test subscriptons.
+ this.hitTestSubscriptions_ = new Map();
+ // Currently active transient hit test subscriptions.
+ this.transientHitTestSubscriptions_ = new Map();
+ // ID of the next subscription to be assigned.
+ this.next_hit_test_id_ = 1n;
+
+ this.anchor_controllers_ = new Map();
+ // ID of the next anchor to be assigned.
+ this.next_anchor_id_ = 1n;
+ // Anchor creation callback (initially null, can be set by tests).
+ this.anchor_creation_callback_ = null;
+
+ this.depthSensingData_ = null;
+ this.depthSensingDataDirty_ = false;
+
+ let supportedModes = [];
+ if (fakeDeviceInit.supportedModes) {
+ supportedModes = fakeDeviceInit.supportedModes.slice();
+ if (fakeDeviceInit.supportedModes.length === 0) {
+ supportedModes = ["inline"];
+ }
+ } else {
+ // Back-compat mode.
+ console.warn("Please use `supportedModes` to signal which modes are supported by this device.");
+ if (fakeDeviceInit.supportsImmersive == null) {
+ throw new TypeError("'supportsImmersive' must be set");
+ }
+
+ supportedModes = ["inline"];
+ if (fakeDeviceInit.supportsImmersive) {
+ supportedModes.push("immersive-vr");
+ }
+ }
+
+ this.supportedModes_ = this._convertModesToEnum(supportedModes);
+ if (this.supportedModes_.length == 0) {
+ console.error("Device has empty supported modes array!");
+ throw new InvalidStateError();
+ }
+
+ if (fakeDeviceInit.viewerOrigin != null) {
+ this.setViewerOrigin(fakeDeviceInit.viewerOrigin);
+ }
+
+ if (fakeDeviceInit.floorOrigin != null) {
+ this.setFloorOrigin(fakeDeviceInit.floorOrigin);
+ }
+
+ if (fakeDeviceInit.world) {
+ this.setWorld(fakeDeviceInit.world);
+ }
+
+ if (fakeDeviceInit.depthSensingData) {
+ this.setDepthSensingData(fakeDeviceInit.depthSensingData);
+ }
+
+ this.defaultFramebufferScale_ = default_framebuffer_scale;
+ this.enviromentBlendMode_ = this._convertBlendModeToEnum(fakeDeviceInit.environmentBlendMode);
+ this.interactionMode_ = this._convertInteractionModeToEnum(fakeDeviceInit.interactionMode);
+
+ // This appropriately handles if the coordinates are null
+ this.setBoundsGeometry(fakeDeviceInit.boundsCoordinates);
+
+ this.setViews(fakeDeviceInit.views, fakeDeviceInit.secondaryViews);
+
+ // Need to support webVR which doesn't have a notion of features
+ this._setFeatures(fakeDeviceInit.supportedFeatures || []);
+ }
+
+ // WebXR Test API
+ setViews(primaryViews, secondaryViews) {
+ this.cameraImage_ = null;
+ this.primaryViews_ = [];
+ this.secondaryViews_ = [];
+ let xOffset = 0;
+ if (primaryViews) {
+ this.primaryViews_ = [];
+ xOffset = this._setViews(primaryViews, xOffset, this.primaryViews_);
+ const cameraImage = this._findCameraImage(primaryViews);
+
+ if (cameraImage) {
+ this.cameraImage_ = cameraImage;
+ }
+ }
+
+ if (secondaryViews) {
+ this.secondaryViews_ = [];
+ this._setViews(secondaryViews, xOffset, this.secondaryViews_);
+ const cameraImage = this._findCameraImage(secondaryViews);
+
+ if (cameraImage) {
+ if (!isSameCameraImageInit(this.cameraImage_, cameraImage)) {
+ throw new Error("If present, camera resolutions on each view must match each other!"
+ + " Secondary views' camera doesn't match primary views.");
+ }
+
+ this.cameraImage_ = cameraImage;
+ }
+ }
+ }
+
+ disconnect() {
+ this.service_._removeRuntime(this);
+ this.presentation_provider_._close();
+ if (this.sessionClient_) {
+ this.sessionClient_.$.close();
+ this.sessionClient_ = null;
+ }
+
+ return Promise.resolve();
+ }
+
+ setViewerOrigin(origin, emulatedPosition = false) {
+ const p = origin.position;
+ const q = origin.orientation;
+ this.pose_ = {
+ orientation: { x: q[0], y: q[1], z: q[2], w: q[3] },
+ position: { x: p[0], y: p[1], z: p[2] },
+ emulatedPosition: emulatedPosition,
+ angularVelocity: null,
+ linearVelocity: null,
+ angularAcceleration: null,
+ linearAcceleration: null,
+ inputState: null,
+ poseIndex: 0
+ };
+ }
+
+ clearViewerOrigin() {
+ this.pose_ = null;
+ }
+
+ setFloorOrigin(floorOrigin) {
+ if (!this.stageParameters_) {
+ this.stageParameters_ = default_stage_parameters;
+ this.stageParameters_.bounds = this.bounds_;
+ }
+
+ // floorOrigin is passed in as mojoFromFloor.
+ this.stageParameters_.mojoFromFloor =
+ {matrix: getMatrixFromTransform(floorOrigin)};
+
+ this._onStageParametersUpdated();
+ }
+
+ clearFloorOrigin() {
+ if (this.stageParameters_) {
+ this.stageParameters_ = null;
+ this._onStageParametersUpdated();
+ }
+ }
+
+ setBoundsGeometry(bounds) {
+ if (bounds == null) {
+ this.bounds_ = null;
+ } else if (bounds.length < 3) {
+ throw new Error("Bounds must have a length of at least 3");
+ } else {
+ this.bounds_ = bounds;
+ }
+
+ // We can only set bounds if we have stageParameters set; otherwise, we
+ // don't know the transform from local space to bounds space.
+ // We'll cache the bounds so that they can be set in the future if the
+ // floorLevel transform is set, but we won't update them just yet.
+ if (this.stageParameters_) {
+ this.stageParameters_.bounds = this.bounds_;
+ this._onStageParametersUpdated();
+ }
+ }
+
+ simulateResetPose() {
+ this.send_mojo_space_reset_ = true;
+ }
+
+ simulateVisibilityChange(visibilityState) {
+ let mojoState = null;
+ switch (visibilityState) {
+ case "visible":
+ mojoState = vrMojom.XRVisibilityState.VISIBLE;
+ break;
+ case "visible-blurred":
+ mojoState = vrMojom.XRVisibilityState.VISIBLE_BLURRED;
+ break;
+ case "hidden":
+ mojoState = vrMojom.XRVisibilityState.HIDDEN;
+ break;
+ }
+ if (mojoState && this.sessionClient_) {
+ this.sessionClient_.onVisibilityStateChanged(mojoState);
+ }
+ }
+
+ simulateInputSourceConnection(fakeInputSourceInit) {
+ const index = this.next_input_source_index_;
+ this.next_input_source_index_++;
+
+ const source = new MockXRInputSource(fakeInputSourceInit, index, this);
+ this.input_sources_.set(index, source);
+ return source;
+ }
+
+ // WebXR Test API Hit Test extensions
+ setWorld(world) {
+ this.world_ = world;
+ }
+
+ clearWorld() {
+ this.world_ = null;
+ }
+
+ // WebXR Test API Anchor extensions
+ setAnchorCreationCallback(callback) {
+ this.anchor_creation_callback_ = callback;
+ }
+
+ setHitTestSourceCreationCallback(callback) {
+ this.hit_test_source_creation_callback_ = callback;
+ }
+
+ // WebXR Test API Lighting estimation extensions
+ setLightEstimate(fakeXrLightEstimateInit) {
+ if (!fakeXrLightEstimateInit.sphericalHarmonicsCoefficients) {
+ throw new TypeError("sphericalHarmonicsCoefficients must be set");
+ }
+
+ if (fakeXrLightEstimateInit.sphericalHarmonicsCoefficients.length != 27) {
+ throw new TypeError("Must supply all 27 sphericalHarmonicsCoefficients");
+ }
+
+ if (fakeXrLightEstimateInit.primaryLightDirection && fakeXrLightEstimateInit.primaryLightDirection.w != 0) {
+ throw new TypeError("W component of primaryLightDirection must be 0");
+ }
+
+ if (fakeXrLightEstimateInit.primaryLightIntensity && fakeXrLightEstimateInit.primaryLightIntensity.w != 1) {
+ throw new TypeError("W component of primaryLightIntensity must be 1");
+ }
+
+ // If the primaryLightDirection or primaryLightIntensity aren't set, we need to set them
+ // to the defaults that the spec expects. ArCore will either give us everything or nothing,
+ // so these aren't nullable on the mojom.
+ if (!fakeXrLightEstimateInit.primaryLightDirection) {
+ fakeXrLightEstimateInit.primaryLightDirection = { x: 0.0, y: 1.0, z: 0.0, w: 0.0 };
+ }
+
+ if (!fakeXrLightEstimateInit.primaryLightIntensity) {
+ fakeXrLightEstimateInit.primaryLightIntensity = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 };
+ }
+
+ let c = fakeXrLightEstimateInit.sphericalHarmonicsCoefficients;
+
+ this.light_estimate_ = {
+ lightProbe: {
+ // XRSphereicalHarmonics
+ sphericalHarmonics: {
+ coefficients: [
+ { red: c[0], green: c[1], blue: c[2] },
+ { red: c[3], green: c[4], blue: c[5] },
+ { red: c[6], green: c[7], blue: c[8] },
+ { red: c[9], green: c[10], blue: c[11] },
+ { red: c[12], green: c[13], blue: c[14] },
+ { red: c[15], green: c[16], blue: c[17] },
+ { red: c[18], green: c[19], blue: c[20] },
+ { red: c[21], green: c[22], blue: c[23] },
+ { red: c[24], green: c[25], blue: c[26] }
+ ]
+ },
+ // Vector3dF
+ mainLightDirection: {
+ x: fakeXrLightEstimateInit.primaryLightDirection.x,
+ y: fakeXrLightEstimateInit.primaryLightDirection.y,
+ z: fakeXrLightEstimateInit.primaryLightDirection.z
+ },
+ // RgbTupleF32
+ mainLightIntensity: {
+ red: fakeXrLightEstimateInit.primaryLightIntensity.x,
+ green: fakeXrLightEstimateInit.primaryLightIntensity.y,
+ blue: fakeXrLightEstimateInit.primaryLightIntensity.z
+ }
+ }
+ }
+ }
+
+ // WebXR Test API depth Sensing Extensions
+ setDepthSensingData(depthSensingData) {
+ for(const key of ["depthData", "normDepthBufferFromNormView", "rawValueToMeters", "width", "height"]) {
+ if(!(key in depthSensingData)) {
+ throw new TypeError("Required key not present. Key: " + key);
+ }
+ }
+
+ if(depthSensingData.depthData != null) {
+ // Create new object w/ properties based on the depthSensingData, but
+ // convert the FakeXRRigidTransformInit into a transformation matrix object.
+ this.depthSensingData_ = Object.assign({},
+ depthSensingData, {
+ normDepthBufferFromNormView: composeGFXTransform(depthSensingData.normDepthBufferFromNormView),
+ });
+ } else {
+ throw new TypeError("`depthData` is not set");
+ }
+
+ this.depthSensingDataDirty_ = true;
+ }
+
+ clearDepthSensingData() {
+ this.depthSensingData_ = null;
+ this.depthSensingDataDirty_ = true;
+ }
+
+ // Internal Implementation/Helper Methods
+ _convertModeToEnum(sessionMode) {
+ if (sessionMode in MockRuntime._sessionModeToMojoMap) {
+ return MockRuntime._sessionModeToMojoMap[sessionMode];
+ }
+
+ throw new TypeError("Unrecognized value for XRSessionMode enum: " + sessionMode);
+ }
+
+ _convertModesToEnum(sessionModes) {
+ return sessionModes.map(mode => this._convertModeToEnum(mode));
+ }
+
+ _convertBlendModeToEnum(blendMode) {
+ if (blendMode in MockRuntime._environmentBlendModeToMojoMap) {
+ return MockRuntime._environmentBlendModeToMojoMap[blendMode];
+ } else {
+ if (this.supportedModes_.includes(xrSessionMojom.XRSessionMode.kImmersiveAr)) {
+ return vrMojom.XREnvironmentBlendMode.kAdditive;
+ } else if (this.supportedModes_.includes(
+ xrSessionMojom.XRSessionMode.kImmersiveVr)) {
+ return vrMojom.XREnvironmentBlendMode.kOpaque;
+ }
+ }
+ }
+
+ _convertInteractionModeToEnum(interactionMode) {
+ if (interactionMode in MockRuntime._interactionModeToMojoMap) {
+ return MockRuntime._interactionModeToMojoMap[interactionMode];
+ } else {
+ return vrMojom.XRInteractionMode.kWorldSpace;
+ }
+ }
+
+ _setViews(deviceViews, xOffset, views) {
+ for (let i = 0; i < deviceViews.length; i++) {
+ views[i] = this._getView(deviceViews[i], xOffset);
+ xOffset += deviceViews[i].resolution.width;
+ }
+
+ return xOffset;
+ }
+
+ _findCameraImage(views) {
+ const viewWithCamera = views.find(view => view.cameraImageInit);
+ if (viewWithCamera) {
+ //If we have one view with a camera resolution, all views should have the same camera resolution.
+ const allViewsHaveSameCamera = views.every(
+ view => isSameCameraImageInit(view.cameraImageInit, viewWithCamera.cameraImageInit));
+
+ if (!allViewsHaveSameCamera) {
+ throw new Error("If present, camera resolutions on each view must match each other!");
+ }
+
+ return viewWithCamera.cameraImageInit;
+ }
+
+ return null;
+ }
+
+ _onStageParametersUpdated() {
+ // Indicate for the frame loop that the stage parameters have been updated.
+ this.stageParametersId_++;
+ }
+
+ _getDefaultViews() {
+ if (this.primaryViews_) {
+ return this.primaryViews_;
+ }
+
+ const viewport_size = 20;
+ return [{
+ eye: vrMojom.XREye.kLeft,
+ fieldOfView: {
+ upDegrees: 48.316,
+ downDegrees: 50.099,
+ leftDegrees: 50.899,
+ rightDegrees: 35.197
+ },
+ mojoFromView: this._getMojoFromViewerWithOffset(composeGFXTransform({
+ position: [-0.032, 0, 0],
+ orientation: [0, 0, 0, 1]
+ })),
+ viewport: { x: 0, y: 0, width: viewport_size, height: viewport_size }
+ },
+ {
+ eye: vrMojom.XREye.kRight,
+ fieldOfView: {
+ upDegrees: 48.316,
+ downDegrees: 50.099,
+ leftDegrees: 50.899,
+ rightDegrees: 35.197
+ },
+ mojoFromView: this._getMojoFromViewerWithOffset(composeGFXTransform({
+ position: [0.032, 0, 0],
+ orientation: [0, 0, 0, 1]
+ })),
+ viewport: { x: viewport_size, y: 0, width: viewport_size, height: viewport_size }
+ }];
+ }
+
+ // This function converts between the matrix provided by the WebXR test API
+ // and the internal data representation.
+ _getView(fakeXRViewInit, xOffset) {
+ let fov = null;
+
+ if (fakeXRViewInit.fieldOfView) {
+ fov = {
+ upDegrees: fakeXRViewInit.fieldOfView.upDegrees,
+ downDegrees: fakeXRViewInit.fieldOfView.downDegrees,
+ leftDegrees: fakeXRViewInit.fieldOfView.leftDegrees,
+ rightDegrees: fakeXRViewInit.fieldOfView.rightDegrees
+ };
+ } else {
+ const m = fakeXRViewInit.projectionMatrix;
+
+ function toDegrees(tan) {
+ return Math.atan(tan) * 180 / Math.PI;
+ }
+
+ const leftTan = (1 - m[8]) / m[0];
+ const rightTan = (1 + m[8]) / m[0];
+ const upTan = (1 + m[9]) / m[5];
+ const downTan = (1 - m[9]) / m[5];
+
+ fov = {
+ upDegrees: toDegrees(upTan),
+ downDegrees: toDegrees(downTan),
+ leftDegrees: toDegrees(leftTan),
+ rightDegrees: toDegrees(rightTan)
+ };
+ }
+
+ let viewEye = vrMojom.XREye.kNone;
+ // The eye passed in corresponds to the values in the WebXR spec, which are
+ // the strings "none", "left", and "right". They should be converted to the
+ // corresponding values of XREye in vr_service.mojom.
+ switch(fakeXRViewInit.eye) {
+ case "none":
+ viewEye = vrMojom.XREye.kNone;
+ break;
+ case "left":
+ viewEye = vrMojom.XREye.kLeft;
+ break;
+ case "right":
+ viewEye = vrMojom.XREye.kRight;
+ break;
+ }
+
+ return {
+ eye: viewEye,
+ fieldOfView: fov,
+ mojoFromView: this._getMojoFromViewerWithOffset(composeGFXTransform(fakeXRViewInit.viewOffset)),
+ viewport: {
+ x: xOffset,
+ y: 0,
+ width: fakeXRViewInit.resolution.width,
+ height: fakeXRViewInit.resolution.height
+ },
+ isFirstPersonObserver: fakeXRViewInit.isFirstPersonObserver ? true : false,
+ viewOffset: composeGFXTransform(fakeXRViewInit.viewOffset)
+ };
+ }
+
+ _setFeatures(supportedFeatures) {
+ function convertFeatureToMojom(feature) {
+ if (feature in MockRuntime._featureToMojoMap) {
+ return MockRuntime._featureToMojoMap[feature];
+ } else {
+ return xrSessionMojom.XRSessionFeature.INVALID;
+ }
+ }
+
+ this.supportedFeatures_ = [];
+
+ for (let i = 0; i < supportedFeatures.length; i++) {
+ const feature = convertFeatureToMojom(supportedFeatures[i]);
+ if (feature !== xrSessionMojom.XRSessionFeature.INVALID) {
+ this.supportedFeatures_.push(feature);
+ }
+ }
+ }
+
+ // These methods are intended to be used by MockXRInputSource only.
+ _addInputSource(source) {
+ if (!this.input_sources_.has(source.source_id_)) {
+ this.input_sources_.set(source.source_id_, source);
+ }
+ }
+
+ _removeInputSource(source) {
+ this.input_sources_.delete(source.source_id_);
+ }
+
+ // These methods are intended to be used by FakeXRAnchorController only.
+ _deleteAnchorController(controllerId) {
+ this.anchor_controllers_.delete(controllerId);
+ }
+
+ // Extension point for non-standard modules.
+ _injectAdditionalFrameData(options, frameData) {
+ }
+
+ // Mojo function implementations.
+
+ // XRFrameDataProvider implementation.
+ getFrameData(options) {
+ return new Promise((resolve) => {
+
+ const populatePose = () => {
+ const mojo_space_reset = this.send_mojo_space_reset_;
+ this.send_mojo_space_reset_ = false;
+
+ if (this.pose_) {
+ this.pose_.poseIndex++;
+ }
+
+ // Setting the input_state to null tests a slightly different path than
+ // the browser tests where if the last input source is removed, the device
+ // code always sends up an empty array, but it's also valid mojom to send
+ // up a null array.
+ let input_state = null;
+ if (this.input_sources_.size > 0) {
+ input_state = [];
+ for (const input_source of this.input_sources_.values()) {
+ input_state.push(input_source._getInputSourceState());
+ }
+ }
+
+ let frame_views = this.primaryViews_;
+ for (let i = 0; i < this.primaryViews_.length; i++) {
+ this.primaryViews_[i].mojoFromView =
+ this._getMojoFromViewerWithOffset(this.primaryViews_[i].viewOffset);
+ }
+ if (this.enabledFeatures_.includes(xrSessionMojom.XRSessionFeature.SECONDARY_VIEWS)) {
+ for (let i = 0; i < this.secondaryViews_.length; i++) {
+ this.secondaryViews_[i].mojoFromView =
+ this._getMojoFromViewerWithOffset(this.secondaryViews_[i].viewOffset);
+ }
+
+ frame_views = frame_views.concat(this.secondaryViews_);
+ }
+
+ const frameData = {
+ mojoFromViewer: this.pose_,
+ views: frame_views,
+ mojoSpaceReset: mojo_space_reset,
+ inputState: input_state,
+ timeDelta: {
+ // window.performance.now() is in milliseconds, so convert to microseconds.
+ microseconds: BigInt(Math.floor(window.performance.now() * 1000)),
+ },
+ frameId: this.next_frame_id_,
+ bufferHolder: null,
+ cameraImageSize: this.cameraImage_ ? {
+ width: this.cameraImage_.width,
+ height: this.cameraImage_.height
+ } : null,
+ renderingTimeRatio: 0,
+ stageParameters: this.stageParameters_,
+ stageParametersId: this.stageParametersId_,
+ lightEstimationData: this.light_estimate_
+ };
+
+ this.next_frame_id_++;
+
+ this._calculateHitTestResults(frameData);
+
+ this._calculateAnchorInformation(frameData);
+
+ this._calculateDepthInformation(frameData);
+
+ this._injectAdditionalFrameData(options, frameData);
+
+ resolve({frameData});
+ };
+
+ if(this.sessionOptions_.mode == xrSessionMojom.XRSessionMode.kInline) {
+ // Inline sessions should not have a delay introduced since it causes them
+ // to miss a vsync blink-side and delays propagation of changes that happened
+ // within a rAFcb by one frame (e.g. setViewerOrigin() calls would take 2 frames
+ // to propagate).
+ populatePose();
+ } else {
+ // For immerive sessions, add additional delay to allow for anchor creation
+ // promises to run.
+ setTimeout(populatePose, 3); // note: according to MDN, the timeout is not exact
+ }
+ });
+ }
+
+ getEnvironmentIntegrationProvider(environmentProviderRequest) {
+ if (this.environmentProviderReceiver_) {
+ this.environmentProviderReceiver_.$.close();
+ }
+ this.environmentProviderReceiver_ =
+ new vrMojom.XREnvironmentIntegrationProviderReceiver(this);
+ this.environmentProviderReceiver_.$.bindHandle(
+ environmentProviderRequest.handle);
+ }
+
+ // XREnvironmentIntegrationProvider implementation:
+ subscribeToHitTest(nativeOriginInformation, entityTypes, ray) {
+ if (!this.supportedModes_.includes(xrSessionMojom.XRSessionMode.kImmersiveAr)) {
+ // Reject outside of AR.
+ return Promise.resolve({
+ result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,
+ subscriptionId : 0n
+ });
+ }
+
+ if (!this._nativeOriginKnown(nativeOriginInformation)) {
+ return Promise.resolve({
+ result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,
+ subscriptionId : 0n
+ });
+ }
+
+ // Reserve the id for hit test source:
+ const id = this.next_hit_test_id_++;
+ const hitTestParameters = { isTransient: false, profileName: null };
+ const controller = new FakeXRHitTestSourceController(id);
+
+
+ return this._shouldHitTestSourceCreationSucceed(hitTestParameters, controller)
+ .then((succeeded) => {
+ if(succeeded) {
+ // Store the subscription information as-is (including controller):
+ this.hitTestSubscriptions_.set(id, { nativeOriginInformation, entityTypes, ray, controller });
+
+ return Promise.resolve({
+ result : vrMojom.SubscribeToHitTestResult.SUCCESS,
+ subscriptionId : id
+ });
+ } else {
+ return Promise.resolve({
+ result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,
+ subscriptionId : 0n
+ });
+ }
+ });
+ }
+
+ subscribeToHitTestForTransientInput(profileName, entityTypes, ray){
+ if (!this.supportedModes_.includes(xrSessionMojom.XRSessionMode.kImmersiveAr)) {
+ // Reject outside of AR.
+ return Promise.resolve({
+ result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,
+ subscriptionId : 0n
+ });
+ }
+
+ const id = this.next_hit_test_id_++;
+ const hitTestParameters = { isTransient: true, profileName: profileName };
+ const controller = new FakeXRHitTestSourceController(id);
+
+ // Check if we have hit test source creation callback.
+ // If yes, ask it if the hit test source creation should succeed.
+ // If no, for back-compat, assume the hit test source creation succeeded.
+ return this._shouldHitTestSourceCreationSucceed(hitTestParameters, controller)
+ .then((succeeded) => {
+ if(succeeded) {
+ // Store the subscription information as-is (including controller):
+ this.transientHitTestSubscriptions_.set(id, { profileName, entityTypes, ray, controller });
+
+ return Promise.resolve({
+ result : vrMojom.SubscribeToHitTestResult.SUCCESS,
+ subscriptionId : id
+ });
+ } else {
+ return Promise.resolve({
+ result : vrMojom.SubscribeToHitTestResult.FAILURE_GENERIC,
+ subscriptionId : 0n
+ });
+ }
+ });
+ }
+
+ unsubscribeFromHitTest(subscriptionId) {
+ let controller = null;
+ if(this.transientHitTestSubscriptions_.has(subscriptionId)){
+ controller = this.transientHitTestSubscriptions_.get(subscriptionId).controller;
+ this.transientHitTestSubscriptions_.delete(subscriptionId);
+ } else if(this.hitTestSubscriptions_.has(subscriptionId)){
+ controller = this.hitTestSubscriptions_.get(subscriptionId).controller;
+ this.hitTestSubscriptions_.delete(subscriptionId);
+ }
+
+ if(controller) {
+ controller.deleted = true;
+ }
+ }
+
+ createAnchor(nativeOriginInformation, nativeOriginFromAnchor) {
+ return new Promise((resolve) => {
+ if(this.anchor_creation_callback_ == null) {
+ resolve({
+ result : vrMojom.CreateAnchorResult.FAILURE,
+ anchorId : 0n
+ });
+
+ return;
+ }
+
+ const mojoFromNativeOrigin = this._getMojoFromNativeOrigin(nativeOriginInformation);
+ if(mojoFromNativeOrigin == null) {
+ resolve({
+ result : vrMojom.CreateAnchorResult.FAILURE,
+ anchorId : 0n
+ });
+
+ return;
+ }
+
+ const mojoFromAnchor = XRMathHelper.mul4x4(mojoFromNativeOrigin, nativeOriginFromAnchor);
+
+ const anchorCreationParameters = {
+ requestedAnchorOrigin: mojoFromAnchor,
+ isAttachedToEntity: false,
+ };
+
+ const anchorController = new FakeXRAnchorController();
+
+ this.anchor_creation_callback_(anchorCreationParameters, anchorController)
+ .then((result) => {
+ if(result) {
+ // If the test allowed the anchor creation,
+ // store the anchor controller & return success.
+
+ const anchor_id = this.next_anchor_id_;
+ this.next_anchor_id_++;
+
+ this.anchor_controllers_.set(anchor_id, anchorController);
+ anchorController.device = this;
+ anchorController.id = anchor_id;
+
+ resolve({
+ result : vrMojom.CreateAnchorResult.SUCCESS,
+ anchorId : anchor_id
+ });
+ } else {
+ // The test has rejected anchor creation.
+ resolve({
+ result : vrMojom.CreateAnchorResult.FAILURE,
+ anchorId : 0n
+ });
+ }
+ })
+ .catch(() => {
+ // The test threw an error, treat anchor creation as failed.
+ resolve({
+ result : vrMojom.CreateAnchorResult.FAILURE,
+ anchorId : 0n
+ });
+ });
+ });
+ }
+
+ createPlaneAnchor(planeFromAnchor, planeId) {
+ return new Promise((resolve) => {
+
+ // Not supported yet.
+
+ resolve({
+ result : vrMojom.CreateAnchorResult.FAILURE,
+ anchorId : 0n,
+ });
+ });
+ }
+
+ detachAnchor(anchorId) {}
+
+ // Utility function
+ _requestRuntimeSession(sessionOptions) {
+ return this._runtimeSupportsSession(sessionOptions).then((result) => {
+ // The JavaScript bindings convert c_style_names to camelCase names.
+ const options = {
+ transportMethod:
+ vrMojom.XRPresentationTransportMethod.SUBMIT_AS_MAILBOX_HOLDER,
+ waitForTransferNotification: true,
+ waitForRenderNotification: true,
+ waitForGpuFence: false,
+ };
+
+ let submit_frame_sink;
+ if (result.supportsSession) {
+ submit_frame_sink = {
+ clientReceiver: this.presentation_provider_._getClientReceiver(),
+ provider: this.presentation_provider_._bindProvider(sessionOptions),
+ transportOptions: options
+ };
+
+ const dataProviderPtr = new vrMojom.XRFrameDataProviderRemote();
+ this.dataProviderReceiver_ =
+ new vrMojom.XRFrameDataProviderReceiver(this);
+ this.dataProviderReceiver_.$.bindHandle(
+ dataProviderPtr.$.bindNewPipeAndPassReceiver().handle);
+ this.sessionOptions_ = sessionOptions;
+
+ this.sessionClient_ = new vrMojom.XRSessionClientRemote();
+ const clientReceiver = this.sessionClient_.$.bindNewPipeAndPassReceiver();
+
+ const enabled_features = [];
+ for (let i = 0; i < sessionOptions.requiredFeatures.length; i++) {
+ const feature = sessionOptions.requiredFeatures[i];
+ if (this._runtimeSupportsFeature(feature, sessionOptions)) {
+ enabled_features.push(feature);
+ } else {
+ return Promise.resolve({session: null});
+ }
+ }
+
+ for (let i =0; i < sessionOptions.optionalFeatures.length; i++) {
+ const feature = sessionOptions.optionalFeatures[i];
+ if (this._runtimeSupportsFeature(feature, sessionOptions)) {
+ enabled_features.push(feature);
+ }
+ }
+
+ this.enabledFeatures_ = enabled_features;
+
+ return Promise.resolve({
+ session: {
+ submitFrameSink: submit_frame_sink,
+ dataProvider: dataProviderPtr,
+ clientReceiver: clientReceiver,
+ enabledFeatures: enabled_features,
+ deviceConfig: {
+ defaultFramebufferScale: this.defaultFramebufferScale_,
+ supportsViewportScaling: true,
+ depthConfiguration: enabled_features.includes(
+ xrSessionMojom.XRSessionFeature.DEPTH) ?
+ {
+ depthUsage: xrSessionMojom.XRDepthUsage.kCPUOptimized,
+ depthDataFormat:
+ xrSessionMojom.XRDepthDataFormat.kLuminanceAlpha,
+ } :
+ null,
+ views: this._getDefaultViews(),
+ },
+ enviromentBlendMode: this.enviromentBlendMode_,
+ interactionMode: this.interactionMode_
+ }
+ });
+ } else {
+ return Promise.resolve({session: null});
+ }
+ });
+ }
+
+ _runtimeSupportsSession(options) {
+ let result = this.supportedModes_.includes(options.mode);
+ return Promise.resolve({
+ supportsSession: result,
+ });
+ }
+
+ _runtimeSupportsFeature(feature, options) {
+ if (this.supportedFeatures_.indexOf(feature) === -1) {
+ return false;
+ }
+
+ switch (feature) {
+ case xrSessionMojom.XRSessionFeature.DEPTH:
+ // This matches what Chrome can currently support.
+ return options.depthOptions &&
+ (options.depthOptions.usagePreferences.length == 0 ||
+ options.depthOptions.usagePreferences.includes(
+ xrSessionMojom.XRDepthUsage.kCPUOptimized)) &&
+ (options.depthOptions.dataFormatPreferences.length == 0 ||
+ options.depthOptions.dataFormatPreferences.includes(
+ xrSessionMojom.XRDepthDataFormat.kLuminanceAlpha));
+ default:
+ return true;
+ }
+ }
+
+ // Private functions - utilities:
+ _nativeOriginKnown(nativeOriginInformation){
+
+ if (nativeOriginInformation.inputSourceSpaceInfo !== undefined) {
+ if (!this.input_sources_.has(nativeOriginInformation.inputSourceSpaceInfo.inputSourceId)) {
+ // Unknown input source.
+ return false;
+ }
+
+ return true;
+ } else if (nativeOriginInformation.referenceSpaceType !== undefined) {
+ // Bounded_floor & unbounded ref spaces are not yet supported for AR:
+ if (nativeOriginInformation.referenceSpaceType == vrMojom.XRReferenceSpaceType.kUnbounded
+ || nativeOriginInformation.referenceSpaceType == vrMojom.XRReferenceSpaceType.kBoundedFloor) {
+ return false;
+ }
+
+ return true;
+ } else {
+ // Planes and anchors are not yet supported by the mock interface.
+ return false;
+ }
+ }
+
+ // Private functions - anchors implementation:
+
+ // Modifies passed in frameData to add anchor information.
+ _calculateAnchorInformation(frameData) {
+ if (!this.supportedModes_.includes(xrSessionMojom.XRSessionMode.kImmersiveAr)) {
+ return;
+ }
+
+ frameData.anchorsData = {allAnchorsIds: [], updatedAnchorsData: []};
+ for(const [id, controller] of this.anchor_controllers_) {
+ frameData.anchorsData.allAnchorsIds.push(id);
+
+ // Send the entire anchor data over if there was a change since last GetFrameData().
+ if(controller.dirty) {
+ const anchorData = {id};
+ if(!controller.paused) {
+ anchorData.mojoFromAnchor = getPoseFromTransform(
+ XRMathHelper.decomposeRigidTransform(
+ controller._getAnchorOrigin()));
+ }
+
+ controller._markProcessed();
+
+ frameData.anchorsData.updatedAnchorsData.push(anchorData);
+ }
+ }
+ }
+
+ // Private functions - depth sensing implementation:
+
+ // Modifies passed in frameData to add anchor information.
+ _calculateDepthInformation(frameData) {
+ if (!this.supportedModes_.includes(xrSessionMojom.XRSessionMode.kImmersiveAr)) {
+ return;
+ }
+
+ if (!this.enabledFeatures_.includes(xrSessionMojom.XRSessionFeature.DEPTH)) {
+ return;
+ }
+
+ let newDepthData;
+
+ // If we don't have a current depth data, we'll return null
+ // (i.e. no data is not a valid data, so it cannot be "StillValid").
+ if (this.depthSensingData_ == null) {
+ newDepthData = null;
+ } else if(!this.depthSensingDataDirty_) {
+ newDepthData = { dataStillValid: {}};
+ } else {
+ newDepthData = {
+ updatedDepthData: {
+ timeDelta: frameData.timeDelta,
+ normTextureFromNormView: this.depthSensingData_.normDepthBufferFromNormView,
+ rawValueToMeters: this.depthSensingData_.rawValueToMeters,
+ size: { width: this.depthSensingData_.width, height: this.depthSensingData_.height },
+ pixelData: { bytes: this.depthSensingData_.depthData }
+ }
+ };
+ }
+
+ for (let i = 0; i < this.primaryViews_.length; i++) {
+ this.primaryViews_[i].depthData = newDepthData;
+ }
+ if (this.enabledFeatures_.includes(xrSessionMojom.XRSessionFeature.SECONDARY_VIEWS)) {
+ for (let i = 0; i < this.secondaryViews_.length; i++) {
+ this.secondaryViews_[i].depthData = newDepthData;
+ }
+ }
+
+ this.depthSensingDataDirty_ = false;
+ }
+
+ // Private functions - hit test implementation:
+
+ // Returns a Promise that signifies whether hit test source creation should succeed.
+ // If we have a hit test source creation callback installed, invoke it and return its result.
+ // If it's not installed, for back-compat just return a promise that resolves to true.
+ _shouldHitTestSourceCreationSucceed(hitTestParameters, controller) {
+ if(this.hit_test_source_creation_callback_) {
+ return this.hit_test_source_creation_callback_(hitTestParameters, controller);
+ } else {
+ return Promise.resolve(true);
+ }
+ }
+
+ // Modifies passed in frameData to add hit test results.
+ _calculateHitTestResults(frameData) {
+ if (!this.supportedModes_.includes(xrSessionMojom.XRSessionMode.kImmersiveAr)) {
+ return;
+ }
+
+ frameData.hitTestSubscriptionResults = {results: [],
+ transientInputResults: []};
+ if (!this.world_) {
+ return;
+ }
+
+ // Non-transient hit test:
+ for (const [id, subscription] of this.hitTestSubscriptions_) {
+ const mojo_from_native_origin = this._getMojoFromNativeOrigin(subscription.nativeOriginInformation);
+ if (!mojo_from_native_origin) continue;
+
+ const [mojo_ray_origin, mojo_ray_direction] = this._transformRayToMojoSpace(
+ subscription.ray,
+ mojo_from_native_origin
+ );
+
+ const results = this._hitTestWorld(mojo_ray_origin, mojo_ray_direction, subscription.entityTypes);
+ frameData.hitTestSubscriptionResults.results.push(
+ {subscriptionId: id, hitTestResults: results});
+ }
+
+ // Transient hit test:
+ const mojo_from_viewer = this._getMojoFromViewer();
+
+ for (const [id, subscription] of this.transientHitTestSubscriptions_) {
+ const result = {subscriptionId: id,
+ inputSourceIdToHitTestResults: new Map()};
+
+ // Find all input sources that match the profile name:
+ const matching_input_sources = Array.from(this.input_sources_.values())
+ .filter(input_source => input_source.profiles_.includes(subscription.profileName));
+
+ for (const input_source of matching_input_sources) {
+ const mojo_from_native_origin = input_source._getMojoFromInputSource(mojo_from_viewer);
+
+ const [mojo_ray_origin, mojo_ray_direction] = this._transformRayToMojoSpace(
+ subscription.ray,
+ mojo_from_native_origin
+ );
+
+ const results = this._hitTestWorld(mojo_ray_origin, mojo_ray_direction, subscription.entityTypes);
+
+ result.inputSourceIdToHitTestResults.set(input_source.source_id_, results);
+ }
+
+ frameData.hitTestSubscriptionResults.transientInputResults.push(result);
+ }
+ }
+
+ // Returns 2-element array [origin, direction] of a ray in mojo space.
+ // |ray| is expressed relative to native origin.
+ _transformRayToMojoSpace(ray, mojo_from_native_origin) {
+ const ray_origin = {
+ x: ray.origin.x,
+ y: ray.origin.y,
+ z: ray.origin.z,
+ w: 1
+ };
+ const ray_direction = {
+ x: ray.direction.x,
+ y: ray.direction.y,
+ z: ray.direction.z,
+ w: 0
+ };
+
+ const mojo_ray_origin = XRMathHelper.transform_by_matrix(
+ mojo_from_native_origin,
+ ray_origin);
+ const mojo_ray_direction = XRMathHelper.transform_by_matrix(
+ mojo_from_native_origin,
+ ray_direction);
+
+ return [mojo_ray_origin, mojo_ray_direction];
+ }
+
+ // Hit tests the passed in ray (expressed as origin and direction) against the mocked world data.
+ _hitTestWorld(origin, direction, entityTypes) {
+ let result = [];
+
+ for (const region of this.world_.hitTestRegions) {
+ const partial_result = this._hitTestRegion(
+ region,
+ origin, direction,
+ entityTypes);
+
+ result = result.concat(partial_result);
+ }
+
+ return result.sort((lhs, rhs) => lhs.distance - rhs.distance).map((hitTest) => {
+ delete hitTest.distance;
+ return hitTest;
+ });
+ }
+
+ // Hit tests the passed in ray (expressed as origin and direction) against world region.
+ // |entityTypes| is a set of FakeXRRegionTypes.
+ // |region| is FakeXRRegion.
+ // Returns array of XRHitResults, each entry will be decorated with the distance from the ray origin (along the ray).
+ _hitTestRegion(region, origin, direction, entityTypes) {
+ const regionNameToMojoEnum = {
+ "point": vrMojom.EntityTypeForHitTest.POINT,
+ "plane": vrMojom.EntityTypeForHitTest.PLANE,
+ "mesh":null
+ };
+
+ if (!entityTypes.includes(regionNameToMojoEnum[region.type])) {
+ return [];
+ }
+
+ const result = [];
+ for (const face of region.faces) {
+ const maybe_hit = this._hitTestFace(face, origin, direction);
+ if (maybe_hit) {
+ result.push(maybe_hit);
+ }
+ }
+
+ // The results should be sorted by distance and there should be no 2 entries with
+ // the same distance from ray origin - that would mean they are the same point.
+ // This situation is possible when a ray intersects the region through an edge shared
+ // by 2 faces.
+ return result.sort((lhs, rhs) => lhs.distance - rhs.distance)
+ .filter((val, index, array) => index === 0 || val.distance !== array[index - 1].distance);
+ }
+
+ // Hit tests the passed in ray (expressed as origin and direction) against a single face.
+ // |face|, |origin|, and |direction| are specified in world (aka mojo) coordinates.
+ // |face| is an array of DOMPointInits.
+ // Returns null if the face does not intersect with the ray, otherwise the result is
+ // an XRHitResult with matrix describing the pose of the intersection point.
+ _hitTestFace(face, origin, direction) {
+ const add = XRMathHelper.add;
+ const sub = XRMathHelper.sub;
+ const mul = XRMathHelper.mul;
+ const normalize = XRMathHelper.normalize;
+ const dot = XRMathHelper.dot;
+ const cross = XRMathHelper.cross;
+ const neg = XRMathHelper.neg;
+
+ //1. Calculate plane normal in world coordinates.
+ const point_A = face.vertices[0];
+ const point_B = face.vertices[1];
+ const point_C = face.vertices[2];
+
+ const edge_AB = sub(point_B, point_A);
+ const edge_AC = sub(point_C, point_A);
+
+ const normal = normalize(cross(edge_AB, edge_AC));
+
+ const numerator = dot(sub(point_A, origin), normal);
+ const denominator = dot(direction, normal);
+
+ if (Math.abs(denominator) < XRMathHelper.EPSILON) {
+ // Planes are nearly parallel - there's either infinitely many intersection points or 0.
+ // Both cases signify a "no hit" for us.
+ return null;
+ } else {
+ // Single intersection point between the infinite plane and the line (*not* ray).
+ // Need to calculate the hit test matrix taking into account the face vertices.
+ const distance = numerator / denominator;
+ if (distance < 0) {
+ // Line - plane intersection exists, but not the half-line - plane does not.
+ return null;
+ } else {
+ const intersection_point = add(origin, mul(distance, direction));
+ // Since we are treating the face as a solid, flip the normal so that its
+ // half-space will contain the ray origin.
+ const y_axis = denominator > 0 ? neg(normal) : normal;
+
+ let z_axis = null;
+ const cos_direction_and_y_axis = dot(direction, y_axis);
+ if (Math.abs(cos_direction_and_y_axis) > (1 - XRMathHelper.EPSILON)) {
+ // Ray and the hit test normal are co-linear - try using the 'up' or 'right' vector's projection on the face plane as the Z axis.
+ // Note: this edge case is currently not covered by the spec.
+ const up = {x: 0.0, y: 1.0, z: 0.0, w: 0.0};
+ const right = {x: 1.0, y: 0.0, z: 0.0, w: 0.0};
+
+ z_axis = Math.abs(dot(up, y_axis)) > (1 - XRMathHelper.EPSILON)
+ ? sub(up, mul(dot(right, y_axis), y_axis)) // `up is also co-linear with hit test normal, use `right`
+ : sub(up, mul(dot(up, y_axis), y_axis)); // `up` is not co-linear with hit test normal, use it
+ } else {
+ // Project the ray direction onto the plane, negate it and use as a Z axis.
+ z_axis = neg(sub(direction, mul(cos_direction_and_y_axis, y_axis))); // Z should point towards the ray origin, not away.
+ }
+
+ z_axis = normalize(z_axis);
+ const x_axis = normalize(cross(y_axis, z_axis));
+
+ // Filter out the points not in polygon.
+ if (!XRMathHelper.pointInFace(intersection_point, face)) {
+ return null;
+ }
+
+ const hitResult = {planeId: 0n};
+ hitResult.distance = distance; // Extend the object with additional information used by higher layers.
+ // It will not be serialized over mojom.
+
+ const matrix = new Array(16);
+
+ matrix[0] = x_axis.x;
+ matrix[1] = x_axis.y;
+ matrix[2] = x_axis.z;
+ matrix[3] = 0;
+
+ matrix[4] = y_axis.x;
+ matrix[5] = y_axis.y;
+ matrix[6] = y_axis.z;
+ matrix[7] = 0;
+
+ matrix[8] = z_axis.x;
+ matrix[9] = z_axis.y;
+ matrix[10] = z_axis.z;
+ matrix[11] = 0;
+
+ matrix[12] = intersection_point.x;
+ matrix[13] = intersection_point.y;
+ matrix[14] = intersection_point.z;
+ matrix[15] = 1;
+
+ hitResult.mojoFromResult = getPoseFromTransform(
+ XRMathHelper.decomposeRigidTransform(matrix));
+ return hitResult;
+ }
+ }
+ }
+
+ _getMojoFromViewer() {
+ if (!this.pose_) {
+ return XRMathHelper.identity();
+ }
+ const transform = {
+ position: [
+ this.pose_.position.x,
+ this.pose_.position.y,
+ this.pose_.position.z],
+ orientation: [
+ this.pose_.orientation.x,
+ this.pose_.orientation.y,
+ this.pose_.orientation.z,
+ this.pose_.orientation.w],
+ };
+
+ return getMatrixFromTransform(transform);
+ }
+
+ _getMojoFromViewerWithOffset(viewOffset) {
+ return { matrix: XRMathHelper.mul4x4(this._getMojoFromViewer(), viewOffset.matrix) };
+ }
+
+ _getMojoFromNativeOrigin(nativeOriginInformation) {
+ const mojo_from_viewer = this._getMojoFromViewer();
+
+ if (nativeOriginInformation.inputSourceSpaceInfo !== undefined) {
+ if (!this.input_sources_.has(nativeOriginInformation.inputSourceSpaceInfo.inputSourceId)) {
+ return null;
+ } else {
+ const inputSource = this.input_sources_.get(nativeOriginInformation.inputSourceSpaceInfo.inputSourceId);
+ return inputSource._getMojoFromInputSource(mojo_from_viewer);
+ }
+ } else if (nativeOriginInformation.referenceSpaceType !== undefined) {
+ switch (nativeOriginInformation.referenceSpaceType) {
+ case vrMojom.XRReferenceSpaceType.kLocal:
+ return XRMathHelper.identity();
+ case vrMojom.XRReferenceSpaceType.kLocalFloor:
+ if (this.stageParameters_ == null || this.stageParameters_.mojoFromFloor == null) {
+ console.warn("Standing transform not available.");
+ return null;
+ }
+ return this.stageParameters_.mojoFromFloor.matrix;
+ case vrMojom.XRReferenceSpaceType.kViewer:
+ return mojo_from_viewer;
+ case vrMojom.XRReferenceSpaceType.kBoundedFloor:
+ return null;
+ case vrMojom.XRReferenceSpaceType.kUnbounded:
+ return null;
+ default:
+ throw new TypeError("Unrecognized XRReferenceSpaceType!");
+ }
+ } else {
+ // Anchors & planes are not yet supported for hit test.
+ return null;
+ }
+ }
+}
+
+class MockXRInputSource {
+ constructor(fakeInputSourceInit, id, pairedDevice) {
+ this.source_id_ = id;
+ this.pairedDevice_ = pairedDevice;
+ this.handedness_ = fakeInputSourceInit.handedness;
+ this.target_ray_mode_ = fakeInputSourceInit.targetRayMode;
+
+ if (fakeInputSourceInit.pointerOrigin == null) {
+ throw new TypeError("FakeXRInputSourceInit.pointerOrigin is required.");
+ }
+
+ this.setPointerOrigin(fakeInputSourceInit.pointerOrigin);
+ this.setProfiles(fakeInputSourceInit.profiles);
+
+ this.primary_input_pressed_ = false;
+ if (fakeInputSourceInit.selectionStarted != null) {
+ this.primary_input_pressed_ = fakeInputSourceInit.selectionStarted;
+ }
+
+ this.primary_input_clicked_ = false;
+ if (fakeInputSourceInit.selectionClicked != null) {
+ this.primary_input_clicked_ = fakeInputSourceInit.selectionClicked;
+ }
+
+ this.primary_squeeze_pressed_ = false;
+ this.primary_squeeze_clicked_ = false;
+
+ this.mojo_from_input_ = null;
+ if (fakeInputSourceInit.gripOrigin != null) {
+ this.setGripOrigin(fakeInputSourceInit.gripOrigin);
+ }
+
+ // This properly handles if supportedButtons were not specified.
+ this.setSupportedButtons(fakeInputSourceInit.supportedButtons);
+
+ this.emulated_position_ = false;
+ this.desc_dirty_ = true;
+ }
+
+ // WebXR Test API
+ setHandedness(handedness) {
+ if (this.handedness_ != handedness) {
+ this.desc_dirty_ = true;
+ this.handedness_ = handedness;
+ }
+ }
+
+ setTargetRayMode(targetRayMode) {
+ if (this.target_ray_mode_ != targetRayMode) {
+ this.desc_dirty_ = true;
+ this.target_ray_mode_ = targetRayMode;
+ }
+ }
+
+ setProfiles(profiles) {
+ this.desc_dirty_ = true;
+ this.profiles_ = profiles;
+ }
+
+ setGripOrigin(transform, emulatedPosition = false) {
+ // grip_origin was renamed to mojo_from_input in mojo
+ this.mojo_from_input_ = composeGFXTransform(transform);
+ this.emulated_position_ = emulatedPosition;
+
+ // Technically, setting the grip shouldn't make the description dirty, but
+ // the webxr-test-api sets our pointer as mojoFromPointer; however, we only
+ // support it across mojom as inputFromPointer, so we need to recalculate it
+ // whenever the grip moves.
+ this.desc_dirty_ = true;
+ }
+
+ clearGripOrigin() {
+ // grip_origin was renamed to mojo_from_input in mojo
+ if (this.mojo_from_input_ != null) {
+ this.mojo_from_input_ = null;
+ this.emulated_position_ = false;
+ this.desc_dirty_ = true;
+ }
+ }
+
+ setPointerOrigin(transform, emulatedPosition = false) {
+ // pointer_origin is mojo_from_pointer.
+ this.desc_dirty_ = true;
+ this.mojo_from_pointer_ = composeGFXTransform(transform);
+ this.emulated_position_ = emulatedPosition;
+ }
+
+ disconnect() {
+ this.pairedDevice_._removeInputSource(this);
+ }
+
+ reconnect() {
+ this.pairedDevice_._addInputSource(this);
+ }
+
+ startSelection() {
+ this.primary_input_pressed_ = true;
+ if (this.gamepad_) {
+ this.gamepad_.buttons[0].pressed = true;
+ this.gamepad_.buttons[0].touched = true;
+ }
+ }
+
+ endSelection() {
+ if (!this.primary_input_pressed_) {
+ throw new Error("Attempted to end selection which was not started");
+ }
+
+ this.primary_input_pressed_ = false;
+ this.primary_input_clicked_ = true;
+
+ if (this.gamepad_) {
+ this.gamepad_.buttons[0].pressed = false;
+ this.gamepad_.buttons[0].touched = false;
+ }
+ }
+
+ simulateSelect() {
+ this.primary_input_clicked_ = true;
+ }
+
+ setSupportedButtons(supportedButtons) {
+ this.gamepad_ = null;
+ this.supported_buttons_ = [];
+
+ // If there are no supported buttons, we can stop now.
+ if (supportedButtons == null || supportedButtons.length < 1) {
+ return;
+ }
+
+ const supported_button_map = {};
+ this.gamepad_ = this._getEmptyGamepad();
+ for (let i = 0; i < supportedButtons.length; i++) {
+ const buttonType = supportedButtons[i].buttonType;
+ this.supported_buttons_.push(buttonType);
+ supported_button_map[buttonType] = supportedButtons[i];
+ }
+
+ // Let's start by building the button state in order of priority:
+ // Primary button is index 0.
+ this.gamepad_.buttons.push({
+ pressed: this.primary_input_pressed_,
+ touched: this.primary_input_pressed_,
+ value: this.primary_input_pressed_ ? 1.0 : 0.0
+ });
+
+ // Now add the rest of our buttons
+ this._addGamepadButton(supported_button_map['grip']);
+ this._addGamepadButton(supported_button_map['touchpad']);
+ this._addGamepadButton(supported_button_map['thumbstick']);
+ this._addGamepadButton(supported_button_map['optional-button']);
+ this._addGamepadButton(supported_button_map['optional-thumbstick']);
+
+ // Finally, back-fill placeholder buttons/axes
+ for (let i = 0; i < this.gamepad_.buttons.length; i++) {
+ if (this.gamepad_.buttons[i] == null) {
+ this.gamepad_.buttons[i] = {
+ pressed: false,
+ touched: false,
+ value: 0
+ };
+ }
+ }
+
+ for (let i=0; i < this.gamepad_.axes.length; i++) {
+ if (this.gamepad_.axes[i] == null) {
+ this.gamepad_.axes[i] = 0;
+ }
+ }
+ }
+
+ updateButtonState(buttonState) {
+ if (this.supported_buttons_.indexOf(buttonState.buttonType) == -1) {
+ throw new Error("Tried to update state on an unsupported button");
+ }
+
+ const buttonIndex = this._getButtonIndex(buttonState.buttonType);
+ const axesStartIndex = this._getAxesStartIndex(buttonState.buttonType);
+
+ if (buttonIndex == -1) {
+ throw new Error("Unknown Button Type!");
+ }
+
+ // is this a 'squeeze' button?
+ if (buttonIndex === this._getButtonIndex('grip')) {
+ // squeeze
+ if (buttonState.pressed) {
+ this.primary_squeeze_pressed_ = true;
+ } else if (this.gamepad_.buttons[buttonIndex].pressed) {
+ this.primary_squeeze_clicked_ = true;
+ this.primary_squeeze_pressed_ = false;
+ } else {
+ this.primary_squeeze_clicked_ = false;
+ this.primary_squeeze_pressed_ = false;
+ }
+ }
+
+ this.gamepad_.buttons[buttonIndex].pressed = buttonState.pressed;
+ this.gamepad_.buttons[buttonIndex].touched = buttonState.touched;
+ this.gamepad_.buttons[buttonIndex].value = buttonState.pressedValue;
+
+ if (axesStartIndex != -1) {
+ this.gamepad_.axes[axesStartIndex] = buttonState.xValue == null ? 0.0 : buttonState.xValue;
+ this.gamepad_.axes[axesStartIndex + 1] = buttonState.yValue == null ? 0.0 : buttonState.yValue;
+ }
+ }
+
+ // DOM Overlay Extensions
+ setOverlayPointerPosition(x, y) {
+ this.overlay_pointer_position_ = {x: x, y: y};
+ }
+
+ // Helpers for Mojom
+ _getInputSourceState() {
+ const input_state = {};
+
+ input_state.sourceId = this.source_id_;
+ input_state.isAuxiliary = false;
+
+ input_state.primaryInputPressed = this.primary_input_pressed_;
+ input_state.primaryInputClicked = this.primary_input_clicked_;
+
+ input_state.primarySqueezePressed = this.primary_squeeze_pressed_;
+ input_state.primarySqueezeClicked = this.primary_squeeze_clicked_;
+ // Setting the input source's "clicked" state should generate one "select"
+ // event. Reset the input value to prevent it from continuously generating
+ // events.
+ this.primary_input_clicked_ = false;
+ // Setting the input source's "clicked" state should generate one "squeeze"
+ // event. Reset the input value to prevent it from continuously generating
+ // events.
+ this.primary_squeeze_clicked_ = false;
+
+ input_state.mojoFromInput = this.mojo_from_input_;
+
+ input_state.gamepad = this.gamepad_;
+
+ input_state.emulatedPosition = this.emulated_position_;
+
+ if (this.desc_dirty_) {
+ const input_desc = {};
+
+ switch (this.target_ray_mode_) {
+ case 'gaze':
+ input_desc.targetRayMode = vrMojom.XRTargetRayMode.GAZING;
+ break;
+ case 'tracked-pointer':
+ input_desc.targetRayMode = vrMojom.XRTargetRayMode.POINTING;
+ break;
+ case 'screen':
+ input_desc.targetRayMode = vrMojom.XRTargetRayMode.TAPPING;
+ break;
+ default:
+ throw new Error('Unhandled target ray mode ' + this.target_ray_mode_);
+ }
+
+ switch (this.handedness_) {
+ case 'left':
+ input_desc.handedness = vrMojom.XRHandedness.LEFT;
+ break;
+ case 'right':
+ input_desc.handedness = vrMojom.XRHandedness.RIGHT;
+ break;
+ default:
+ input_desc.handedness = vrMojom.XRHandedness.NONE;
+ break;
+ }
+
+ // Mojo requires us to send the pointerOrigin as relative to the grip
+ // space. If we don't have a grip space, we'll just assume that there
+ // is a grip at identity. This allows tests to simulate controllers that
+ // are really just a pointer with no tracked grip, though we will end up
+ // exposing that grip space.
+ let mojo_from_input = XRMathHelper.identity();
+ switch (this.target_ray_mode_) {
+ case 'gaze':
+ case 'screen':
+ // For gaze and screen space, we won't have a mojo_from_input; however
+ // the "input" position is just the viewer, so use mojo_from_viewer.
+ mojo_from_input = this.pairedDevice_._getMojoFromViewer();
+ break;
+ case 'tracked-pointer':
+ // If we have a tracked grip position (e.g. mojo_from_input), then use
+ // that. If we don't, then we'll just set the pointer offset directly,
+ // using identity as set above.
+ if (this.mojo_from_input_) {
+ mojo_from_input = this.mojo_from_input_.matrix;
+ }
+ break;
+ default:
+ throw new Error('Unhandled target ray mode ' + this.target_ray_mode_);
+ }
+
+ // To convert mojo_from_pointer to input_from_pointer, we need:
+ // input_from_pointer = input_from_mojo * mojo_from_pointer
+ // Since we store mojo_from_input, we need to invert it here before
+ // multiplying.
+ let input_from_mojo = XRMathHelper.inverse(mojo_from_input);
+ input_desc.inputFromPointer = {};
+ input_desc.inputFromPointer.matrix =
+ XRMathHelper.mul4x4(input_from_mojo, this.mojo_from_pointer_.matrix);
+
+ input_desc.profiles = this.profiles_;
+
+ input_state.description = input_desc;
+
+ this.desc_dirty_ = false;
+ }
+
+ // Pointer data for DOM Overlay, set by setOverlayPointerPosition()
+ if (this.overlay_pointer_position_) {
+ input_state.overlayPointerPosition = this.overlay_pointer_position_;
+ this.overlay_pointer_position_ = null;
+ }
+
+ return input_state;
+ }
+
+ _getEmptyGamepad() {
+ // Mojo complains if some of the properties on Gamepad are null, so set
+ // everything to reasonable defaults that tests can override.
+ const gamepad = {
+ connected: true,
+ id: [],
+ timestamp: 0n,
+ axes: [],
+ buttons: [],
+ touchEvents: [],
+ mapping: GamepadMapping.GamepadMappingStandard,
+ displayId: 0,
+ };
+
+ switch (this.handedness_) {
+ case 'left':
+ gamepad.hand = GamepadHand.GamepadHandLeft;
+ break;
+ case 'right':
+ gamepad.hand = GamepadHand.GamepadHandRight;
+ break;
+ default:
+ gamepad.hand = GamepadHand.GamepadHandNone;
+ break;
+ }
+
+ return gamepad;
+ }
+
+ _addGamepadButton(buttonState) {
+ if (buttonState == null) {
+ return;
+ }
+
+ const buttonIndex = this._getButtonIndex(buttonState.buttonType);
+ const axesStartIndex = this._getAxesStartIndex(buttonState.buttonType);
+
+ if (buttonIndex == -1) {
+ throw new Error("Unknown Button Type!");
+ }
+
+ this.gamepad_.buttons[buttonIndex] = {
+ pressed: buttonState.pressed,
+ touched: buttonState.touched,
+ value: buttonState.pressedValue
+ };
+
+ // Add x/y value if supported.
+ if (axesStartIndex != -1) {
+ this.gamepad_.axes[axesStartIndex] = (buttonState.xValue == null ? 0.0 : buttonSate.xValue);
+ this.gamepad_.axes[axesStartIndex + 1] = (buttonState.yValue == null ? 0.0 : buttonSate.yValue);
+ }
+ }
+
+ // General Helper methods
+ _getButtonIndex(buttonType) {
+ switch (buttonType) {
+ case 'grip':
+ return 1;
+ case 'touchpad':
+ return 2;
+ case 'thumbstick':
+ return 3;
+ case 'optional-button':
+ return 4;
+ case 'optional-thumbstick':
+ return 5;
+ default:
+ return -1;
+ }
+ }
+
+ _getAxesStartIndex(buttonType) {
+ switch (buttonType) {
+ case 'touchpad':
+ return 0;
+ case 'thumbstick':
+ return 2;
+ case 'optional-thumbstick':
+ return 4;
+ default:
+ return -1;
+ }
+ }
+
+ _getMojoFromInputSource(mojo_from_viewer) {
+ return this.mojo_from_pointer_.matrix;
+ }
+}
+
+// Mojo helper classes
+class FakeXRHitTestSourceController {
+ constructor(id) {
+ this.id_ = id;
+ this.deleted_ = false;
+ }
+
+ get deleted() {
+ return this.deleted_;
+ }
+
+ // Internal setter:
+ set deleted(value) {
+ this.deleted_ = value;
+ }
+}
+
+class MockXRPresentationProvider {
+ constructor() {
+ this.receiver_ = null;
+ this.submit_frame_count_ = 0;
+ this.missing_frame_count_ = 0;
+ }
+
+ _bindProvider() {
+ const provider = new vrMojom.XRPresentationProviderRemote();
+
+ if (this.receiver_) {
+ this.receiver_.$.close();
+ }
+ this.receiver_ = new vrMojom.XRPresentationProviderReceiver(this);
+ this.receiver_.$.bindHandle(provider.$.bindNewPipeAndPassReceiver().handle);
+ return provider;
+ }
+
+ _getClientReceiver() {
+ this.submitFrameClient_ = new vrMojom.XRPresentationClientRemote();
+ return this.submitFrameClient_.$.bindNewPipeAndPassReceiver();
+ }
+
+ // XRPresentationProvider mojo implementation
+ updateLayerBounds(frameId, leftBounds, rightBounds, sourceSize) {}
+
+ submitFrameMissing(frameId, mailboxHolder, timeWaited) {
+ this.missing_frame_count_++;
+ }
+
+ submitFrame(frameId, mailboxHolder, timeWaited) {
+ this.submit_frame_count_++;
+
+ // Trigger the submit completion callbacks here. WARNING: The
+ // Javascript-based mojo mocks are *not* re-entrant. It's OK to
+ // wait for these notifications on the next frame, but waiting
+ // within the current frame would never finish since the incoming
+ // calls would be queued until the current execution context finishes.
+ this.submitFrameClient_.onSubmitFrameTransferred(true);
+ this.submitFrameClient_.onSubmitFrameRendered();
+ }
+
+ submitFrameWithTextureHandle(frameId, texture, syncToken) {}
+
+ submitFrameDrawnIntoTexture(frameId, syncToken, timeWaited) {}
+
+ // Utility methods
+ _close() {
+ if (this.receiver_) {
+ this.receiver_.$.close();
+ }
+ }
+}
+
+// Export these into the global object as a side effect of importing this
+// module.
+self.ChromeXRTest = ChromeXRTest;
+self.MockRuntime = MockRuntime;
+self.MockVRService = MockVRService;
+self.SubscribeToHitTestResult = vrMojom.SubscribeToHitTestResult;
+
+navigator.xr.test = new ChromeXRTest();
diff --git a/test/fixtures/wpt/resources/chromium/webxr-test.js.headers b/test/fixtures/wpt/resources/chromium/webxr-test.js.headers
new file mode 100644
index 00000000000..6805c323df5
--- /dev/null
+++ b/test/fixtures/wpt/resources/chromium/webxr-test.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/wpt/resources/test/README.md b/test/fixtures/wpt/resources/test/README.md
new file mode 100644
index 00000000000..edc03ef214d
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/README.md
@@ -0,0 +1,83 @@
+# `testharness.js` test suite
+
+The test suite for the `testharness.js` testing framework.
+
+## Executing Tests
+
+Install the following dependencies:
+
+- [Python 2.7.9+](https://www.python.org/)
+- [the tox Python package](https://tox.readthedocs.io/en/latest/)
+- [the Mozilla Firefox web browser](https://mozilla.org/firefox)
+- [the GeckoDriver server](https://github.com/mozilla/geckodriver)
+
+Make sure `geckodriver` can be found in your `PATH`.
+
+Currently, the tests should be run with the latest *Firefox Nightly*. In order to
+specify the path to Firefox Nightly, use the following command-line option:
+
+ tox -- --binary=/path/to/FirefoxNightly
+
+### Automated Script
+
+Alternatively, you may run `tools/ci/ci_resources_unittest.sh`, which only depends on
+Python 2. The script will install other dependencies automatically and start `tox` with
+the correct arguments.
+
+## Authoring Tests
+
+Test cases are expressed as `.html` files located within the `tests/unit/` and
+`tests/functional/` sub-directories. Each test should include the
+`testharness.js` library with the following markup:
+
+
+
+
+This should be followed by one or more `
+
+### Unit tests
+
+The "unit test" type allows for concisely testing the expected behavior of
+assertion methods. These tests may define any number of sub-tests; the
+acceptance criteria is simply that all tests executed pass.
+
+### Functional tests
+
+Thoroughly testing the behavior of the harness itself requires ensuring a
+number of considerations which cannot be verified with the "unit testing"
+strategy. These include:
+
+- Ensuring that some tests are not run
+- Ensuring conditions that cause test failures
+- Ensuring conditions that cause harness errors
+
+Functional tests allow for these details to be verified. Every functional test
+must include a summary of the expected results as a JSON string within a
+`
diff --git a/test/fixtures/wpt/resources/test/conftest.py b/test/fixtures/wpt/resources/test/conftest.py
new file mode 100644
index 00000000000..723087d3184
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/conftest.py
@@ -0,0 +1,266 @@
+import copy
+import json
+import os
+import ssl
+import sys
+import subprocess
+import urllib
+
+import html5lib
+import py
+import pytest
+
+from wptserver import WPTServer
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+WPT_ROOT = os.path.normpath(os.path.join(HERE, '..', '..'))
+HARNESS = os.path.join(HERE, 'harness.html')
+TEST_TYPES = ('functional', 'unit')
+
+sys.path.insert(0, os.path.normpath(os.path.join(WPT_ROOT, "tools")))
+import localpaths
+
+sys.path.insert(0, os.path.normpath(os.path.join(WPT_ROOT, "tools", "webdriver")))
+import webdriver
+
+
+def pytest_addoption(parser):
+ parser.addoption("--binary", action="store", default=None, help="path to browser binary")
+ parser.addoption("--headless", action="store_true", default=False, help="run browser in headless mode")
+
+
+def pytest_collect_file(file_path, path, parent):
+ if file_path.suffix.lower() != '.html':
+ return
+
+ # Tests are organized in directories by type
+ test_type = os.path.relpath(str(file_path), HERE)
+ if os.path.sep not in test_type or ".." in test_type:
+ # HTML files in this directory are not tests
+ return
+ test_type = test_type.split(os.path.sep)[1]
+
+ return HTMLFile.from_parent(parent, path=file_path, test_type=test_type)
+
+
+def pytest_configure(config):
+ config.proc = subprocess.Popen(["geckodriver"])
+ config.add_cleanup(config.proc.kill)
+
+ capabilities = {"alwaysMatch": {"acceptInsecureCerts": True, "moz:firefoxOptions": {}}}
+ if config.getoption("--binary"):
+ capabilities["alwaysMatch"]["moz:firefoxOptions"]["binary"] = config.getoption("--binary")
+ if config.getoption("--headless"):
+ capabilities["alwaysMatch"]["moz:firefoxOptions"]["args"] = ["--headless"]
+
+ config.driver = webdriver.Session("localhost", 4444,
+ capabilities=capabilities)
+ config.add_cleanup(config.driver.end)
+
+ # Although the name of the `_create_unverified_context` method suggests
+ # that it is not intended for external consumption, the standard library's
+ # documentation explicitly endorses its use:
+ #
+ # > To revert to the previous, unverified, behavior
+ # > ssl._create_unverified_context() can be passed to the context
+ # > parameter.
+ #
+ # https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection
+ config.ssl_context = ssl._create_unverified_context()
+
+ config.server = WPTServer(WPT_ROOT)
+ config.server.start(config.ssl_context)
+ config.add_cleanup(config.server.stop)
+
+
+def resolve_uri(context, uri):
+ if uri.startswith('/'):
+ base = WPT_ROOT
+ path = uri[1:]
+ else:
+ base = os.path.dirname(context)
+ path = uri
+
+ return os.path.exists(os.path.join(base, path))
+
+
+def _summarize(actual):
+ def _scrub_stack(test_obj):
+ copy = dict(test_obj)
+ del copy['stack']
+ return copy
+
+ def _expand_status(status_obj):
+ for key, value in [item for item in status_obj.items()]:
+ # In "status" and "test" objects, the "status" value enum
+ # definitions are interspersed with properties for unrelated
+ # metadata. The following condition is a best-effort attempt to
+ # ignore non-enum properties.
+ if key != key.upper() or not isinstance(value, int):
+ continue
+
+ del status_obj[key]
+
+ if status_obj['status'] == value:
+ status_obj[u'status_string'] = key
+
+ del status_obj['status']
+
+ return status_obj
+
+ def _summarize_test(test_obj):
+ del test_obj['index']
+
+ assert 'phase' in test_obj
+ assert 'phases' in test_obj
+ assert 'COMPLETE' in test_obj['phases']
+ assert test_obj['phase'] == test_obj['phases']['COMPLETE']
+ del test_obj['phases']
+ del test_obj['phase']
+
+ return _expand_status(_scrub_stack(test_obj))
+
+ def _summarize_status(status_obj):
+ return _expand_status(_scrub_stack(status_obj))
+
+
+ summarized = {}
+
+ summarized[u'summarized_status'] = _summarize_status(actual['status'])
+ summarized[u'summarized_tests'] = [
+ _summarize_test(test) for test in actual['tests']]
+ summarized[u'summarized_tests'].sort(key=lambda test_obj: test_obj.get('name'))
+ summarized[u'summarized_asserts'] = [
+ {"assert_name": assert_item["assert_name"],
+ "test": assert_item["test"]["name"] if assert_item["test"] else None,
+ "args": assert_item["args"],
+ "status": assert_item["status"]} for assert_item in actual["asserts"]]
+ summarized[u'type'] = actual['type']
+
+ return summarized
+
+
+class HTMLFile(pytest.File):
+ def __init__(self, test_type=None, **kwargs):
+ super().__init__(**kwargs)
+ self.test_type = test_type
+
+ def collect(self):
+ url = self.session.config.server.url(self.path)
+ # Some tests are reliant on the WPT servers substitution functionality,
+ # so tests must be retrieved from the server rather than read from the
+ # file system directly.
+ handle = urllib.request.urlopen(url,
+ context=self.parent.session.config.ssl_context)
+ try:
+ markup = handle.read()
+ finally:
+ handle.close()
+
+ if self.test_type not in TEST_TYPES:
+ raise ValueError('Unrecognized test type: "%s"' % self.test_type)
+
+ parsed = html5lib.parse(markup, namespaceHTMLElements=False)
+ name = None
+ expected = None
+
+ for element in parsed.iter():
+ if not name and element.tag == 'title':
+ name = element.text
+ continue
+ if element.tag == 'script':
+ if element.attrib.get('id') == 'expected':
+ try:
+ expected = json.loads(element.text)
+ except ValueError:
+ print("Failed parsing JSON in %s" % filename)
+ raise
+
+ if not name:
+ raise ValueError('No name found in %s add a element' % filename)
+ elif self.test_type == 'functional':
+ if not expected:
+ raise ValueError('Functional tests must specify expected report data')
+ elif self.test_type == 'unit' and expected:
+ raise ValueError('Unit tests must not specify expected report data')
+
+ yield HTMLItem.from_parent(self, name=name, url=url, expected=expected)
+
+
+class HTMLItem(pytest.Item):
+ def __init__(self, name, parent=None, config=None, session=None, nodeid=None, test_type=None, url=None, expected=None, **kwargs):
+ super().__init__(name, parent, config, session, nodeid, **kwargs)
+
+ self.test_type = self.parent.test_type
+ self.url = url
+ self.expected = expected
+
+ def reportinfo(self):
+ return self.fspath, None, self.url
+
+ def runtest(self):
+ if self.test_type == 'unit':
+ self._run_unit_test()
+ elif self.test_type == 'functional':
+ self._run_functional_test()
+ else:
+ raise NotImplementedError
+
+ def _run_unit_test(self):
+ driver = self.session.config.driver
+ server = self.session.config.server
+
+ driver.url = server.url(HARNESS)
+
+ actual = driver.execute_async_script(
+ 'runTest("%s", "foo", arguments[0])' % self.url
+ )
+
+ summarized = _summarize(copy.deepcopy(actual))
+
+ print(json.dumps(summarized, indent=2))
+
+ assert summarized[u'summarized_status'][u'status_string'] == u'OK', summarized[u'summarized_status'][u'message']
+ for test in summarized[u'summarized_tests']:
+ msg = "%s\n%s" % (test[u'name'], test[u'message'])
+ assert test[u'status_string'] == u'PASS', msg
+
+ def _run_functional_test(self):
+ driver = self.session.config.driver
+ server = self.session.config.server
+
+ driver.url = server.url(HARNESS)
+
+ test_url = self.url
+ actual = driver.execute_async_script('runTest("%s", "foo", arguments[0])' % test_url)
+
+ print(json.dumps(actual, indent=2))
+
+ summarized = _summarize(copy.deepcopy(actual))
+
+ print(json.dumps(summarized, indent=2))
+
+ # Test object ordering is not guaranteed. This weak assertion verifies
+ # that the indices are unique and sequential
+ indices = [test_obj.get('index') for test_obj in actual['tests']]
+ self._assert_sequence(indices)
+
+ self.expected[u'summarized_tests'].sort(key=lambda test_obj: test_obj.get('name'))
+
+ # Make asserts opt-in for now
+ if "summarized_asserts" not in self.expected:
+ del summarized["summarized_asserts"]
+ else:
+ # We can't be sure of the order of asserts even within the same test
+ # although we could also check for the failing assert being the final
+ # one
+ for obj in [summarized, self.expected]:
+ obj["summarized_asserts"].sort(
+ key=lambda x: (x["test"] or "", x["status"], x["assert_name"], tuple(x["args"])))
+
+ assert summarized == self.expected
+
+ @staticmethod
+ def _assert_sequence(nums):
+ if nums and len(nums) > 0:
+ assert nums == list(range(1, nums[-1] + 1))
diff --git a/test/fixtures/wpt/resources/test/harness.html b/test/fixtures/wpt/resources/test/harness.html
new file mode 100644
index 00000000000..5ee0f285e83
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/harness.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/idl-helper.js b/test/fixtures/wpt/resources/test/idl-helper.js
new file mode 100644
index 00000000000..2b73527ff2b
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/idl-helper.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var typedefFrom = interfaceFrom;
+var dictionaryFrom = interfaceFrom;
+function interfaceFrom(i) {
+ var idl = new IdlArray();
+ idl.add_idls(i);
+ for (var prop in idl.members) {
+ return idl.members[prop];
+ }
+}
+
+function memberFrom(m) {
+ var idl = new IdlArray();
+ idl.add_idls('interface A { ' + m + '; };');
+ return idl.members["A"].members[0];
+}
+
+function typeFrom(type) {
+ var ast = WebIDL2.parse('interface Foo { ' + type + ' a(); };');
+ ast = ast[0]; // get the first fragment
+ ast = ast.members[0]; // get the first member
+ return ast.idlType; // get the type of the first field
+}
diff --git a/test/fixtures/wpt/resources/test/nested-testharness.js b/test/fixtures/wpt/resources/test/nested-testharness.js
new file mode 100644
index 00000000000..d97c1568c75
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/nested-testharness.js
@@ -0,0 +1,80 @@
+'use strict';
+
+/**
+ * Execute testharness.js and one or more scripts in an iframe. Report the
+ * results of the execution.
+ *
+ * @param {...function|...string} bodies - a function body. If specified as a
+ * function object, it will be
+ * serialized to a string using the
+ * built-in
+ * `Function.prototype.toString` prior
+ * to inclusion in the generated
+ * iframe.
+ *
+ * @returns {Promise} eventual value describing the result of the test
+ * execution; the summary object has two properties:
+ * `harness` (a string describing the harness status) and
+ * `tests` (an object whose "own" property names are the
+ * titles of the defined sub-tests and whose associated
+ * values are the subtest statuses).
+ */
+function makeTest(...bodies) {
+ const closeScript = '<' + '/script>';
+ let src = `
+
+
+
+Document title
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup.html
new file mode 100644
index 00000000000..468319fdbea
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup.html
@@ -0,0 +1,91 @@
+
+
+
+Test#add_cleanup
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async.html
new file mode 100644
index 00000000000..07ade4b93b6
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async.html
@@ -0,0 +1,85 @@
+
+
+
+Test#add_cleanup with Promise-returning functions
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_bad_return.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_bad_return.html
new file mode 100644
index 00000000000..867bde2c399
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_bad_return.html
@@ -0,0 +1,50 @@
+
+
+
+Test#add_cleanup with non-thenable-returning function
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_rejection.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_rejection.html
new file mode 100644
index 00000000000..e51465e7eb0
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_rejection.html
@@ -0,0 +1,94 @@
+
+
+
+Test#add_cleanup with Promise-returning functions (rejection handling)
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_rejection_after_load.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_rejection_after_load.html
new file mode 100644
index 00000000000..f9b28461000
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_rejection_after_load.html
@@ -0,0 +1,52 @@
+
+
+
+Test#add_cleanup with Promise-returning functions (rejection handling following "load" event)
+
+
+
Promise Tests
+
This test demonstrates the use of promise_test. Assumes ECMAScript 6
+Promise support. Some failures are expected.
Verify the series of sub-tests that are executed for "partial" namespace objects.
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/iframe-callback.html b/test/fixtures/wpt/resources/test/tests/functional/iframe-callback.html
new file mode 100644
index 00000000000..f49d0aa6b80
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/iframe-callback.html
@@ -0,0 +1,116 @@
+
+
+
+Example with iframe that notifies containing document via callbacks
+
+
+
+
+
Callbacks From Tests Running In An IFRAME
+
A test is run inside an iframe with a same origin document. The
+containing document should receive callbacks as the tests progress inside the
+iframe. A single passing test is expected in the summary below.
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/iframe-consolidate-errors.html b/test/fixtures/wpt/resources/test/tests/functional/iframe-consolidate-errors.html
new file mode 100644
index 00000000000..ef9b8702eca
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/iframe-consolidate-errors.html
@@ -0,0 +1,50 @@
+
+
+
+Example with iframe that consolidates errors via fetch_tests_from_window
+
+
+
+
+
+
Fetching Tests From a Child Context
+
This test demonstrates the use of fetch_tests_from_window to pull
+tests from an iframe into the primary document.
+
The test suite is expected to fail due to an unhandled exception in the
+child context.
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/iframe-consolidate-tests.html b/test/fixtures/wpt/resources/test/tests/functional/iframe-consolidate-tests.html
new file mode 100644
index 00000000000..246dddee115
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/iframe-consolidate-tests.html
@@ -0,0 +1,85 @@
+
+
+
+Example with iframe that consolidates tests via fetch_tests_from_window
+
+
+
+
+
+
Fetching Tests From a Child Context
+
This test demonstrates the use of fetch_tests_from_window to pull
+tests from an iframe into the primary document.
+
The test suite will not complete until tests in the child context have finished
+executing
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/iframe-msg.html b/test/fixtures/wpt/resources/test/tests/functional/iframe-msg.html
new file mode 100644
index 00000000000..283a5d98cc3
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/iframe-msg.html
@@ -0,0 +1,84 @@
+
+
+
+Example with iframe that notifies containing document via cross document messaging
+
+
+
+
+
Notifications From Tests Running In An IFRAME
+
A test is run inside an iframe with a same origin document. The
+containing document should receive messages via postMessage/
+onmessage as the tests progress inside the iframe. A single
+passing test is expected in the summary below.
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/log-insertion.html b/test/fixtures/wpt/resources/test/tests/functional/log-insertion.html
new file mode 100644
index 00000000000..9a63c3dbded
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/log-insertion.html
@@ -0,0 +1,46 @@
+
+Log insertion
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/no-title.html b/test/fixtures/wpt/resources/test/tests/functional/no-title.html
new file mode 100644
index 00000000000..a337e4e5f57
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/no-title.html
@@ -0,0 +1,146 @@
+
+
+
+Tests with no title
+
+
+
+
+
This test demonstrates the use of promise_test. Assumes ECMAScript 6
+Promise support. Some failures are expected.
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/queue.html b/test/fixtures/wpt/resources/test/tests/functional/queue.html
new file mode 100644
index 00000000000..0c721286ec2
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/queue.html
@@ -0,0 +1,130 @@
+
+
+
+Test queuing synchronous tests
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/setup-function-worker.js b/test/fixtures/wpt/resources/test/tests/functional/setup-function-worker.js
new file mode 100644
index 00000000000..82c1456aa64
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/setup-function-worker.js
@@ -0,0 +1,14 @@
+importScripts("/resources/testharness.js");
+
+// Regression test for https://github.com/web-platform-tests/wpt/issues/27299,
+// where we broke the ability for a setup function in a worker to contain an
+// assertion (even a passing one).
+setup(function() {
+ assert_true(true, "True is true");
+});
+
+// We must define at least one test for the harness, though it is not what we
+// are testing here.
+test(function() {
+ assert_false(false, "False is false");
+}, 'Worker test');
diff --git a/test/fixtures/wpt/resources/test/tests/functional/setup-worker-service.html b/test/fixtures/wpt/resources/test/tests/functional/setup-worker-service.html
new file mode 100644
index 00000000000..9f24adac2d0
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/setup-worker-service.html
@@ -0,0 +1,86 @@
+
+
+
+Setup function in a service worker
+
+
+
+
+
Setup function in a service worker
+
This test assumes that the browser supports ServiceWorkers.
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/single-page-test-fail.html b/test/fixtures/wpt/resources/test/tests/functional/single-page-test-fail.html
new file mode 100644
index 00000000000..8bbd530c48d
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/single-page-test-fail.html
@@ -0,0 +1,28 @@
+
+Example with file_is_test (should fail)
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/single-page-test-no-assertions.html b/test/fixtures/wpt/resources/test/tests/functional/single-page-test-no-assertions.html
new file mode 100644
index 00000000000..9b39d2a02cc
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/single-page-test-no-assertions.html
@@ -0,0 +1,25 @@
+
+Example single page test with no asserts
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/single-page-test-no-body.html b/test/fixtures/wpt/resources/test/tests/functional/single-page-test-no-body.html
new file mode 100644
index 00000000000..cb018f4dae5
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/single-page-test-no-body.html
@@ -0,0 +1,26 @@
+
+Example single page test with no body
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/single-page-test-pass.html b/test/fixtures/wpt/resources/test/tests/functional/single-page-test-pass.html
new file mode 100644
index 00000000000..e143e22f3c7
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/single-page-test-pass.html
@@ -0,0 +1,28 @@
+
+Example with file_is_test
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/step_wait.html b/test/fixtures/wpt/resources/test/tests/functional/step_wait.html
new file mode 100644
index 00000000000..8235d9d48a1
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/step_wait.html
@@ -0,0 +1,79 @@
+
+Tests for step_wait
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/step_wait_func.html b/test/fixtures/wpt/resources/test/tests/functional/step_wait_func.html
new file mode 100644
index 00000000000..9fed18a3e20
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/step_wait_func.html
@@ -0,0 +1,49 @@
+
+Tests for step_wait_func and step_wait_func_done
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/task-scheduling-promise-test.html b/test/fixtures/wpt/resources/test/tests/functional/task-scheduling-promise-test.html
new file mode 100644
index 00000000000..9d8e5c11cc9
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/task-scheduling-promise-test.html
@@ -0,0 +1,241 @@
+
+testharness.js - task scheduling
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/task-scheduling-test.html b/test/fixtures/wpt/resources/test/tests/functional/task-scheduling-test.html
new file mode 100644
index 00000000000..035844448de
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/task-scheduling-test.html
@@ -0,0 +1,141 @@
+
+testharness.js - task scheduling
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/uncaught-exception-handle.html b/test/fixtures/wpt/resources/test/tests/functional/uncaught-exception-handle.html
new file mode 100644
index 00000000000..764b0c4055b
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/uncaught-exception-handle.html
@@ -0,0 +1,33 @@
+
+
+
+Harness Handling Uncaught Exception
+
+
+
+
+
Demonstrates running testharness based tests inside a dedicated web worker.
+
The test harness is expected to fail due to an uncaught exception in one worker.
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/worker-error.js b/test/fixtures/wpt/resources/test/tests/functional/worker-error.js
new file mode 100644
index 00000000000..7b89602f04b
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/worker-error.js
@@ -0,0 +1,8 @@
+importScripts("/resources/testharness.js");
+
+// The following sub-test ensures that the worker is not interpreted as a
+// single-page test. The subsequent uncaught exception should therefore be
+// interpreted as a harness error rather than a single-page test failure.
+test(function() {}, "worker test that completes successfully before exception");
+
+throw new Error("This failure is expected.");
diff --git a/test/fixtures/wpt/resources/test/tests/functional/worker-service.html b/test/fixtures/wpt/resources/test/tests/functional/worker-service.html
new file mode 100644
index 00000000000..2e07746e622
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/worker-service.html
@@ -0,0 +1,115 @@
+
+
+
+Example with a service worker
+
+
+
+
+
Service Worker Tests
+
Demonstrates running testharness based tests inside a service worker.
+
The test harness should time out due to one of the tests inside the worker timing out.
+
This test assumes that the browser supports ServiceWorkers.
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/functional/worker-shared.html b/test/fixtures/wpt/resources/test/tests/functional/worker-shared.html
new file mode 100644
index 00000000000..e26f17dec27
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/functional/worker-shared.html
@@ -0,0 +1,73 @@
+
+
+
+Example with a shared worker
+
+
+
+
+
Shared Web Worker Tests
+
Demonstrates running testharness based tests inside a shared worker.
+
The test harness should time out due to one of the tests in the worker timing out.
+
+ The tests in this file are executed in parallel to avoid exceeding the "long"
+ timeout duration.
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/exceptional-cases.html b/test/fixtures/wpt/resources/test/tests/unit/exceptional-cases.html
new file mode 100644
index 00000000000..4054d0311d2
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/exceptional-cases.html
@@ -0,0 +1,392 @@
+
+
+
+
+
+
+ Exceptional cases
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/format-value.html b/test/fixtures/wpt/resources/test/tests/unit/format-value.html
new file mode 100644
index 00000000000..13d01b81f35
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/format-value.html
@@ -0,0 +1,123 @@
+
+
+
+ format_value utility function
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/helpers.js b/test/fixtures/wpt/resources/test/tests/unit/helpers.js
new file mode 100644
index 00000000000..ca378a27c91
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/helpers.js
@@ -0,0 +1,21 @@
+// Helper for testing assertion failure cases for a testharness.js API
+//
+// The `assert_throws_*` functions cannot be used for this purpose because they
+// always fail in response to AssertionError exceptions, even when this is
+// expressed as the expected error.
+function test_failure(fn, name) {
+ test(function() {
+ try {
+ fn();
+ } catch (err) {
+ if (err instanceof AssertionError) {
+ return;
+ }
+ throw new AssertionError('Expected an AssertionError, but' + err);
+ }
+ throw new AssertionError(
+ 'Expected an AssertionError, but no error was thrown'
+ );
+ }, name);
+}
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/late-test.html b/test/fixtures/wpt/resources/test/tests/unit/late-test.html
new file mode 100644
index 00000000000..c9f8ec61fe5
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/late-test.html
@@ -0,0 +1,56 @@
+
+
+
+Test declared after harness completion
+
+
+
+
+
+
This test simulates an automated test running scenario, where the test
+results emitted by testharness.js may be interpreted after some delay. It is
+intended to demonstrate that in such cases, any additional tests which are
+executed during that delay are included in the dataset.
+
+
Although these "late tests" are likely an indication of a mistake in test
+design, they are also recorded. Previously, "late tests" were ignored.
+This test changed to assert "late tests" were no longer ignored after
+https://github.com/web-platform-tests/wpt/pull/38806 was introduced.
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/promise_setup-timeout.html b/test/fixtures/wpt/resources/test/tests/unit/promise_setup-timeout.html
new file mode 100644
index 00000000000..c4947feef42
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/promise_setup-timeout.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+ promise_setup - timeout
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/promise_setup.html b/test/fixtures/wpt/resources/test/tests/unit/promise_setup.html
new file mode 100644
index 00000000000..2abb10a476e
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/promise_setup.html
@@ -0,0 +1,333 @@
+
+
+
+
+
+
+ promise_setup
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/single_test.html b/test/fixtures/wpt/resources/test/tests/unit/single_test.html
new file mode 100644
index 00000000000..ff766e66cec
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/single_test.html
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+ single_test
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/test-return-restrictions.html b/test/fixtures/wpt/resources/test/tests/unit/test-return-restrictions.html
new file mode 100644
index 00000000000..0295c5214dc
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/test-return-restrictions.html
@@ -0,0 +1,156 @@
+
+
+
+
+
+ Restrictions on return value from `test`
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/throwing-assertions.html b/test/fixtures/wpt/resources/test/tests/unit/throwing-assertions.html
new file mode 100644
index 00000000000..a36a56043cc
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/throwing-assertions.html
@@ -0,0 +1,268 @@
+
+
+
+
+
+
+ Test the methods that make assertions about exceptions
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tests/unit/unpaired-surrogates.html b/test/fixtures/wpt/resources/test/tests/unit/unpaired-surrogates.html
new file mode 100644
index 00000000000..b2321113265
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tests/unit/unpaired-surrogates.html
@@ -0,0 +1,143 @@
+
+
+
+
+
+ Restrictions on return value from `test`
+
+
+
+
+
diff --git a/test/fixtures/wpt/resources/test/tox.ini b/test/fixtures/wpt/resources/test/tox.ini
new file mode 100644
index 00000000000..49603ef64bd
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/tox.ini
@@ -0,0 +1,13 @@
+[tox]
+envlist = py38,py39,py310,py311
+skipsdist=True
+
+[testenv]
+passenv=DISPLAY # Necessary for the spawned GeckoDriver process to connect to
+ # the appropriate display.
+
+deps =
+ -r{toxinidir}/../../tools/requirements_pytest.txt
+ -r{toxinidir}/requirements.txt
+
+commands = pytest -vv {posargs}
diff --git a/test/fixtures/wpt/resources/test/wptserver.py b/test/fixtures/wpt/resources/test/wptserver.py
new file mode 100644
index 00000000000..1f913dd96d0
--- /dev/null
+++ b/test/fixtures/wpt/resources/test/wptserver.py
@@ -0,0 +1,58 @@
+import logging
+import os
+import subprocess
+import time
+import sys
+import urllib
+
+
+class WPTServer(object):
+ def __init__(self, wpt_root):
+ self.logger = logging.getLogger()
+ self.wpt_root = wpt_root
+
+ # This is a terrible hack to get the default config of wptserve.
+ sys.path.insert(0, os.path.join(wpt_root, "tools"))
+ from serve.serve import build_config
+ with build_config(self.logger) as config:
+ self.host = config["browser_host"]
+ self.http_port = config["ports"]["http"][0]
+ self.https_port = config["ports"]["https"][0]
+
+ self.base_url = 'http://%s:%s' % (self.host, self.http_port)
+ self.https_base_url = 'https://%s:%s' % (self.host, self.https_port)
+
+ def start(self, ssl_context):
+ self.devnull = open(os.devnull, 'w')
+ wptserve_cmd = [os.path.join(self.wpt_root, 'wpt'), 'serve']
+ if sys.executable:
+ wptserve_cmd[0:0] = [sys.executable]
+ self.logger.info('Executing %s' % ' '.join(wptserve_cmd))
+ self.proc = subprocess.Popen(
+ wptserve_cmd,
+ stderr=self.devnull,
+ cwd=self.wpt_root)
+
+ for retry in range(5):
+ # Exponential backoff.
+ time.sleep(2 ** retry)
+ exit_code = self.proc.poll()
+ if exit_code != None:
+ logging.warning('Command "%s" exited with %s', ' '.join(wptserve_cmd), exit_code)
+ break
+ try:
+ urllib.request.urlopen(self.base_url, timeout=1)
+ urllib.request.urlopen(self.https_base_url, timeout=1, context=ssl_context)
+ return
+ except urllib.error.URLError:
+ pass
+
+ raise Exception('Could not start wptserve on %s' % self.base_url)
+
+ def stop(self):
+ self.proc.terminate()
+ self.proc.wait()
+ self.devnull.close()
+
+ def url(self, abs_path):
+ return self.https_base_url + '/' + os.path.relpath(abs_path, self.wpt_root)
diff --git a/test/fixtures/wpt/resources/testdriver.js b/test/fixtures/wpt/resources/testdriver.js
index 2d1a89690cc..985dbb0e403 100644
--- a/test/fixtures/wpt/resources/testdriver.js
+++ b/test/fixtures/wpt/resources/testdriver.js
@@ -49,6 +49,58 @@
* @namespace {test_driver}
*/
window.test_driver = {
+ /**
+ Represents `WebDriver BiDi `_ protocol.
+ */
+ bidi: {
+ /**
+ * `log `_ module.
+ */
+ log: {
+ /**
+ * `log.entryAdded `_ event.
+ */
+ 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}
+ */
+ 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.
+ *
+ * @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.
+ */
+ on: function (callback) {
+ return window.test_driver_internal.bidi.log.entry_added.on(callback);
+ },
+ once: function () {
+ return new Promise(resolve => {
+ const remove_handler = window.test_driver_internal.bidi.log.entry_added.on(
+ data => {
+ resolve(data);
+ remove_handler();
+ });
+ });
+ },
+ }
+ }
+ },
+
/**
* Set the context in which testharness.js is loaded
*
@@ -1066,6 +1118,29 @@
*/
clear_device_posture: function(context=null) {
return window.test_driver_internal.clear_device_posture(context);
+ },
+
+ /**
+ * Runs the `bounce tracking timer algorithm
+ * `_,
+ * which removes all hosts from the stateful bounce tracking map, without
+ * regard for the bounce tracking grace period and returns a list of the
+ * deleted hosts.
+ *
+ * Matches the `Run Bounce Tracking Mitigations
+ * https://privacycg.github.io/nav-tracking-mitigations/#run-bounce-tracking-mitigations-command`_
+ * WebDriver command.
+ *
+ * @param {WindowProxy} [context=null] - Browsing context in which to
+ * run the call, or null for the
+ * current browsing context.
+ * @returns {Promise} Fulfilled after the bounce tracking timer
+ * algorithm has finished running. Returns an array
+ * of all hosts that were in the stateful bounce
+ * tracking map before deletion occurred.
+ */
+ run_bounce_tracking_mitigations: function (context = null) {
+ return window.test_driver_internal.run_bounce_tracking_mitigations(context);
}
};
@@ -1078,6 +1153,21 @@
*/
in_automation: false,
+ bidi: {
+ log: {
+ entry_added: {
+ async subscribe() {
+ throw new Error(
+ "bidi.log.entry_added.subscribe is not implemented by testdriver-vendor.js");
+ },
+ on() {
+ throw new Error(
+ "bidi.log.entry_added.on is not implemented by testdriver-vendor.js");
+ }
+ }
+ }
+ },
+
async click(element, coords) {
if (this.in_automation) {
throw new Error("click() is not implemented by testdriver-vendor.js");
@@ -1100,6 +1190,14 @@
throw new Error("get_named_cookie() is not implemented by testdriver-vendor.js");
},
+ async get_computed_role(element) {
+ throw new Error("get_computed_role is a testdriver.js function which cannot be run in this context.");
+ },
+
+ async get_computed_name(element) {
+ throw new Error("get_computed_name is a testdriver.js function which cannot be run in this context.");
+ },
+
async send_keys(element, keys) {
if (this.in_automation) {
throw new Error("send_keys() is not implemented by testdriver-vendor.js");
@@ -1254,6 +1352,10 @@
async clear_device_posture(context=null) {
throw new Error("clear_device_posture() is not implemented by testdriver-vendor.js");
+ },
+
+ async run_bounce_tracking_mitigations(context=null) {
+ throw new Error("run_bounce_tracking_mitigations() is not implemented by testdriver-vendor.js");
}
};
})();
diff --git a/test/fixtures/wpt/resources/testharness.js b/test/fixtures/wpt/resources/testharness.js
index c5c375e1720..7fd5336bf34 100644
--- a/test/fixtures/wpt/resources/testharness.js
+++ b/test/fixtures/wpt/resources/testharness.js
@@ -857,7 +857,7 @@
promise = promiseOrConstructor;
description = descriptionOrPromise;
assert(maybeDescription === undefined,
- "Too many args pased to no-constructor version of promise_rejects_dom");
+ "Too many args passed to no-constructor version of promise_rejects_dom, or accidentally explicitly passed undefined");
}
return bring_promise_to_current_realm(promise)
.then(test.unreached_func("Should have rejected: " + description))
@@ -2174,7 +2174,7 @@
func = funcOrConstructor;
description = descriptionOrFunc;
assert(maybeDescription === undefined,
- "Too many args pased to no-constructor version of assert_throws_dom");
+ "Too many args passed to no-constructor version of assert_throws_dom, or accidentally explicitly passed undefined");
}
assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor)
}
@@ -4408,13 +4408,20 @@
{
var substitution_re = /\$\{([^ }]*)\}/g;
- function do_substitution(input) {
+ function do_substitution(input)
+ {
var components = input.split(substitution_re);
var rv = [];
- for (var i = 0; i < components.length; i += 2) {
- rv.push(components[i]);
- if (components[i + 1]) {
- rv.push(String(substitutions[components[i + 1]]));
+ if (components.length === 1) {
+ rv = components;
+ } else if (substitutions) {
+ for (var i = 0; i < components.length; i += 2) {
+ if (components[i]) {
+ rv.push(components[i]);
+ }
+ if (substitutions[components[i + 1]]) {
+ rv.push(String(substitutions[components[i + 1]]));
+ }
}
}
return rv;
diff --git a/test/fixtures/wpt/resources/webidl2/build.sh b/test/fixtures/wpt/resources/webidl2/build.sh
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/service-workers/cache-storage/cache-abort.https.any.js b/test/fixtures/wpt/service-workers/cache-storage/cache-abort.https.any.js
index 960d1bb1bff..99f29b0a08b 100644
--- a/test/fixtures/wpt/service-workers/cache-storage/cache-abort.https.any.js
+++ b/test/fixtures/wpt/service-workers/cache-storage/cache-abort.https.any.js
@@ -73,7 +73,7 @@ for (const method in methodsToTest) {
`${method} should reject`);
// infinite-slow-response.py doesn't know when to stop.
- return fetch(`../../../fetch/api/resources/stash-put.py?key=${abortKey}`);
+ return fetch(`../../../fetch/api/resources/stash-put.py?key=${abortKey}&value=close`);
}, `${method}() followed by abort after headers received should reject ` +
`with AbortError`);
}
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.css b/test/fixtures/wpt/service-workers/service-worker/resources/direct.css
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.css
rename to test/fixtures/wpt/service-workers/service-worker/resources/direct.css
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.html b/test/fixtures/wpt/service-workers/service-worker/resources/direct.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.html
rename to test/fixtures/wpt/service-workers/service-worker/resources/direct.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.js b/test/fixtures/wpt/service-workers/service-worker/resources/direct.js
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.js
rename to test/fixtures/wpt/service-workers/service-worker/resources/direct.js
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.py b/test/fixtures/wpt/service-workers/service-worker/resources/direct.py
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.py
rename to test/fixtures/wpt/service-workers/service-worker/resources/direct.py
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.txt b/test/fixtures/wpt/service-workers/service-worker/resources/direct.txt
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/direct.txt
rename to test/fixtures/wpt/service-workers/service-worker/resources/direct.txt
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/imported-sw.js b/test/fixtures/wpt/service-workers/service-worker/resources/imported-sw.js
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/imported-sw.js
rename to test/fixtures/wpt/service-workers/service-worker/resources/imported-sw.js
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/or-test/direct1.text b/test/fixtures/wpt/service-workers/service-worker/resources/or-test/direct1.text
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/or-test/direct1.text
rename to test/fixtures/wpt/service-workers/service-worker/resources/or-test/direct1.text
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/or-test/direct1.text.headers b/test/fixtures/wpt/service-workers/service-worker/resources/or-test/direct1.text.headers
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/or-test/direct1.text.headers
rename to test/fixtures/wpt/service-workers/service-worker/resources/or-test/direct1.text.headers
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/or-test/direct2.text b/test/fixtures/wpt/service-workers/service-worker/resources/or-test/direct2.text
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/or-test/direct2.text
rename to test/fixtures/wpt/service-workers/service-worker/resources/or-test/direct2.text
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/or-test/direct2.text.headers b/test/fixtures/wpt/service-workers/service-worker/resources/or-test/direct2.text.headers
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/or-test/direct2.text.headers
rename to test/fixtures/wpt/service-workers/service-worker/resources/or-test/direct2.text.headers
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/router-rules.js b/test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/router-rules.js
rename to test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js
diff --git a/test/fixtures/wpt/service-workers/service-worker/resources/shadowrealm-promise-rejection-test-worker.js b/test/fixtures/wpt/service-workers/service-worker/resources/shadowrealm-promise-rejection-test-worker.js
new file mode 100644
index 00000000000..5c9737a67aa
--- /dev/null
+++ b/test/fixtures/wpt/service-workers/service-worker/resources/shadowrealm-promise-rejection-test-worker.js
@@ -0,0 +1,11 @@
+var realm = new ShadowRealm();
+
+// Promise rejection from ShadowRealm should be handled within service
+// worker thread.
+realm.evaluate('Promise.reject("foo"); () => {}');
+
+// Nested ShadowRealms are also possible.
+realm.evaluate(`
+const innerRealm = new ShadowRealm();
+innerRealm.evaluate('Promise.reject("foo"); () => {}');
+`);
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/simple-test-for-condition-main-resource.html b/test/fixtures/wpt/service-workers/service-worker/resources/simple-test-for-condition-main-resource.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/simple-test-for-condition-main-resource.html
rename to test/fixtures/wpt/service-workers/service-worker/resources/simple-test-for-condition-main-resource.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/simple.csv b/test/fixtures/wpt/service-workers/service-worker/resources/simple.csv
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/simple.csv
rename to test/fixtures/wpt/service-workers/service-worker/resources/simple.csv
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-helpers.sub.js b/test/fixtures/wpt/service-workers/service-worker/resources/static-router-helpers.sub.js
similarity index 86%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-helpers.sub.js
rename to test/fixtures/wpt/service-workers/service-worker/resources/static-router-helpers.sub.js
index cf34e98635f..0ab1f1fae1d 100644
--- a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-helpers.sub.js
+++ b/test/fixtures/wpt/service-workers/service-worker/resources/static-router-helpers.sub.js
@@ -31,15 +31,21 @@ const reset_info_in_worker =
await promise;
}
+// This script's directory name. It is used for specifying test files.
+const scriptDir = document.currentScript.src.match(/.*\//)[0];
+
// Register the ServiceWorker and wait until activated.
// {ruleKey} represents the key of routerRules defined in router-rules.js.
// {swScript} represents the service worker source URL.
-const registerAndActivate = async (test, ruleKey, swScript) => {
+// {swScope} represents the service worker resource scope.
+const registerAndActivate = async (test, ruleKey, swScript, swScope) => {
if (!swScript) {
- swScript = 'resources/static-router-sw.js'
+ swScript = scriptDir + 'static-router-sw.js'
+ }
+ if (!swScope) {
+ swScope = scriptDir;
}
const swURL = `${swScript}?key=${ruleKey}`;
- const swScope = 'resources/';
const reg = await service_worker_unregister_and_register(
test, swURL, swScope, { type: 'module' });
add_completion_callback(() => reg.unregister());
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-no-fetch-handler-sw.js b/test/fixtures/wpt/service-workers/service-worker/resources/static-router-no-fetch-handler-sw.js
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-no-fetch-handler-sw.js
rename to test/fixtures/wpt/service-workers/service-worker/resources/static-router-no-fetch-handler-sw.js
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-race-network-and-fetch-handler-sw.js b/test/fixtures/wpt/service-workers/service-worker/resources/static-router-race-network-and-fetch-handler-sw.js
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-race-network-and-fetch-handler-sw.js
rename to test/fixtures/wpt/service-workers/service-worker/resources/static-router-race-network-and-fetch-handler-sw.js
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js b/test/fixtures/wpt/service-workers/service-worker/resources/static-router-sw.js
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js
rename to test/fixtures/wpt/service-workers/service-worker/resources/static-router-sw.js
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-sw.sub.js b/test/fixtures/wpt/service-workers/service-worker/resources/static-router-sw.sub.js
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-sw.sub.js
rename to test/fixtures/wpt/service-workers/service-worker/resources/static-router-sw.sub.js
diff --git a/test/fixtures/wpt/service-workers/service-worker/shadowrealm-promise-rejection.https.html b/test/fixtures/wpt/service-workers/service-worker/shadowrealm-promise-rejection.https.html
new file mode 100644
index 00000000000..3fa2331b7f9
--- /dev/null
+++ b/test/fixtures/wpt/service-workers/service-worker/shadowrealm-promise-rejection.https.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-fetch-event.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-fetch-event.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-fetch-event.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-fetch-event.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-invalid-rules.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-invalid-rules.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-main-resource.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-main-resource.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-multiple-router-registrations.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-multiple-router-registrations.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-multiple-router-registrations.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-multiple-router-registrations.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-mutiple-conditions.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-mutiple-conditions.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-mutiple-conditions.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-mutiple-conditions.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-no-fetch-handler.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-no-fetch-handler.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-no-fetch-handler.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-no-fetch-handler.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-race-network-and-fetch-handler.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-race-network-and-fetch-handler.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-race-network-and-fetch-handler.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-race-network-and-fetch-handler.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-request-destination.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-request-destination.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-request-destination.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-request-destination.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-request-method.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-request-method.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-request-method.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-request-method.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-subresource.https.html
similarity index 100%
rename from test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html
rename to test/fixtures/wpt/service-workers/service-worker/static-router-subresource.https.html
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/README.md b/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/README.md
index 5429b61d40e..33e508aba2c 100644
--- a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/README.md
+++ b/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/README.md
@@ -1,4 +1,4 @@
-A test suite for the ServiceWorker Static Routing API.
+A test suite for the ServiceWorker Static Routing API Resource Timing.
-WICG proposal: https://github.com/WICG/proposals/issues/102
-Specification PR: https://github.com/w3c/ServiceWorker/pull/1686
+Explainer: https://github.com/WICG/service-worker-static-routing-api/blob/main/resource-timing-api.md
+Resource-Timing Proposal: https://github.com/w3c/resource-timing/issues/389
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/simple.html b/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/simple.html
deleted file mode 100644
index 0c3e3e78707..00000000000
--- a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/simple.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-Simple
-Here's a simple html file.
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/test-helpers.sub.js b/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/test-helpers.sub.js
deleted file mode 100644
index 64a7f7d24fd..00000000000
--- a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/test-helpers.sub.js
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copied from
-// service-workers/service-worker/resources/testharness-helpers.js to be used under tentative.
-
-// Adapter for testharness.js-style tests with Service Workers
-
-/**
- * @param options an object that represents RegistrationOptions except for scope.
- * @param options.type a WorkerType.
- * @param options.updateViaCache a ServiceWorkerUpdateViaCache.
- * @see https://w3c.github.io/ServiceWorker/#dictdef-registrationoptions
- */
-function service_worker_unregister_and_register(test, url, scope, options) {
- if (!scope || scope.length == 0)
- return Promise.reject(new Error('tests must define a scope'));
-
- if (options && options.scope)
- return Promise.reject(new Error('scope must not be passed in options'));
-
- options = Object.assign({ scope: scope }, options);
- return service_worker_unregister(test, scope)
- .then(function() {
- return navigator.serviceWorker.register(url, options);
- })
- .catch(unreached_rejection(test,
- 'unregister and register should not fail'));
-}
-
-// This unregisters the registration that precisely matches scope. Use this
-// when unregistering by scope. If no registration is found, it just resolves.
-function service_worker_unregister(test, scope) {
- var absoluteScope = (new URL(scope, window.location).href);
- return navigator.serviceWorker.getRegistration(scope)
- .then(function(registration) {
- if (registration && registration.scope === absoluteScope)
- return registration.unregister();
- })
- .catch(unreached_rejection(test, 'unregister should not fail'));
-}
-
-function service_worker_unregister_and_done(test, scope) {
- return service_worker_unregister(test, scope)
- .then(test.done.bind(test));
-}
-
-function unreached_fulfillment(test, prefix) {
- return test.step_func(function(result) {
- var error_prefix = prefix || 'unexpected fulfillment';
- assert_unreached(error_prefix + ': ' + result);
- });
-}
-
-// Rejection-specific helper that provides more details
-function unreached_rejection(test, prefix) {
- return test.step_func(function(error) {
- var reason = error.message || error.name || error;
- var error_prefix = prefix || 'unexpected rejection';
- assert_unreached(error_prefix + ': ' + reason);
- });
-}
-
-/**
- * Adds an iframe to the document and returns a promise that resolves to the
- * iframe when it finishes loading. The caller is responsible for removing the
- * iframe later if needed.
- *
- * @param {string} url
- * @returns {HTMLIFrameElement}
- */
-function with_iframe(url) {
- return new Promise(function(resolve) {
- var frame = document.createElement('iframe');
- frame.className = 'test-iframe';
- frame.src = url;
- frame.onload = function() { resolve(frame); };
- document.body.appendChild(frame);
- });
-}
-
-function normalizeURL(url) {
- return new URL(url, self.location).toString().replace(/#.*$/, '');
-}
-
-function wait_for_update(test, registration) {
- if (!registration || registration.unregister == undefined) {
- return Promise.reject(new Error(
- 'wait_for_update must be passed a ServiceWorkerRegistration'));
- }
-
- return new Promise(test.step_func(function(resolve) {
- var handler = test.step_func(function() {
- registration.removeEventListener('updatefound', handler);
- resolve(registration.installing);
- });
- registration.addEventListener('updatefound', handler);
- }));
-}
-
-// Return true if |state_a| is more advanced than |state_b|.
-function is_state_advanced(state_a, state_b) {
- if (state_b === 'installing') {
- switch (state_a) {
- case 'installed':
- case 'activating':
- case 'activated':
- case 'redundant':
- return true;
- }
- }
-
- if (state_b === 'installed') {
- switch (state_a) {
- case 'activating':
- case 'activated':
- case 'redundant':
- return true;
- }
- }
-
- if (state_b === 'activating') {
- switch (state_a) {
- case 'activated':
- case 'redundant':
- return true;
- }
- }
-
- if (state_b === 'activated') {
- switch (state_a) {
- case 'redundant':
- return true;
- }
- }
- return false;
-}
-
-function wait_for_state(test, worker, state) {
- if (!worker || worker.state == undefined) {
- return Promise.reject(new Error(
- 'wait_for_state needs a ServiceWorker object to be passed.'));
- }
- if (worker.state === state)
- return Promise.resolve(state);
-
- if (is_state_advanced(worker.state, state)) {
- return Promise.reject(new Error(
- `Waiting for ${state} but the worker is already ${worker.state}.`));
- }
- return new Promise(test.step_func(function(resolve, reject) {
- worker.addEventListener('statechange', test.step_func(function() {
- if (worker.state === state)
- resolve(state);
-
- if (is_state_advanced(worker.state, state)) {
- reject(new Error(
- `The state of the worker becomes ${worker.state} while waiting` +
- `for ${state}.`));
- }
- }));
- }));
-}
-
-// Declare a test that runs entirely in the ServiceWorkerGlobalScope. The |url|
-// is the service worker script URL. This function:
-// - Instantiates a new test with the description specified in |description|.
-// The test will succeed if the specified service worker can be successfully
-// registered and installed.
-// - Creates a new ServiceWorker registration with a scope unique to the current
-// document URL. Note that this doesn't allow more than one
-// service_worker_test() to be run from the same document.
-// - Waits for the new worker to begin installing.
-// - Imports tests results from tests running inside the ServiceWorker.
-function service_worker_test(url, description) {
- // If the document URL is https://example.com/document and the script URL is
- // https://example.com/script/worker.js, then the scope would be
- // https://example.com/script/scope/document.
- var scope = new URL('scope' + window.location.pathname,
- new URL(url, window.location)).toString();
- promise_test(function(test) {
- return service_worker_unregister_and_register(test, url, scope)
- .then(function(registration) {
- add_completion_callback(function() {
- registration.unregister();
- });
- return wait_for_update(test, registration)
- .then(function(worker) {
- return fetch_tests_from_worker(worker);
- });
- });
- }, description);
-}
-
-function base_path() {
- return location.pathname.replace(/\/[^\/]*$/, '/');
-}
-
-function test_login(test, origin, username, password, cookie) {
- return new Promise(function(resolve, reject) {
- with_iframe(
- origin + base_path() +
- 'resources/fetch-access-control-login.html')
- .then(test.step_func(function(frame) {
- var channel = new MessageChannel();
- channel.port1.onmessage = test.step_func(function() {
- frame.remove();
- resolve();
- });
- frame.contentWindow.postMessage(
- {username: username, password: password, cookie: cookie},
- origin, [channel.port2]);
- }));
- });
-}
-
-function test_websocket(test, frame, url) {
- return new Promise(function(resolve, reject) {
- var ws = new frame.contentWindow.WebSocket(url, ['echo', 'chat']);
- var openCalled = false;
- ws.addEventListener('open', test.step_func(function(e) {
- assert_equals(ws.readyState, 1, "The WebSocket should be open");
- openCalled = true;
- ws.close();
- }), true);
-
- ws.addEventListener('close', test.step_func(function(e) {
- assert_true(openCalled, "The WebSocket should be closed after being opened");
- resolve();
- }), true);
-
- ws.addEventListener('error', reject);
- });
-}
-
-function login_https(test) {
- var host_info = get_host_info();
- return test_login(test, host_info.HTTPS_REMOTE_ORIGIN,
- 'username1s', 'password1s', 'cookie1')
- .then(function() {
- return test_login(test, host_info.HTTPS_ORIGIN,
- 'username2s', 'password2s', 'cookie2');
- });
-}
-
-function websocket(test, frame) {
- return test_websocket(test, frame, get_websocket_url());
-}
-
-function get_websocket_url() {
- return 'wss://{{host}}:{{ports[wss][0]}}/echo';
-}
-
-// The navigator.serviceWorker.register() method guarantees that the newly
-// installing worker is available as registration.installing when its promise
-// resolves. However some tests test installation using a element where
-// it is possible for the installing worker to have already become the waiting
-// or active worker. So this method is used to get the newest worker when these
-// tests need access to the ServiceWorker itself.
-function get_newest_worker(registration) {
- if (registration.installing)
- return registration.installing;
- if (registration.waiting)
- return registration.waiting;
- if (registration.active)
- return registration.active;
-}
-
-function register_using_link(script, options) {
- var scope = options.scope;
- var link = document.createElement('link');
- link.setAttribute('rel', 'serviceworker');
- link.setAttribute('href', script);
- link.setAttribute('scope', scope);
- document.getElementsByTagName('head')[0].appendChild(link);
- return new Promise(function(resolve, reject) {
- link.onload = resolve;
- link.onerror = reject;
- })
- .then(() => navigator.serviceWorker.getRegistration(scope));
-}
-
-function with_sandboxed_iframe(url, sandbox) {
- return new Promise(function(resolve) {
- var frame = document.createElement('iframe');
- frame.sandbox = sandbox;
- frame.src = url;
- frame.onload = function() { resolve(frame); };
- document.body.appendChild(frame);
- });
-}
-
-// Registers, waits for activation, then unregisters on a sample scope.
-//
-// This can be used to wait for a period of time needed to register,
-// activate, and then unregister a service worker. When checking that
-// certain behavior does *NOT* happen, this is preferable to using an
-// arbitrary delay.
-async function wait_for_activation_on_sample_scope(t, window_or_workerglobalscope) {
- const script = '/service-workers/service-worker/resources/empty-worker.js';
- const scope = 'resources/there/is/no/there/there?' + Date.now();
- let registration = await window_or_workerglobalscope.navigator.serviceWorker.register(script, { scope });
- await wait_for_state(t, registration.installing, 'activated');
- await registration.unregister();
-}
-
diff --git a/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-resource-timing.https.html b/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-resource-timing.https.html
new file mode 100644
index 00000000000..c85c5ea3b9a
--- /dev/null
+++ b/test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-resource-timing.https.html
@@ -0,0 +1,331 @@
+
+
+
+ Static Router: timing information should be shown when used.
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/storage/opaque-origin.https.window.js b/test/fixtures/wpt/storage/opaque-origin.https.window.js
index cc1d31fdf2c..b9539760db7 100644
--- a/test/fixtures/wpt/storage/opaque-origin.https.window.js
+++ b/test/fixtures/wpt/storage/opaque-origin.https.window.js
@@ -1,4 +1,7 @@
// META: title=StorageManager API and opaque origins
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=resources/helpers.js
function load_iframe(src, sandbox) {
return new Promise(resolve => {
@@ -49,6 +52,10 @@ function make_script(snippet) {
'<\/script>';
}
+promise_setup(async () => {
+ await tryDenyingPermission();
+});
+
['navigator.storage.persisted()',
'navigator.storage.estimate()',
// persist() can prompt, so make sure we test that last
diff --git a/test/fixtures/wpt/storage/resources/helpers.js b/test/fixtures/wpt/storage/resources/helpers.js
new file mode 100644
index 00000000000..2dc5ab00dd1
--- /dev/null
+++ b/test/fixtures/wpt/storage/resources/helpers.js
@@ -0,0 +1,9 @@
+// Try explicitly denying so that persist() won't wait for user prompt
+async function tryDenyingPermission() {
+ try {
+ await test_driver.set_permission({ name: "persistent-storage" }, "denied");
+ } catch {
+ // Not all implementations support this yet, but some implementations may
+ // still be able to continue without explicit permission
+ }
+}
diff --git a/test/fixtures/wpt/storage/storagemanager-persist-persisted-match.https.any.js b/test/fixtures/wpt/storage/storagemanager-persist-persisted-match.https.window.js
similarity index 74%
rename from test/fixtures/wpt/storage/storagemanager-persist-persisted-match.https.any.js
rename to test/fixtures/wpt/storage/storagemanager-persist-persisted-match.https.window.js
index edbe67fae2c..9a4e0d329fc 100644
--- a/test/fixtures/wpt/storage/storagemanager-persist-persisted-match.https.any.js
+++ b/test/fixtures/wpt/storage/storagemanager-persist-persisted-match.https.window.js
@@ -1,4 +1,11 @@
// META: title=StorageManager: result of persist() matches result of persisted()
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=resources/helpers.js
+
+promise_setup(async () => {
+ await tryDenyingPermission();
+});
promise_test(async t => {
var persistResult = await navigator.storage.persist();
diff --git a/test/fixtures/wpt/storage/storagemanager-persist.https.window.js b/test/fixtures/wpt/storage/storagemanager-persist.https.window.js
index 13e17a16e14..1bcbefd8a96 100644
--- a/test/fixtures/wpt/storage/storagemanager-persist.https.window.js
+++ b/test/fixtures/wpt/storage/storagemanager-persist.https.window.js
@@ -1,4 +1,11 @@
// META: title=StorageManager: persist()
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=resources/helpers.js
+
+promise_setup(async () => {
+ await tryDenyingPermission();
+});
promise_test(function() {
var promise = navigator.storage.persist();
diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json
deleted file mode 100644
index 16a0c77fae6..00000000000
--- a/test/fixtures/wpt/versions.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "common": {
- "commit": "8bfc72a4f700cd663e737b2425a79bac9874a409",
- "path": "common"
- },
- "eventsource": {
- "commit": "93ca7d336321a45f1bf1bbd63163b1e3ec6ae01f",
- "path": "eventsource"
- },
- "fetch": {
- "commit": "1b9332c3c8a84d34e38271d29518c204ab2cfb6e",
- "path": "fetch"
- },
- "FileAPI": {
- "commit": "5aa50dd4151b5bc1d04d5505366c6e27df30af5b",
- "path": "FileAPI"
- },
- "interfaces": {
- "commit": "40d3681ef52958c19a59d5a2af6ae259bd82ce7a",
- "path": "interfaces"
- },
- "mimesniff": {
- "commit": "0e9d465d283841979b44e109ed269dc72d34a050",
- "path": "mimesniff"
- },
- "resources": {
- "commit": "34dfef83fccdcc0faa24efd6d5c9013bbd44f095",
- "path": "resources"
- },
- "service-workers": {
- "commit": "3ebc2c5109dacb76334d9e85f5eda7a4b7afbebe",
- "path": "service-workers"
- },
- "storage": {
- "commit": "9f1cfd6824f166bd08d46c061be9e507d6bb45a7",
- "path": "storage"
- },
- "websockets": {
- "commit": "a7a594d8c03a850fd99bab1fd85bb6fe310081a2",
- "path": "websockets"
- },
- "xhr": {
- "commit": "5aa50dd4151b5bc1d04d5505366c6e27df30af5b",
- "path": "xhr"
- }
-}
diff --git a/test/fixtures/wpt/websockets/handlers/basic_auth_wsh.py b/test/fixtures/wpt/websockets/handlers/basic_auth_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/delayed-passive-close_wsh.py b/test/fixtures/wpt/websockets/handlers/delayed-passive-close_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/echo-cookie_wsh.py b/test/fixtures/wpt/websockets/handlers/echo-cookie_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/echo-query_v13_wsh.py b/test/fixtures/wpt/websockets/handlers/echo-query_v13_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/echo-query_wsh.py b/test/fixtures/wpt/websockets/handlers/echo-query_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/echo_close_data_wsh.py b/test/fixtures/wpt/websockets/handlers/echo_close_data_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/echo_exit_wsh.py b/test/fixtures/wpt/websockets/handlers/echo_exit_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/echo_raw_wsh.py b/test/fixtures/wpt/websockets/handlers/echo_raw_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/echo_wsh.py b/test/fixtures/wpt/websockets/handlers/echo_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/empty-message_wsh.py b/test/fixtures/wpt/websockets/handlers/empty-message_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/handshake_no_extensions_wsh.py b/test/fixtures/wpt/websockets/handlers/handshake_no_extensions_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/handshake_no_protocol_wsh.py b/test/fixtures/wpt/websockets/handlers/handshake_no_protocol_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/handshake_protocol_wsh.py b/test/fixtures/wpt/websockets/handlers/handshake_protocol_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/handshake_sleep_2_wsh.py b/test/fixtures/wpt/websockets/handlers/handshake_sleep_2_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/invalid_wsh.py b/test/fixtures/wpt/websockets/handlers/invalid_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/origin_wsh.py b/test/fixtures/wpt/websockets/handlers/origin_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/protocol_array_wsh.py b/test/fixtures/wpt/websockets/handlers/protocol_array_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/protocol_wsh.py b/test/fixtures/wpt/websockets/handlers/protocol_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/receive-backpressure_wsh.py b/test/fixtures/wpt/websockets/handlers/receive-backpressure_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/referrer_wsh.py b/test/fixtures/wpt/websockets/handlers/referrer_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/send-backpressure_wsh.py b/test/fixtures/wpt/websockets/handlers/send-backpressure_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/set-cookie-secure_wsh.py b/test/fixtures/wpt/websockets/handlers/set-cookie-secure_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/set-cookie_http_wsh.py b/test/fixtures/wpt/websockets/handlers/set-cookie_http_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/set-cookie_wsh.py b/test/fixtures/wpt/websockets/handlers/set-cookie_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/simple_handshake_wsh.py b/test/fixtures/wpt/websockets/handlers/simple_handshake_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/sleep_10_v13_wsh.py b/test/fixtures/wpt/websockets/handlers/sleep_10_v13_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/stash_responder_blocking_wsh.py b/test/fixtures/wpt/websockets/handlers/stash_responder_blocking_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/stash_responder_wsh.py b/test/fixtures/wpt/websockets/handlers/stash_responder_wsh.py
old mode 100644
new mode 100755
diff --git a/test/fixtures/wpt/websockets/handlers/wrong_accept_key_wsh.py b/test/fixtures/wpt/websockets/handlers/wrong_accept_key_wsh.py
old mode 100644
new mode 100755