From dbb886fad2a1662ba25f3121a90ea2dd72402d84 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Sat, 2 Aug 2025 17:14:07 +0200 Subject: [PATCH] Implement initial version of `navigator.sendBeacon` (#38301) Gated behind the feature flag `dom_navigator_sendbeacon_enabled` as the `keep-alive` fetch parameter is crucial for real-life use cases such as analytics requests. Part of #4577 Part of #38302 Signed-off-by: Tim van der Lippe --- components/config/prefs.rs | 2 + components/script/dom/navigator.rs | 172 +++++++++++++++++- .../script/dom/performanceresourcetiming.rs | 4 +- .../script_bindings/codegen/Bindings.conf | 2 +- .../script_bindings/webidls/Navigator.webidl | 6 + components/shared/net/request.rs | 2 +- ports/servoshell/prefs.rs | 1 + tests/wpt/include.ini | 2 + .../beacon/beacon-basic.https.window.js.ini | 12 ++ .../beacon/beacon-cors.https.window.js.ini | 6 + ...-no-referrer-when-downgrade.https.html.ini | 4 + ...ct-origin-when-cross-origin.https.html.ini | 4 + ...ader-referrer-strict-origin.https.html.ini | 4 + .../header-referrer-unsafe-url.https.html.ini | 4 + .../connect-src-beacon-allowed.sub.html.ini | 3 - .../connect-src-beacon-blocked.sub.html.ini | 3 - ...rc-beacon-redirect-to-blocked.sub.html.ini | 3 - .../dom/usvstring-reflection.https.html.ini | 3 - .../top.http-rp/opt-in/beacon.https.html.ini | 4 +- .../gen/top.meta/opt-in/beacon.https.html.ini | 3 - .../gen/top.meta/unset/beacon.https.html.ini | 6 - .../initiator-type/misc.html.ini | 16 +- 22 files changed, 237 insertions(+), 29 deletions(-) create mode 100644 tests/wpt/meta/beacon/beacon-basic.https.window.js.ini create mode 100644 tests/wpt/meta/beacon/beacon-cors.https.window.js.ini create mode 100644 tests/wpt/meta/beacon/headers/header-referrer-no-referrer-when-downgrade.https.html.ini create mode 100644 tests/wpt/meta/beacon/headers/header-referrer-strict-origin-when-cross-origin.https.html.ini create mode 100644 tests/wpt/meta/beacon/headers/header-referrer-strict-origin.https.html.ini create mode 100644 tests/wpt/meta/beacon/headers/header-referrer-unsafe-url.https.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-allowed.sub.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-blocked.sub.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-redirect-to-blocked.sub.html.ini delete mode 100644 tests/wpt/meta/mixed-content/gen/top.meta/opt-in/beacon.https.html.ini delete mode 100644 tests/wpt/meta/mixed-content/gen/top.meta/unset/beacon.https.html.ini diff --git a/components/config/prefs.rs b/components/config/prefs.rs index fe19830ce21..030cc4d7920 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -99,6 +99,7 @@ pub struct Preferences { pub dom_microdata_testing_enabled: bool, pub dom_mouse_event_which_enabled: bool, pub dom_mutation_observer_enabled: bool, + pub dom_navigator_sendbeacon_enabled: bool, pub dom_notification_enabled: bool, pub dom_offscreen_canvas_enabled: bool, pub dom_permissions_enabled: bool, @@ -279,6 +280,7 @@ impl Preferences { dom_microdata_testing_enabled: false, dom_mouse_event_which_enabled: false, dom_mutation_observer_enabled: true, + dom_navigator_sendbeacon_enabled: false, dom_notification_enabled: false, dom_offscreen_canvas_enabled: false, dom_permissions_enabled: false, diff --git a/components/script/dom/navigator.rs b/components/script/dom/navigator.rs index ee67359f859..84481b18e8c 100644 --- a/components/script/dom/navigator.rs +++ b/components/script/dom/navigator.rs @@ -4,28 +4,45 @@ use std::cell::Cell; use std::convert::TryInto; -use std::sync::LazyLock; +use std::sync::{Arc, LazyLock, Mutex}; use dom_struct::dom_struct; +use headers::HeaderMap; +use http::header::{self, HeaderValue}; use js::rust::MutableHandleValue; +use net_traits::request::{ + CredentialsMode, Destination, RequestBuilder, RequestId, RequestMode, + is_cors_safelisted_request_content_type, +}; +use net_traits::{ + FetchMetadata, FetchResponseListener, NetworkError, ResourceFetchTiming, ResourceTimingType, +}; use servo_config::pref; +use servo_url::ServoUrl; +use crate::body::Extractable; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods; +use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; -use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::utils::to_frozen_array; #[cfg(feature = "bluetooth")] use crate::dom::bluetooth::Bluetooth; use crate::dom::clipboard::Clipboard; +use crate::dom::csp::{GlobalCspReporting, Violation}; use crate::dom::gamepad::Gamepad; use crate::dom::gamepadevent::GamepadEventType; +use crate::dom::globalscope::GlobalScope; use crate::dom::mediadevices::MediaDevices; use crate::dom::mediasession::MediaSession; use crate::dom::mimetypearray::MimeTypeArray; use crate::dom::navigatorinfo; +use crate::dom::performanceresourcetiming::InitiatorType; use crate::dom::permissions::Permissions; use crate::dom::pluginarray::PluginArray; use crate::dom::serviceworkercontainer::ServiceWorkerContainer; @@ -35,6 +52,7 @@ use crate::dom::webgpu::gpu::GPU; use crate::dom::window::Window; #[cfg(feature = "webxr")] use crate::dom::xrsystem::XRSystem; +use crate::network_listener::{PreInvoke, ResourceTimingListener, submit_timing}; use crate::script_runtime::{CanGc, JSContext}; pub(super) fn hardware_concurrency() -> u64 { @@ -320,9 +338,159 @@ impl NavigatorMethods for Navigator { .or_init(|| Clipboard::new(&self.global(), CanGc::note())) } + /// + fn SendBeacon(&self, url: USVString, data: Option, can_gc: CanGc) -> Fallible { + let global = self.global(); + // Step 1. Set base to this's relevant settings object's API base URL. + let base = global.api_base_url(); + // Step 2. Set origin to this's relevant settings object's origin. + let origin = global.origin().immutable().clone(); + // Step 3. Set parsedUrl to the result of the URL parser steps with url and base. + // If the algorithm returns an error, or if parsedUrl's scheme is not "http" or "https", + // throw a "TypeError" exception and terminate these steps. + let Ok(url) = ServoUrl::parse_with_base(Some(&base), &url) else { + return Err(Error::Type("Cannot parse URL".to_owned())); + }; + if !matches!(url.scheme(), "http" | "https") { + return Err(Error::Type("URL is not http(s)".to_owned())); + } + let mut request_body = None; + // Step 4. Let headerList be an empty list. + let mut headers = HeaderMap::with_capacity(1); + // Step 5. Let corsMode be "no-cors". + let mut cors_mode = RequestMode::NoCors; + // Step 6. If data is not null: + if let Some(data) = data { + // Step 6.1. Set transmittedData and contentType to the result of extracting data's byte stream + // with the keepalive flag set. + let extracted_body = data.extract(&global, can_gc)?; + // Step 6.2. If the amount of data that can be queued to be sent by keepalive enabled requests + // is exceeded by the size of transmittedData (as defined in HTTP-network-or-cache fetch), + // set the return value to false and terminate these steps. + if let Some(total_bytes) = extracted_body.total_bytes { + if total_bytes > 64 * 1024 { + return Ok(false); + } + } + // Step 6.3. If contentType is not null: + if let Some(content_type) = extracted_body.content_type.as_ref() { + // Set corsMode to "cors". + cors_mode = RequestMode::CorsMode; + // If contentType value is a CORS-safelisted request-header value for the Content-Type header, + // set corsMode to "no-cors". + if is_cors_safelisted_request_content_type(content_type.as_bytes()) { + cors_mode = RequestMode::NoCors; + } + // Append a Content-Type header with value contentType to headerList. + // + // We cannot use typed header insertion with `mime::Mime` parsing here, + // since it lowercases `charset=UTF-8`: https://github.com/hyperium/mime/issues/116 + headers.insert( + header::CONTENT_TYPE, + HeaderValue::from_str(content_type).unwrap(), + ); + } + request_body = Some(extracted_body.into_net_request_body().0); + } + // Step 7.1. Let req be a new request, initialized as follows: + let request = RequestBuilder::new(None, url.clone(), global.get_referrer()) + .mode(cors_mode) + .destination(Destination::None) + .policy_container(global.policy_container()) + .insecure_requests_policy(global.insecure_requests_policy()) + .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin()) + .method(http::Method::POST) + .body(request_body) + .origin(origin) + // TODO: Set keep-alive flag + .credentials_mode(CredentialsMode::Include) + .headers(headers); + // Step 7.2. Fetch req. + global.fetch( + request, + Arc::new(Mutex::new(BeaconFetchListener { + url, + global: Trusted::new(&global), + resource_timing: ResourceFetchTiming::new(ResourceTimingType::None), + })), + global.task_manager().networking_task_source().into(), + ); + // Step 7. Set the return value to true, return the sendBeacon() call, + // and continue to run the following steps in parallel: + Ok(true) + } + /// fn Servo(&self) -> DomRoot { self.servo_internals .or_init(|| ServoInternals::new(&self.global(), CanGc::note())) } } + +struct BeaconFetchListener { + /// URL of this request. + url: ServoUrl, + /// Timing data for this resource. + resource_timing: ResourceFetchTiming, + /// The global object fetching the report uri violation + global: Trusted, +} + +impl FetchResponseListener for BeaconFetchListener { + fn process_request_body(&mut self, _: RequestId) {} + + fn process_request_eof(&mut self, _: RequestId) {} + + fn process_response( + &mut self, + _: RequestId, + fetch_metadata: Result, + ) { + _ = fetch_metadata; + } + + fn process_response_chunk(&mut self, _: RequestId, chunk: Vec) { + _ = chunk; + } + + fn process_response_eof( + &mut self, + _: RequestId, + response: Result, + ) { + _ = response; + } + + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } + + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } + + fn submit_resource_timing(&mut self) { + submit_timing(self, CanGc::note()) + } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = self.resource_timing_global(); + global.report_csp_violations(violations, None, None); + } +} + +impl ResourceTimingListener for BeaconFetchListener { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + (InitiatorType::Beacon, self.url.clone()) + } + + fn resource_timing_global(&self) -> DomRoot { + self.global.root() + } +} + +impl PreInvoke for BeaconFetchListener { + fn should_invoke(&self) -> bool { + true + } +} diff --git a/components/script/dom/performanceresourcetiming.rs b/components/script/dom/performanceresourcetiming.rs index 88bd36b77fc..de13a79935c 100644 --- a/components/script/dom/performanceresourcetiming.rs +++ b/components/script/dom/performanceresourcetiming.rs @@ -23,9 +23,10 @@ use crate::script_runtime::CanGc; // TODO Cross origin resources MUST BE INCLUDED as PerformanceResourceTiming objects // https://w3c.github.io/resource-timing/#sec-cross-origin-resources -// TODO CSS, Beacon +// TODO CSS #[derive(Debug, JSTraceable, MallocSizeOf, PartialEq)] pub(crate) enum InitiatorType { + Beacon, LocalName(String), Navigation, XMLHttpRequest, @@ -191,6 +192,7 @@ impl PerformanceResourceTimingMethods for PerformanceResou // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-initiatortype fn InitiatorType(&self) -> DOMString { match self.initiator_type { + InitiatorType::Beacon => DOMString::from("beacon"), InitiatorType::LocalName(ref n) => DOMString::from(n.clone()), InitiatorType::Navigation => DOMString::from("navigation"), InitiatorType::XMLHttpRequest => DOMString::from("xmlhttprequest"), diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 2a4aaa82439..e356cb52b53 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -503,7 +503,7 @@ DOMInterfaces = { 'Navigator': { 'inRealms': ['GetVRDisplays'], - 'canGc': ['Languages'], + 'canGc': ['Languages', 'SendBeacon'], }, 'Node': { diff --git a/components/script_bindings/webidls/Navigator.webidl b/components/script_bindings/webidls/Navigator.webidl index 6ac6175f3ca..51681e18e20 100644 --- a/components/script_bindings/webidls/Navigator.webidl +++ b/components/script_bindings/webidls/Navigator.webidl @@ -81,3 +81,9 @@ interface mixin NavigatorConcurrentHardware { partial interface Navigator { [SecureContext, SameObject, Pref="dom_async_clipboard_enabled"] readonly attribute Clipboard clipboard; }; + +// https://w3c.github.io/beacon/#sendbeacon-method +partial interface Navigator { + [Throws, Pref="dom_navigator_sendbeacon_enabled"] + boolean sendBeacon(USVString url, optional BodyInit? data = null); +}; diff --git a/components/shared/net/request.rs b/components/shared/net/request.rs index 12d960357b4..334f3750f72 100644 --- a/components/shared/net/request.rs +++ b/components/shared/net/request.rs @@ -801,7 +801,7 @@ fn is_cors_safelisted_language(value: &[u8]) -> bool { // https://fetch.spec.whatwg.org/#cors-safelisted-request-header // subclause `content-type` -fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool { +pub fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool { // step 1 if value.iter().any(is_cors_unsafe_request_header_byte) { return false; diff --git a/ports/servoshell/prefs.rs b/ports/servoshell/prefs.rs index 439d366d254..6ad93e1ff78 100644 --- a/ports/servoshell/prefs.rs +++ b/ports/servoshell/prefs.rs @@ -591,6 +591,7 @@ pub(crate) fn parse_command_line_arguments(args: Vec) -> ArgumentParsing "dom_fontface_enabled", "dom_intersection_observer_enabled", "dom_mouse_event_which_enabled", + "dom_navigator_sendbeacon_enabled", "dom_notification_enabled", "dom_offscreen_canvas_enabled", "dom_permissions_enabled", diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index 3fac6d480bc..c230951b9b1 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -3,6 +3,8 @@ skip: true skip: false [_webgl] skip: false +[beacon] + skip: false [clipboard-apis] skip: false [console] diff --git a/tests/wpt/meta/beacon/beacon-basic.https.window.js.ini b/tests/wpt/meta/beacon/beacon-basic.https.window.js.ini new file mode 100644 index 00000000000..cf521f7fda1 --- /dev/null +++ b/tests/wpt/meta/beacon/beacon-basic.https.window.js.ini @@ -0,0 +1,12 @@ +[beacon-basic.https.window.html] + [Payload size restriction should be accumulated: type = string] + expected: FAIL + + [Payload size restriction should be accumulated: type = arraybuffer] + expected: FAIL + + [Payload size restriction should be accumulated: type = blob] + expected: FAIL + + [sendBeacon() with a stream does not work due to the keepalive flag being set] + expected: FAIL diff --git a/tests/wpt/meta/beacon/beacon-cors.https.window.js.ini b/tests/wpt/meta/beacon/beacon-cors.https.window.js.ini new file mode 100644 index 00000000000..426986abc0f --- /dev/null +++ b/tests/wpt/meta/beacon/beacon-cors.https.window.js.ini @@ -0,0 +1,6 @@ +[beacon-cors.https.window.html] + [cross-origin, non-CORS-safelisted: failure case (without redirect)] + expected: FAIL + + [cross-origin, non-CORS-safelisted[credentials=false\]] + expected: FAIL diff --git a/tests/wpt/meta/beacon/headers/header-referrer-no-referrer-when-downgrade.https.html.ini b/tests/wpt/meta/beacon/headers/header-referrer-no-referrer-when-downgrade.https.html.ini new file mode 100644 index 00000000000..d85fa94fd2f --- /dev/null +++ b/tests/wpt/meta/beacon/headers/header-referrer-no-referrer-when-downgrade.https.html.ini @@ -0,0 +1,4 @@ +[header-referrer-no-referrer-when-downgrade.https.html] + expected: TIMEOUT + [Test referer header http://web-platform.test:8000/beacon/resources/] + expected: TIMEOUT diff --git a/tests/wpt/meta/beacon/headers/header-referrer-strict-origin-when-cross-origin.https.html.ini b/tests/wpt/meta/beacon/headers/header-referrer-strict-origin-when-cross-origin.https.html.ini new file mode 100644 index 00000000000..b2d3db8ea74 --- /dev/null +++ b/tests/wpt/meta/beacon/headers/header-referrer-strict-origin-when-cross-origin.https.html.ini @@ -0,0 +1,4 @@ +[header-referrer-strict-origin-when-cross-origin.https.html] + expected: TIMEOUT + [Test referer header http://www1.web-platform.test:8000/beacon/resources/] + expected: TIMEOUT diff --git a/tests/wpt/meta/beacon/headers/header-referrer-strict-origin.https.html.ini b/tests/wpt/meta/beacon/headers/header-referrer-strict-origin.https.html.ini new file mode 100644 index 00000000000..8254bef3931 --- /dev/null +++ b/tests/wpt/meta/beacon/headers/header-referrer-strict-origin.https.html.ini @@ -0,0 +1,4 @@ +[header-referrer-strict-origin.https.html] + expected: TIMEOUT + [Test referer header http://web-platform.test:8000/beacon/resources/] + expected: TIMEOUT diff --git a/tests/wpt/meta/beacon/headers/header-referrer-unsafe-url.https.html.ini b/tests/wpt/meta/beacon/headers/header-referrer-unsafe-url.https.html.ini new file mode 100644 index 00000000000..17d04dd791b --- /dev/null +++ b/tests/wpt/meta/beacon/headers/header-referrer-unsafe-url.https.html.ini @@ -0,0 +1,4 @@ +[header-referrer-unsafe-url.https.html] + expected: TIMEOUT + [Test referer header http://web-platform.test:8000/beacon/resources/] + expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-allowed.sub.html.ini b/tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-allowed.sub.html.ini deleted file mode 100644 index ba79b872b4a..00000000000 --- a/tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-allowed.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[connect-src-beacon-allowed.sub.html] - [Expecting logs: ["Pass"\]] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-blocked.sub.html.ini deleted file mode 100644 index d1a7e7f312a..00000000000 --- a/tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[connect-src-beacon-blocked.sub.html] - [Expecting logs: ["Pass", "violated-directive=connect-src"\]] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-redirect-to-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-redirect-to-blocked.sub.html.ini deleted file mode 100644 index 94269848821..00000000000 --- a/tests/wpt/meta/content-security-policy/connect-src/connect-src-beacon-redirect-to-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[connect-src-beacon-redirect-to-blocked.sub.html] - [Expecting logs: ["violated-directive=connect-src"\]] - expected: NOTRUN diff --git a/tests/wpt/meta/html/dom/usvstring-reflection.https.html.ini b/tests/wpt/meta/html/dom/usvstring-reflection.https.html.ini index d7cee34b8bf..da31867ecb1 100644 --- a/tests/wpt/meta/html/dom/usvstring-reflection.https.html.ini +++ b/tests/wpt/meta/html/dom/usvstring-reflection.https.html.ini @@ -14,9 +14,6 @@ [source : unpaired surrogate codepoint should be replaced with U+FFFD] expected: FAIL - [sendBeacon URL: unpaired surrogate codepoint should not make any exceptions.] - expected: FAIL - [RegisterProtocolHandler URL: unpaired surrogate codepoint should not make any exceptions.] expected: FAIL diff --git a/tests/wpt/meta/mixed-content/gen/top.http-rp/opt-in/beacon.https.html.ini b/tests/wpt/meta/mixed-content/gen/top.http-rp/opt-in/beacon.https.html.ini index a910190f49d..ea4a9b58bbe 100644 --- a/tests/wpt/meta/mixed-content/gen/top.http-rp/opt-in/beacon.https.html.ini +++ b/tests/wpt/meta/mixed-content/gen/top.http-rp/opt-in/beacon.https.html.ini @@ -1,6 +1,6 @@ [beacon.https.html] - [Mixed-Content: Expects allowed for beacon to same-https origin and keep-scheme redirection from https context.] + [Mixed-Content: Expects blocked for beacon to cross-https origin and swap-scheme redirection from https context.] expected: FAIL - [Mixed-Content: Expects allowed for beacon to same-https origin and no-redirect redirection from https context.] + [Mixed-Content: Expects blocked for beacon to same-https origin and swap-scheme redirection from https context.] expected: FAIL diff --git a/tests/wpt/meta/mixed-content/gen/top.meta/opt-in/beacon.https.html.ini b/tests/wpt/meta/mixed-content/gen/top.meta/opt-in/beacon.https.html.ini deleted file mode 100644 index 256397774c6..00000000000 --- a/tests/wpt/meta/mixed-content/gen/top.meta/opt-in/beacon.https.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[beacon.https.html] - [Mixed-Content: Expects allowed for beacon to same-https origin and no-redirect redirection from https context.] - expected: FAIL diff --git a/tests/wpt/meta/mixed-content/gen/top.meta/unset/beacon.https.html.ini b/tests/wpt/meta/mixed-content/gen/top.meta/unset/beacon.https.html.ini deleted file mode 100644 index a910190f49d..00000000000 --- a/tests/wpt/meta/mixed-content/gen/top.meta/unset/beacon.https.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[beacon.https.html] - [Mixed-Content: Expects allowed for beacon to same-https origin and keep-scheme redirection from https context.] - expected: FAIL - - [Mixed-Content: Expects allowed for beacon to same-https origin and no-redirect redirection from https context.] - expected: FAIL diff --git a/tests/wpt/meta/resource-timing/initiator-type/misc.html.ini b/tests/wpt/meta/resource-timing/initiator-type/misc.html.ini index 927bf0a1f53..43c37351500 100644 --- a/tests/wpt/meta/resource-timing/initiator-type/misc.html.ini +++ b/tests/wpt/meta/resource-timing/initiator-type/misc.html.ini @@ -1,2 +1,16 @@ [misc.html] - expected: ERROR + expected: TIMEOUT + [The initiator type for must be 'body'] + expected: FAIL + + [The initiator type for must be 'input'] + expected: FAIL + + [The initiator type for must be 'object'] + expected: FAIL + + [The initiator type for for fetch() must be 'fetch'] + expected: FAIL + + [The initiator type for new EventSource() must be 'other'] + expected: TIMEOUT