From 00357a7385933f6a20ff4ec4c25e108b4ccaa70b Mon Sep 17 00:00:00 2001 From: Uzlopak <5059100+Uzlopak@users.noreply.github.com> Date: Wed, 21 Aug 2024 00:15:23 +0000 Subject: [PATCH] chore: update WPT --- test/fixtures/wpt/README.md | 26 - .../wpt/common/dispatcher/dispatcher.js | 6 + .../security-features/tools/generate.py | 0 .../security-features/tools/spec_validator.py | 0 .../wpt/fetch/api/basic/keepalive.any.js | 3 +- .../fetch/api/basic/request-headers.any.js | 2 +- .../fetch/api/resources/keepalive-helper.js | 23 + .../api/response/response-blob-realm.any.js | 1 + ...tionary-decompression.tentative.https.html | 6 +- ...tch-with-link-element.tentative.https.html | 17 +- ...etch-with-link-header.tentative.https.html | 10 +- .../resources/compressed-data.py | 31 +- test/fixtures/wpt/fetch/fetch-later/README.md | 2 +- .../iframe.tentative.https.window.js | 12 +- .../fetch-later/permissions-policy/README.md | 8 + ...tribute-redirect.tentative.https.window.js | 31 + ...policy-attribute.tentative.https.window.js | 36 + ...rmissions-policy.tentative.https.window.js | 45 + ...s-policy.tentative.https.window.js.headers | 1 + ...rmissions-policy.tentative.https.window.js | 38 + ...rmissions-policy.tentative.https.window.js | 34 + ...s-policy.tentative.https.window.js.headers | 1 + ...-by-permissions-policy.tentative.window.js | 8 + .../permissions-policy/resources/helper.js | 13 + .../permissions-policy-deferred-fetch.html | 14 + .../fetch/metadata/resources/post-to-owner.py | 12 +- .../wpt/fetch/metadata/tools/generate.py | 0 .../interfaces/EXT_disjoint_timer_query.idl | 2 +- test/fixtures/wpt/interfaces/FileAPI.idl | 1 + test/fixtures/wpt/interfaces/META.yml | 2 + .../interfaces/OES_vertex_array_object.idl | 2 +- test/fixtures/wpt/interfaces/WebCryptoAPI.idl | 2 +- test/fixtures/wpt/interfaces/compression.idl | 2 +- .../wpt/interfaces/compute-pressure.idl | 2 +- .../wpt/interfaces/credential-management.idl | 2 + .../wpt/interfaces/css-anchor-position.idl | 4 +- ...ss-contain-3.idl => css-conditional-5.idl} | 2 +- .../wpt/interfaces/css-conditional.idl | 2 + .../wpt/interfaces/css-font-loading.idl | 6 +- test/fixtures/wpt/interfaces/css-mixins.idl | 7 + test/fixtures/wpt/interfaces/css-nesting.idl | 2 +- .../wpt/interfaces/css-scroll-snap-2.idl | 5 + .../wpt/interfaces/css-view-transitions-2.idl | 4 - test/fixtures/wpt/interfaces/cssom-view.idl | 6 +- test/fixtures/wpt/interfaces/cssom.idl | 2 + .../wpt/interfaces/digital-identities.idl | 4 +- test/fixtures/wpt/interfaces/dom.idl | 12 +- .../wpt/interfaces/encrypted-media.idl | 142 +- test/fixtures/wpt/interfaces/fenced-frame.idl | 14 +- test/fixtures/wpt/interfaces/fetch.idl | 1 + test/fixtures/wpt/interfaces/geolocation.idl | 2 +- .../interfaces/handwriting-recognition.idl | 100 + test/fixtures/wpt/interfaces/html.idl | 34 +- .../wpt/interfaces/intersection-observer.idl | 6 + .../wpt/interfaces/invokers.tentative.idl | 14 +- .../wpt/interfaces/mediacapture-streams.idl | 15 + .../wpt/interfaces/mediacapture-transform.idl | 2 +- test/fixtures/wpt/interfaces/mediasession.idl | 6 +- .../wpt/interfaces/orientation-event.idl | 2 +- .../wpt/interfaces/payment-handler.idl | 35 - .../wpt/interfaces/payment-request.idl | 64 +- .../wpt/interfaces/permissions-policy.idl | 1 + .../fixtures/wpt/interfaces/pointerevents.idl | 2 + test/fixtures/wpt/interfaces/pointerlock.idl | 8 +- test/fixtures/wpt/interfaces/push-api.idl | 1 + .../wpt/interfaces/saa-non-cookie-storage.idl | 45 + .../wpt/interfaces/scheduling-apis.idl | 1 + .../wpt/interfaces/service-workers.idl | 2 +- .../wpt/interfaces/shared-storage.idl | 12 +- test/fixtures/wpt/interfaces/touch-events.idl | 6 +- .../fixtures/wpt/interfaces/trusted-types.idl | 3 - test/fixtures/wpt/interfaces/turtledove.idl | 37 +- test/fixtures/wpt/interfaces/webaudio.idl | 1 + test/fixtures/wpt/interfaces/webauthn.idl | 44 +- .../webcodecs-av1-codec-registration.idl | 8 - test/fixtures/wpt/interfaces/webcodecs.idl | 4 +- test/fixtures/wpt/interfaces/webgl1.idl | 10 +- test/fixtures/wpt/interfaces/webgl2.idl | 8 +- test/fixtures/wpt/interfaces/webgpu.idl | 19 +- test/fixtures/wpt/interfaces/webmidi.idl | 16 +- test/fixtures/wpt/interfaces/webnn.idl | 245 +- test/fixtures/wpt/interfaces/webrtc.idl | 14 +- test/fixtures/wpt/interfaces/webtransport.idl | 40 +- .../wpt/interfaces/webxr-depth-sensing.idl | 6 +- .../wpt/interfaces/webxr-hit-test.idl | 8 +- test/fixtures/wpt/mimesniff/META.yml | 1 + .../mimesniff/media/resources/make-vectors.sh | 0 .../wpt/mimesniff/sniffing/html.window.js | 12 + .../wpt/mimesniff/sniffing/support/atom.html | 3 + .../wpt/mimesniff/sniffing/support/rss.html | 3 + .../fixtures/wpt/resources/chromium/README.md | 7 + .../chromium/contacts_manager_mock.js | 90 + .../chromium/content-index-helpers.js | 9 + .../chromium/enable-hyperlink-auditing.js | 2 + .../wpt/resources/chromium/fake-hid.js | 297 +++ .../wpt/resources/chromium/fake-serial.js | 469 ++++ .../chromium/mock-barcodedetection.js | 136 ++ .../chromium/mock-barcodedetection.js.headers | 1 + .../chromium/mock-battery-monitor.headers | 1 + .../chromium/mock-battery-monitor.js | 61 + .../resources/chromium/mock-facedetection.js | 130 + .../chromium/mock-facedetection.js.headers | 1 + .../resources/chromium/mock-idle-detection.js | 80 + .../resources/chromium/mock-imagecapture.js | 309 +++ .../resources/chromium/mock-managed-config.js | 91 + .../chromium/mock-pressure-service.js | 131 + .../chromium/mock-pressure-service.js.headers | 1 + .../wpt/resources/chromium/mock-subapps.js | 89 + .../resources/chromium/mock-textdetection.js | 92 + .../chromium/mock-textdetection.js.headers | 1 + .../wpt/resources/chromium/nfc-mock.js | 437 ++++ .../resources/chromium/web-bluetooth-test.js | 629 +++++ .../chromium/web-bluetooth-test.js.headers | 1 + .../resources/chromium/webusb-child-test.js | 47 + .../chromium/webusb-child-test.js.headers | 1 + .../wpt/resources/chromium/webusb-test.js | 583 +++++ .../resources/chromium/webusb-test.js.headers | 1 + .../chromium/webxr-test-math-helper.js | 298 +++ .../webxr-test-math-helper.js.headers | 1 + .../wpt/resources/chromium/webxr-test.js | 2150 +++++++++++++++++ .../resources/chromium/webxr-test.js.headers | 1 + test/fixtures/wpt/resources/test/README.md | 83 + test/fixtures/wpt/resources/test/conftest.py | 266 ++ test/fixtures/wpt/resources/test/harness.html | 26 + .../fixtures/wpt/resources/test/idl-helper.js | 24 + .../wpt/resources/test/nested-testharness.js | 80 + .../wpt/resources/test/requirements.txt | 1 + .../test/tests/functional/abortsignal.html | 49 + .../test/tests/functional/add_cleanup.html | 91 + .../tests/functional/add_cleanup_async.html | 85 + .../add_cleanup_async_bad_return.html | 50 + .../add_cleanup_async_rejection.html | 94 + ...dd_cleanup_async_rejection_after_load.html | 52 + .../functional/add_cleanup_async_timeout.html | 57 + .../functional/add_cleanup_bad_return.html | 61 + .../tests/functional/add_cleanup_count.html | 39 + .../tests/functional/add_cleanup_err.html | 45 + .../functional/add_cleanup_err_multi.html | 52 + .../functional/add_cleanup_sync_queue.html | 55 + .../test/tests/functional/api-tests-1.html | 991 ++++++++ .../test/tests/functional/api-tests-2.html | 62 + .../test/tests/functional/api-tests-3.html | 34 + .../tests/functional/assert-array-equals.html | 162 ++ .../tests/functional/assert-throws-dom.html | 55 + .../test/tests/functional/force_timeout.html | 60 + .../tests/functional/generate-callback.html | 153 ++ .../test_partial_interface_of.html | 89 + .../IdlInterface/test_exposed_wildcard.html | 233 ++ .../test_immutable_prototype.html | 298 +++ .../IdlInterface/test_interface_mixin.html | 131 + .../test_partial_interface_of.html | 187 ++ .../test_primary_interface_of.html | 116 + .../IdlInterface/test_to_json_operation.html | 177 ++ .../IdlNamespace/test_attribute.html | 100 + .../IdlNamespace/test_operation.html | 242 ++ .../IdlNamespace/test_partial_namespace.html | 125 + .../tests/functional/iframe-callback.html | 116 + .../functional/iframe-consolidate-errors.html | 50 + .../functional/iframe-consolidate-tests.html | 85 + .../test/tests/functional/iframe-msg.html | 84 + .../test/tests/functional/log-insertion.html | 46 + .../test/tests/functional/no-title.html | 146 ++ .../test/tests/functional/order.html | 36 + .../test/tests/functional/promise-async.html | 172 ++ .../tests/functional/promise-with-sync.html | 79 + .../test/tests/functional/promise.html | 219 ++ .../test/tests/functional/queue.html | 130 + .../tests/functional/setup-function-worker.js | 14 + .../functional/setup-worker-service.html | 86 + .../functional/single-page-test-fail.html | 28 + .../single-page-test-no-assertions.html | 25 + .../functional/single-page-test-no-body.html | 26 + .../functional/single-page-test-pass.html | 28 + .../test/tests/functional/step_wait.html | 79 + .../test/tests/functional/step_wait_func.html | 49 + .../task-scheduling-promise-test.html | 241 ++ .../functional/task-scheduling-test.html | 141 ++ .../functional/uncaught-exception-handle.html | 33 + .../functional/uncaught-exception-ignore.html | 35 + .../worker-dedicated-uncaught-allow.html | 45 + .../worker-dedicated-uncaught-single.html | 56 + .../functional/worker-dedicated.sub.html | 88 + .../test/tests/functional/worker-error.js | 8 + .../test/tests/functional/worker-service.html | 115 + .../test/tests/functional/worker-shared.html | 73 + .../tests/functional/worker-uncaught-allow.js | 19 + .../functional/worker-uncaught-single.js | 8 + .../resources/test/tests/functional/worker.js | 34 + .../tests/unit/IdlArray/is_json_type.html | 193 ++ .../get_reverse_inheritance_stack.html | 47 + .../test_partial_dictionary.html | 39 + .../tests/unit/IdlInterface/constructors.html | 26 + .../default_to_json_operation.html | 114 + .../do_member_unscopable_asserts.html | 56 + .../IdlInterface/get_interface_object.html | 22 + .../get_interface_object_owner.html | 21 + .../IdlInterface/get_legacy_namespace.html | 20 + .../unit/IdlInterface/get_qualified_name.html | 20 + .../get_reverse_inheritance_stack.html | 49 + ...has_default_to_json_regular_operation.html | 47 + .../has_to_json_regular_operation.html | 31 + .../should_have_interface_object.html | 30 + .../test_primary_interface_of_undefined.html | 29 + .../is_to_json_regular_operation.html | 42 + .../unit/IdlInterfaceMember/toString.html | 36 + .../test/tests/unit/assert_implements.html | 43 + .../unit/assert_implements_optional.html | 43 + .../test/tests/unit/assert_object_equals.html | 152 ++ .../unit/async-test-return-restrictions.html | 135 ++ .../wpt/resources/test/tests/unit/basic.html | 48 + .../unit/exceptional-cases-timeouts.html | 120 + .../test/tests/unit/exceptional-cases.html | 392 +++ .../test/tests/unit/format-value.html | 123 + .../wpt/resources/test/tests/unit/helpers.js | 21 + .../resources/test/tests/unit/late-test.html | 56 + .../tests/unit/promise_setup-timeout.html | 28 + .../test/tests/unit/promise_setup.html | 333 +++ .../test/tests/unit/single_test.html | 94 + .../tests/unit/test-return-restrictions.html | 156 ++ .../test/tests/unit/throwing-assertions.html | 268 ++ .../test/tests/unit/unpaired-surrogates.html | 143 ++ test/fixtures/wpt/resources/test/tox.ini | 13 + test/fixtures/wpt/resources/test/wptserver.py | 58 + test/fixtures/wpt/resources/testdriver.js | 102 + test/fixtures/wpt/resources/testharness.js | 21 +- test/fixtures/wpt/resources/webidl2/build.sh | 0 .../cache-storage/cache-abort.https.any.js | 2 +- .../static-router => }/resources/direct.css | 0 .../static-router => }/resources/direct.html | 0 .../static-router => }/resources/direct.js | 0 .../static-router => }/resources/direct.py | 0 .../static-router => }/resources/direct.txt | 0 .../resources/imported-sw.js | 0 .../resources/or-test/direct1.text | 0 .../resources/or-test/direct1.text.headers | 0 .../resources/or-test/direct2.text | 0 .../resources/or-test/direct2.text.headers | 0 .../resources/router-rules.js | 0 ...adowrealm-promise-rejection-test-worker.js | 11 + ...mple-test-for-condition-main-resource.html | 0 .../static-router => }/resources/simple.csv | 0 .../resources/static-router-helpers.sub.js | 12 +- .../static-router-no-fetch-handler-sw.js | 0 ...outer-race-network-and-fetch-handler-sw.js | 0 .../resources/static-router-sw.js | 0 .../resources/static-router-sw.sub.js | 0 .../shadowrealm-promise-rejection.https.html | 21 + .../static-router-fetch-event.https.html | 0 .../static-router-invalid-rules.https.html | 0 .../static-router-main-resource.https.html | 0 ...r-multiple-router-registrations.https.html | 0 ...tatic-router-mutiple-conditions.https.html | 0 .../static-router-no-fetch-handler.https.html | 0 ...-race-network-and-fetch-handler.https.html | 0 ...atic-router-request-destination.https.html | 0 .../static-router-request-method.https.html | 0 .../static-router-subresource.https.html | 0 .../tentative/static-router/README.md | 6 +- .../static-router/resources/simple.html | 3 - .../resources/test-helpers.sub.js | 303 --- .../static-router-resource-timing.https.html | 331 +++ .../wpt/storage/opaque-origin.https.window.js | 7 + .../fixtures/wpt/storage/resources/helpers.js | 9 + ...r-persist-persisted-match.https.window.js} | 7 + .../storagemanager-persist.https.window.js | 7 + test/fixtures/wpt/versions.json | 46 - .../wpt/websockets/handlers/basic_auth_wsh.py | 0 .../handlers/delayed-passive-close_wsh.py | 0 .../websockets/handlers/echo-cookie_wsh.py | 0 .../websockets/handlers/echo-query_v13_wsh.py | 0 .../wpt/websockets/handlers/echo-query_wsh.py | 0 .../handlers/echo_close_data_wsh.py | 0 .../wpt/websockets/handlers/echo_exit_wsh.py | 0 .../wpt/websockets/handlers/echo_raw_wsh.py | 0 .../wpt/websockets/handlers/echo_wsh.py | 0 .../websockets/handlers/empty-message_wsh.py | 0 .../handlers/handshake_no_extensions_wsh.py | 0 .../handlers/handshake_no_protocol_wsh.py | 0 .../handlers/handshake_protocol_wsh.py | 0 .../handlers/handshake_sleep_2_wsh.py | 0 .../wpt/websockets/handlers/invalid_wsh.py | 0 .../wpt/websockets/handlers/origin_wsh.py | 0 .../websockets/handlers/protocol_array_wsh.py | 0 .../wpt/websockets/handlers/protocol_wsh.py | 0 .../handlers/receive-backpressure_wsh.py | 0 .../wpt/websockets/handlers/referrer_wsh.py | 0 .../handlers/send-backpressure_wsh.py | 0 .../handlers/set-cookie-secure_wsh.py | 0 .../handlers/set-cookie_http_wsh.py | 0 .../wpt/websockets/handlers/set-cookie_wsh.py | 0 .../handlers/simple_handshake_wsh.py | 0 .../websockets/handlers/sleep_10_v13_wsh.py | 0 .../handlers/stash_responder_blocking_wsh.py | 0 .../handlers/stash_responder_wsh.py | 0 .../handlers/wrong_accept_key_wsh.py | 0 295 files changed, 17510 insertions(+), 840 deletions(-) delete mode 100644 test/fixtures/wpt/README.md mode change 100644 => 100755 test/fixtures/wpt/common/security-features/tools/generate.py mode change 100644 => 100755 test/fixtures/wpt/common/security-features/tools/spec_validator.py create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/README.md create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy-attribute-redirect.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy-attribute.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy.tentative.https.window.js.headers create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/deferred-fetch-default-permissions-policy.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/deferred-fetch-disabled-by-permissions-policy.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/deferred-fetch-disabled-by-permissions-policy.tentative.https.window.js.headers create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/deferred-fetch-supported-by-permissions-policy.tentative.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/resources/helper.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/permissions-policy/resources/permissions-policy-deferred-fetch.html mode change 100644 => 100755 test/fixtures/wpt/fetch/metadata/tools/generate.py rename test/fixtures/wpt/interfaces/{css-contain-3.idl => css-conditional-5.idl} (76%) create mode 100644 test/fixtures/wpt/interfaces/css-mixins.idl create mode 100644 test/fixtures/wpt/interfaces/handwriting-recognition.idl create mode 100644 test/fixtures/wpt/interfaces/saa-non-cookie-storage.idl mode change 100644 => 100755 test/fixtures/wpt/mimesniff/media/resources/make-vectors.sh create mode 100644 test/fixtures/wpt/mimesniff/sniffing/html.window.js create mode 100644 test/fixtures/wpt/mimesniff/sniffing/support/atom.html create mode 100644 test/fixtures/wpt/mimesniff/sniffing/support/rss.html create mode 100644 test/fixtures/wpt/resources/chromium/README.md create mode 100644 test/fixtures/wpt/resources/chromium/contacts_manager_mock.js create mode 100644 test/fixtures/wpt/resources/chromium/content-index-helpers.js create mode 100644 test/fixtures/wpt/resources/chromium/enable-hyperlink-auditing.js create mode 100644 test/fixtures/wpt/resources/chromium/fake-hid.js create mode 100644 test/fixtures/wpt/resources/chromium/fake-serial.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-barcodedetection.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-barcodedetection.js.headers create mode 100644 test/fixtures/wpt/resources/chromium/mock-battery-monitor.headers create mode 100644 test/fixtures/wpt/resources/chromium/mock-battery-monitor.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-facedetection.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-facedetection.js.headers create mode 100644 test/fixtures/wpt/resources/chromium/mock-idle-detection.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-imagecapture.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-managed-config.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-pressure-service.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-pressure-service.js.headers create mode 100644 test/fixtures/wpt/resources/chromium/mock-subapps.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-textdetection.js create mode 100644 test/fixtures/wpt/resources/chromium/mock-textdetection.js.headers create mode 100644 test/fixtures/wpt/resources/chromium/nfc-mock.js create mode 100644 test/fixtures/wpt/resources/chromium/web-bluetooth-test.js create mode 100644 test/fixtures/wpt/resources/chromium/web-bluetooth-test.js.headers create mode 100644 test/fixtures/wpt/resources/chromium/webusb-child-test.js create mode 100644 test/fixtures/wpt/resources/chromium/webusb-child-test.js.headers create mode 100644 test/fixtures/wpt/resources/chromium/webusb-test.js create mode 100644 test/fixtures/wpt/resources/chromium/webusb-test.js.headers create mode 100644 test/fixtures/wpt/resources/chromium/webxr-test-math-helper.js create mode 100644 test/fixtures/wpt/resources/chromium/webxr-test-math-helper.js.headers create mode 100644 test/fixtures/wpt/resources/chromium/webxr-test.js create mode 100644 test/fixtures/wpt/resources/chromium/webxr-test.js.headers create mode 100644 test/fixtures/wpt/resources/test/README.md create mode 100644 test/fixtures/wpt/resources/test/conftest.py create mode 100644 test/fixtures/wpt/resources/test/harness.html create mode 100644 test/fixtures/wpt/resources/test/idl-helper.js create mode 100644 test/fixtures/wpt/resources/test/nested-testharness.js create mode 100644 test/fixtures/wpt/resources/test/requirements.txt create mode 100644 test/fixtures/wpt/resources/test/tests/functional/abortsignal.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_bad_return.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_rejection.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_rejection_after_load.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_timeout.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_bad_return.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_count.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_err.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_err_multi.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/add_cleanup_sync_queue.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/api-tests-1.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/api-tests-2.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/api-tests-3.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/assert-array-equals.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/assert-throws-dom.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/force_timeout.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/generate-callback.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_exposed_wildcard.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_immutable_prototype.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_interface_mixin.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_primary_interface_of.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_to_json_operation.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_attribute.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_operation.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_partial_namespace.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/iframe-callback.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/iframe-consolidate-errors.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/iframe-consolidate-tests.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/iframe-msg.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/log-insertion.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/no-title.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/order.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/promise-async.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/promise-with-sync.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/promise.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/queue.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/setup-function-worker.js create mode 100644 test/fixtures/wpt/resources/test/tests/functional/setup-worker-service.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/single-page-test-fail.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/single-page-test-no-assertions.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/single-page-test-no-body.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/single-page-test-pass.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/step_wait.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/step_wait_func.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/task-scheduling-promise-test.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/task-scheduling-test.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/uncaught-exception-handle.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/uncaught-exception-ignore.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/worker-dedicated-uncaught-allow.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/worker-dedicated-uncaught-single.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/worker-dedicated.sub.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/worker-error.js create mode 100644 test/fixtures/wpt/resources/test/tests/functional/worker-service.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/worker-shared.html create mode 100644 test/fixtures/wpt/resources/test/tests/functional/worker-uncaught-allow.js create mode 100644 test/fixtures/wpt/resources/test/tests/functional/worker-uncaught-single.js create mode 100644 test/fixtures/wpt/resources/test/tests/functional/worker.js create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlArray/is_json_type.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlDictionary/get_reverse_inheritance_stack.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlDictionary/test_partial_dictionary.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/constructors.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/default_to_json_operation.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/do_member_unscopable_asserts.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_interface_object.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_interface_object_owner.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_legacy_namespace.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_qualified_name.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_reverse_inheritance_stack.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/has_default_to_json_regular_operation.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/has_to_json_regular_operation.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/should_have_interface_object.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterface/test_primary_interface_of_undefined.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterfaceMember/is_to_json_regular_operation.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/IdlInterfaceMember/toString.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/assert_implements.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/assert_implements_optional.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/assert_object_equals.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/async-test-return-restrictions.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/basic.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/exceptional-cases-timeouts.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/exceptional-cases.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/format-value.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/helpers.js create mode 100644 test/fixtures/wpt/resources/test/tests/unit/late-test.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/promise_setup-timeout.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/promise_setup.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/single_test.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/test-return-restrictions.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/throwing-assertions.html create mode 100644 test/fixtures/wpt/resources/test/tests/unit/unpaired-surrogates.html create mode 100644 test/fixtures/wpt/resources/test/tox.ini create mode 100644 test/fixtures/wpt/resources/test/wptserver.py mode change 100644 => 100755 test/fixtures/wpt/resources/webidl2/build.sh rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/direct.css (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/direct.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/direct.js (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/direct.py (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/direct.txt (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/imported-sw.js (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/or-test/direct1.text (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/or-test/direct1.text.headers (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/or-test/direct2.text (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/or-test/direct2.text.headers (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/router-rules.js (100%) create mode 100644 test/fixtures/wpt/service-workers/service-worker/resources/shadowrealm-promise-rejection-test-worker.js rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/simple-test-for-condition-main-resource.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/simple.csv (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/static-router-helpers.sub.js (86%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/static-router-no-fetch-handler-sw.js (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/static-router-race-network-and-fetch-handler-sw.js (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/static-router-sw.js (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/resources/static-router-sw.sub.js (100%) create mode 100644 test/fixtures/wpt/service-workers/service-worker/shadowrealm-promise-rejection.https.html rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-fetch-event.https.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-invalid-rules.https.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-main-resource.https.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-multiple-router-registrations.https.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-mutiple-conditions.https.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-no-fetch-handler.https.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-race-network-and-fetch-handler.https.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-request-destination.https.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-request-method.https.html (100%) rename test/fixtures/wpt/service-workers/service-worker/{tentative/static-router => }/static-router-subresource.https.html (100%) delete mode 100644 test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/simple.html delete mode 100644 test/fixtures/wpt/service-workers/service-worker/tentative/static-router/resources/test-helpers.sub.js create mode 100644 test/fixtures/wpt/service-workers/service-worker/tentative/static-router/static-router-resource-timing.https.html create mode 100644 test/fixtures/wpt/storage/resources/helpers.js rename test/fixtures/wpt/storage/{storagemanager-persist-persisted-match.https.any.js => storagemanager-persist-persisted-match.https.window.js} (74%) delete mode 100644 test/fixtures/wpt/versions.json mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/basic_auth_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/delayed-passive-close_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/echo-cookie_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/echo-query_v13_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/echo-query_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/echo_close_data_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/echo_exit_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/echo_raw_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/echo_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/empty-message_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/handshake_no_extensions_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/handshake_no_protocol_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/handshake_protocol_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/handshake_sleep_2_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/invalid_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/origin_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/protocol_array_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/protocol_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/receive-backpressure_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/referrer_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/send-backpressure_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/set-cookie-secure_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/set-cookie_http_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/set-cookie_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/simple_handshake_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/sleep_10_v13_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/stash_responder_blocking_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/stash_responder_wsh.py mode change 100644 => 100755 test/fixtures/wpt/websockets/handlers/wrong_accept_key_wsh.py 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 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + </head> + <body> + <script> +function runTest(url, id, done) { + var child; + + function onMessage(event) { + if (!event.data || event.data.type !== 'complete') { + return; + } + + window.removeEventListener('message', onMessage); + child.close(); + done(event.data); + } + window.addEventListener('message', onMessage); + + window.child = child = window.open(url, id); +} + </script> + </body> +</html> 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 = ` +<!DOCTYPE HTML> +<html> +<head> +<title>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.

+
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_timeout.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_timeout.html new file mode 100644 index 00000000000..429536ce6ee --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_async_timeout.html @@ -0,0 +1,57 @@ + + + +Test#add_cleanup with Promise-returning functions (timeout handling) + + + + +
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_bad_return.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_bad_return.html new file mode 100644 index 00000000000..3cfb28adf25 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_bad_return.html @@ -0,0 +1,61 @@ + + + +Test#add_cleanup with value-returning function + + + + +
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_count.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_count.html new file mode 100644 index 00000000000..2c9b51c6d0e --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_count.html @@ -0,0 +1,39 @@ + + + +Test#add_cleanup reported count + + + + +
+ + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_err.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_err.html new file mode 100644 index 00000000000..60357c66ee9 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_err.html @@ -0,0 +1,45 @@ + + + +Test#add_cleanup: error + + + + +
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_err_multi.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_err_multi.html new file mode 100644 index 00000000000..80ba1b49590 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_err_multi.html @@ -0,0 +1,52 @@ + + + +Test#add_cleanup: multiple functions with one in error + + + + +
+ + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_sync_queue.html b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_sync_queue.html new file mode 100644 index 00000000000..0a61503cc80 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/add_cleanup_sync_queue.html @@ -0,0 +1,55 @@ + + + +Test#add_cleanup: queuing tests + + + + +
+ + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/api-tests-1.html b/test/fixtures/wpt/resources/test/tests/functional/api-tests-1.html new file mode 100644 index 00000000000..9de875b0f12 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/api-tests-1.html @@ -0,0 +1,991 @@ + + + +Sample HTML5 API Tests + + + +

Sample HTML5 API Tests

+
+ + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/api-tests-2.html b/test/fixtures/wpt/resources/test/tests/functional/api-tests-2.html new file mode 100644 index 00000000000..9af94f61acd --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/api-tests-2.html @@ -0,0 +1,62 @@ + + + +Sample HTML5 API Tests + + +

Sample HTML5 API Tests

+

There should be two results

+
+ + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/api-tests-3.html b/test/fixtures/wpt/resources/test/tests/functional/api-tests-3.html new file mode 100644 index 00000000000..991fc6da670 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/api-tests-3.html @@ -0,0 +1,34 @@ + + + +Sample HTML5 API Tests + + + + +

Sample HTML5 API Tests

+
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/assert-array-equals.html b/test/fixtures/wpt/resources/test/tests/functional/assert-array-equals.html new file mode 100644 index 00000000000..b6460a48688 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/assert-array-equals.html @@ -0,0 +1,162 @@ + +assert_array_equals + + +
+ + diff --git a/test/fixtures/wpt/resources/test/tests/functional/assert-throws-dom.html b/test/fixtures/wpt/resources/test/tests/functional/assert-throws-dom.html new file mode 100644 index 00000000000..4dd66b2372a --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/assert-throws-dom.html @@ -0,0 +1,55 @@ + +assert_throws_dom + + +
+ + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/force_timeout.html b/test/fixtures/wpt/resources/test/tests/functional/force_timeout.html new file mode 100644 index 00000000000..2058fdb8621 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/force_timeout.html @@ -0,0 +1,60 @@ + + + +Test#force_timeout + + +

Test#force_timeout

+
+ + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/generate-callback.html b/test/fixtures/wpt/resources/test/tests/functional/generate-callback.html new file mode 100644 index 00000000000..11d41743b3e --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/generate-callback.html @@ -0,0 +1,153 @@ + + + +Sample for using generate_tests to create a series of tests that share the same callback. + + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html new file mode 100644 index 00000000000..f635768c69f --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html @@ -0,0 +1,89 @@ + + + + + + idlharness: Partial dictionary + + + + + + + + +

Verify the series of sub-tests that are executed for "partial" dictionary objects.

+ + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_exposed_wildcard.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_exposed_wildcard.html new file mode 100644 index 00000000000..addc0eb4fc4 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_exposed_wildcard.html @@ -0,0 +1,233 @@ + + + + + idlharness: Exposed=* + + + + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_immutable_prototype.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_immutable_prototype.html new file mode 100644 index 00000000000..5fe05915b05 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_immutable_prototype.html @@ -0,0 +1,298 @@ + + + + + idlharness: Immutable prototypes + + + + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_interface_mixin.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_interface_mixin.html new file mode 100644 index 00000000000..be2844e6987 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_interface_mixin.html @@ -0,0 +1,131 @@ + + + + + + idlharness: interface mixins + + + + + + + +

Verify the series of sub-tests that are executed for "interface mixin" objects.

+ + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html new file mode 100644 index 00000000000..7dd9e676af4 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html @@ -0,0 +1,187 @@ + + + + + + idlharness: Partial interface + + + + + + + + +

Verify the series of sub-tests that are executed for "partial" interface objects.

+ + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_primary_interface_of.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_primary_interface_of.html new file mode 100644 index 00000000000..309de60bb79 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_primary_interface_of.html @@ -0,0 +1,116 @@ + + + + + idlharness: Primary interface + + + + + + +

Verify the series of sub-tests that are executed for "tested" interface +objects but skipped for "untested" interface objects.

+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_to_json_operation.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_to_json_operation.html new file mode 100644 index 00000000000..bbc502a313f --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlInterface/test_to_json_operation.html @@ -0,0 +1,177 @@ + + + + + IdlInterface.prototype.test_to_json_operation() + + + + + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_attribute.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_attribute.html new file mode 100644 index 00000000000..2c94061fc1e --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_attribute.html @@ -0,0 +1,100 @@ + + + + + idlharness: namespace attribute + + + + + + +

Verify the series of sub-tests that are executed for namespace attributes.

+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_operation.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_operation.html new file mode 100644 index 00000000000..da70c8fa31c --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_operation.html @@ -0,0 +1,242 @@ + + + + + idlharness: namespace operation + + + + + + +

Verify the series of sub-tests that are executed for namespace operations.

+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_partial_namespace.html b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_partial_namespace.html new file mode 100644 index 00000000000..eabdcd10a96 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/idlharness/IdlNamespace/test_partial_namespace.html @@ -0,0 +1,125 @@ + + + + + + idlharness: Partial namespace + + + + + + + +

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 + + + + +

Tests with no title

+
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/order.html b/test/fixtures/wpt/resources/test/tests/functional/order.html new file mode 100644 index 00000000000..686383861a0 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/order.html @@ -0,0 +1,36 @@ + + + +Ordering + + + +
+ + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/promise-async.html b/test/fixtures/wpt/resources/test/tests/functional/promise-async.html new file mode 100644 index 00000000000..fa82665cf00 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/promise-async.html @@ -0,0 +1,172 @@ + + + +Async Tests and Promises + + +

Async Tests and Promises

+

This test assumes ECMAScript 6 Promise support. Some failures are expected.

+
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/promise-with-sync.html b/test/fixtures/wpt/resources/test/tests/functional/promise-with-sync.html new file mode 100644 index 00000000000..e8e680a9c76 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/promise-with-sync.html @@ -0,0 +1,79 @@ + + + +Promise Tests and Synchronous Tests + + +

Promise Tests

+

This test demonstrates the use of promise_test alongside synchronous tests.

+
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/promise.html b/test/fixtures/wpt/resources/test/tests/functional/promise.html new file mode 100644 index 00000000000..f35feb0e218 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/promise.html @@ -0,0 +1,219 @@ + + + +Promise Tests + + +

Promise Tests

+

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 + + + + +

Harness Handling Uncaught Exception

+
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/uncaught-exception-ignore.html b/test/fixtures/wpt/resources/test/tests/functional/uncaught-exception-ignore.html new file mode 100644 index 00000000000..6bd0ddbb0d2 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/uncaught-exception-ignore.html @@ -0,0 +1,35 @@ + + + +Harness Ignoring Uncaught Exception + + + + +

Harness Ignoring Uncaught Exception

+
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/worker-dedicated-uncaught-allow.html b/test/fixtures/wpt/resources/test/tests/functional/worker-dedicated-uncaught-allow.html new file mode 100644 index 00000000000..ba28d4914f2 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/worker-dedicated-uncaught-allow.html @@ -0,0 +1,45 @@ + + + +Dedicated Worker Tests - Allowed Uncaught Exception + + + + +

Dedicated Web Worker Tests - Allowed Uncaught Exception

+

Demonstrates running testharness based tests inside a dedicated web worker. +

The test harness is expected to pass despite an uncaught exception in a worker because that worker is configured to allow uncaught exceptions.

+
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/worker-dedicated-uncaught-single.html b/test/fixtures/wpt/resources/test/tests/functional/worker-dedicated-uncaught-single.html new file mode 100644 index 00000000000..486e067114c --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/worker-dedicated-uncaught-single.html @@ -0,0 +1,56 @@ + + + +Dedicated Worker Tests - Uncaught Exception in Single-Page Test + + + + +

Dedicated Web Worker Tests - Uncaught Exception in Single-Page Test

+

Demonstrates running testharness based tests inside a dedicated web worker. +

The test harness is expected to pass despite an uncaught exception in a worker because that worker is a single-page test.

+
+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/worker-dedicated.sub.html b/test/fixtures/wpt/resources/test/tests/functional/worker-dedicated.sub.html new file mode 100644 index 00000000000..efd703c7601 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/worker-dedicated.sub.html @@ -0,0 +1,88 @@ + + + +Dedicated Worker Tests + + + + +

Dedicated Web Worker Tests

+

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. +

This test assumes that the browser supports shared web workers. +

+ + + + diff --git a/test/fixtures/wpt/resources/test/tests/functional/worker-uncaught-allow.js b/test/fixtures/wpt/resources/test/tests/functional/worker-uncaught-allow.js new file mode 100644 index 00000000000..6925d59349a --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/worker-uncaught-allow.js @@ -0,0 +1,19 @@ +importScripts("/resources/testharness.js"); + +setup({allow_uncaught_exception:true}); + +async_test(function(t) { + onerror = function() { + // Further delay the test's completion to ensure that the worker's + // `onerror` handler does not influence results in the parent context. + setTimeout(function() { + t.done(); + }, 0); + }; + + setTimeout(function() { + throw new Error("This error is expected."); + }, 0); +}, 'onerror event is triggered'); + +done(); diff --git a/test/fixtures/wpt/resources/test/tests/functional/worker-uncaught-single.js b/test/fixtures/wpt/resources/test/tests/functional/worker-uncaught-single.js new file mode 100644 index 00000000000..c04542b2f57 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/worker-uncaught-single.js @@ -0,0 +1,8 @@ +importScripts("/resources/testharness.js"); + +setup({ single_test: true }); + +// Because this script enables the `single_test` configuration option, it +// should be interpreted as a single-page test, and the uncaught exception +// should be reported as a test failure (harness status: OK). +throw new Error("This failure is expected."); diff --git a/test/fixtures/wpt/resources/test/tests/functional/worker.js b/test/fixtures/wpt/resources/test/tests/functional/worker.js new file mode 100644 index 00000000000..a923bc2d89e --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/functional/worker.js @@ -0,0 +1,34 @@ +importScripts("/resources/testharness.js"); + +test( + function(test) { + assert_true(true, "True is true"); + }, + "Worker test that completes successfully"); + +test( + function(test) { + assert_true(false, "Failing test"); + }, + "Worker test that fails ('FAIL')"); + +async_test( + function(test) { + assert_true(true, "True is true"); + }, + "Worker test that times out ('TIMEOUT')"); + +async_test("Worker test that doesn't run ('NOT RUN')"); + +async_test( + function(test) { + self.setTimeout( + function() { + test.done(); + }, + 0); + }, + "Worker async_test that completes successfully"); + +// An explicit done() is required for dedicated and shared web workers. +done(); diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlArray/is_json_type.html b/test/fixtures/wpt/resources/test/tests/unit/IdlArray/is_json_type.html new file mode 100644 index 00000000000..caea20067fa --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlArray/is_json_type.html @@ -0,0 +1,193 @@ + + + +IdlArray.prototype.is_json_type() + + +
+ + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlDictionary/get_reverse_inheritance_stack.html b/test/fixtures/wpt/resources/test/tests/unit/IdlDictionary/get_reverse_inheritance_stack.html new file mode 100644 index 00000000000..418bcdec92a --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlDictionary/get_reverse_inheritance_stack.html @@ -0,0 +1,47 @@ + + + +IdlDictionary.prototype.get_reverse_inheritance_stack() + + +
+ + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlDictionary/test_partial_dictionary.html b/test/fixtures/wpt/resources/test/tests/unit/IdlDictionary/test_partial_dictionary.html new file mode 100644 index 00000000000..d6137f6895f --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlDictionary/test_partial_dictionary.html @@ -0,0 +1,39 @@ + + + + + + idlharness: partial dictionaries + + + + + + + + +
+dictionary A {};
+partial dictionary A {
+  boolean B;
+};
+partial dictionary A {
+  boolean C;
+};
+
+ + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/constructors.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/constructors.html new file mode 100644 index 00000000000..e9ee3f8680a --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/constructors.html @@ -0,0 +1,26 @@ + +IdlInterface.prototype.constructors() +
+ + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/default_to_json_operation.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/default_to_json_operation.html new file mode 100644 index 00000000000..5ade7d0d282 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/default_to_json_operation.html @@ -0,0 +1,114 @@ + + + +IdlDictionary.prototype.default_to_json_operation() + + +
+ + + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/do_member_unscopable_asserts.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/do_member_unscopable_asserts.html new file mode 100644 index 00000000000..90142efe6b6 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/do_member_unscopable_asserts.html @@ -0,0 +1,56 @@ + + + +IdlDictionary.prototype.do_member_unscopable_asserts() + + +
+ + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_interface_object.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_interface_object.html new file mode 100644 index 00000000000..a3d901a752d --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_interface_object.html @@ -0,0 +1,22 @@ + +IdlInterface.prototype.get_interface_object() +
+ + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_interface_object_owner.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_interface_object_owner.html new file mode 100644 index 00000000000..51ab2067bc5 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_interface_object_owner.html @@ -0,0 +1,21 @@ + +IdlInterface.prototype.get_interface_object_owner() +
+ + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_legacy_namespace.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_legacy_namespace.html new file mode 100644 index 00000000000..e2d42bb09e3 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_legacy_namespace.html @@ -0,0 +1,20 @@ + +IdlInterface.prototype.get_legacy_namespace() +
+ + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_qualified_name.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_qualified_name.html new file mode 100644 index 00000000000..677a31b5e70 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_qualified_name.html @@ -0,0 +1,20 @@ + +IdlInterface.prototype.get_qualified_name() +
+ + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_reverse_inheritance_stack.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_reverse_inheritance_stack.html new file mode 100644 index 00000000000..0c066baabbc --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/get_reverse_inheritance_stack.html @@ -0,0 +1,49 @@ + + + +IdlInterface.prototype.get_reverse_inheritance_stack() + + +
+ + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/has_default_to_json_regular_operation.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/has_default_to_json_regular_operation.html new file mode 100644 index 00000000000..b47262b72b9 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/has_default_to_json_regular_operation.html @@ -0,0 +1,47 @@ + + + +IdlInterface.prototype.has_default_to_json_regular_operation() + + +
+ + + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/has_to_json_regular_operation.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/has_to_json_regular_operation.html new file mode 100644 index 00000000000..a1a641bd971 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/has_to_json_regular_operation.html @@ -0,0 +1,31 @@ + + + +IdlInterface.prototype.has_to_json_regular_operation() + + +
+ + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/should_have_interface_object.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/should_have_interface_object.html new file mode 100644 index 00000000000..3ce945751d8 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/should_have_interface_object.html @@ -0,0 +1,30 @@ + +IdlInterface.prototype.should_have_interface_object() +
+ + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/test_primary_interface_of_undefined.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/test_primary_interface_of_undefined.html new file mode 100644 index 00000000000..0031558ad42 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterface/test_primary_interface_of_undefined.html @@ -0,0 +1,29 @@ + + + + + idlharness test_primary_interface_of_undefined + + + + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterfaceMember/is_to_json_regular_operation.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterfaceMember/is_to_json_regular_operation.html new file mode 100644 index 00000000000..b3f402dd088 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterfaceMember/is_to_json_regular_operation.html @@ -0,0 +1,42 @@ + + + +IdlInterfaceMember.prototype.is_to_json_regular_operation() + + +
+ + + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/IdlInterfaceMember/toString.html b/test/fixtures/wpt/resources/test/tests/unit/IdlInterfaceMember/toString.html new file mode 100644 index 00000000000..054dbb1ccbd --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/IdlInterfaceMember/toString.html @@ -0,0 +1,36 @@ + + + +IdlInterfaceMember.prototype.toString() + + +
+ + + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/assert_implements.html b/test/fixtures/wpt/resources/test/tests/unit/assert_implements.html new file mode 100644 index 00000000000..6e35f385027 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/assert_implements.html @@ -0,0 +1,43 @@ + + + + + + +assert_implements unittests + diff --git a/test/fixtures/wpt/resources/test/tests/unit/assert_implements_optional.html b/test/fixtures/wpt/resources/test/tests/unit/assert_implements_optional.html new file mode 100644 index 00000000000..4f23e203c57 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/assert_implements_optional.html @@ -0,0 +1,43 @@ + + + + + + +assert_implements_optional unittests + diff --git a/test/fixtures/wpt/resources/test/tests/unit/assert_object_equals.html b/test/fixtures/wpt/resources/test/tests/unit/assert_object_equals.html new file mode 100644 index 00000000000..313d77b9771 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/assert_object_equals.html @@ -0,0 +1,152 @@ + + + + + + +Assertion functions + diff --git a/test/fixtures/wpt/resources/test/tests/unit/async-test-return-restrictions.html b/test/fixtures/wpt/resources/test/tests/unit/async-test-return-restrictions.html new file mode 100644 index 00000000000..0fde2e24223 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/async-test-return-restrictions.html @@ -0,0 +1,135 @@ + + + + + + Restrictions on return value from `async_test` + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/basic.html b/test/fixtures/wpt/resources/test/tests/unit/basic.html new file mode 100644 index 00000000000..d52082f2e07 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/basic.html @@ -0,0 +1,48 @@ + + + +idlharness basic + + +
+ + + + + + + diff --git a/test/fixtures/wpt/resources/test/tests/unit/exceptional-cases-timeouts.html b/test/fixtures/wpt/resources/test/tests/unit/exceptional-cases-timeouts.html new file mode 100644 index 00000000000..760ac7154f1 --- /dev/null +++ b/test/fixtures/wpt/resources/test/tests/unit/exceptional-cases-timeouts.html @@ -0,0 +1,120 @@ + + + + + + + Exceptional cases - timeouts + + +

+ 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