From 457d8a8a5c720fbf5624a95d86ac53f2aee342e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=BClker?= Date: Tue, 8 Oct 2024 07:36:46 +0200 Subject: [PATCH] Bring `http_network_or_cache_fetch` closer to the spec (#33531) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Document "fetch" method Signed-off-by: Simon Wülker * Bring http_network_or_cache_fetch closer to the spec Signed-off-by: Simon Wülker * fix test-tidy errors Signed-off-by: Simon Wülker * Move all code into http_loader.rs Signed-off-by: Simon Wülker * Don't panic if hyper/servo disagree about valid origins Signed-off-by: Simon Wülker * Add "otherwise" to spec comment Signed-off-by: Simon Wülker * Convert FIXME's to TODOs when appropriate Signed-off-by: Simon Wülker * Remove TODO about No-Store cache directive Signed-off-by: Simon Wülker * Remove indentation from multiline spec comments Signed-off-by: Simon Wülker * Add fetch assertions This is from a spec update where assertions about requests origin not being client were added. Signed-off-by: Simon Wülker * Add note about serializing headers Signed-off-by: Simon Wülker * Add TODO about partitioning http cache Signed-off-by: Simon Wülker * Convert FIXME to TODO in script/ Signed-off-by: Simon Wülker * Link to relevant issue for TODO comments Signed-off-by: Simon Wülker --------- Signed-off-by: Simon Wülker --- components/net/http_loader.rs | 456 +++++++++++------- components/script/fetch.rs | 34 +- .../meta/fetch/origin/assorted.window.js.ini | 18 - 3 files changed, 320 insertions(+), 188 deletions(-) diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 2950fd8e85b..b282ee270ef 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -22,7 +22,7 @@ use headers::{ AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowMethods, AccessControlAllowOrigin, AccessControlMaxAge, AccessControlRequestHeaders, AccessControlRequestMethod, Authorization, CacheControl, ContentLength, HeaderMapExt, - IfModifiedSince, LastModified, Origin as HyperOrigin, Pragma, Referer, UserAgent, + IfModifiedSince, LastModified, Pragma, Referer, UserAgent, }; use http::header::{ self, HeaderValue, ACCEPT, AUTHORIZATION, CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LOCATION, @@ -1082,19 +1082,6 @@ pub async fn http_redirect_fetch( fetch_response } -fn try_immutable_origin_to_hyper_origin(url_origin: &ImmutableOrigin) -> Option { - match *url_origin { - ImmutableOrigin::Opaque(_) => Some(HyperOrigin::NULL), - ImmutableOrigin::Tuple(ref scheme, ref host, ref port) => { - let port = match (scheme.as_ref(), port) { - ("http", 80) | ("https", 443) => None, - _ => Some(*port), - }; - HyperOrigin::try_from_parts(scheme, &host.to_string(), port).ok() - }, - } -} - /// [HTTP network or cache fetch](https://fetch.spec.whatwg.org#http-network-or-cache-fetch) #[async_recursion] async fn http_network_or_cache_fetch( @@ -1104,25 +1091,32 @@ async fn http_network_or_cache_fetch( done_chan: &mut DoneChannel, context: &FetchContext, ) -> Response { - // Step 3: Let httpRequest be null. + // Step 1. Let request be fetchParams’s request. + // NOTE: We get request as an argument (Fetchparams are not implemented, see #33616) + + // Step 3. Let httpRequest be null. let mut http_request; - // Step 4: Let response be null. + // Step 4. Let response be null. let mut response: Option = None; - // Step 7: Let the revalidatingFlag be unset. + // Step 7. Let the revalidatingFlag be unset. let mut revalidating_flag = false; + // TODO(#33616): Step 8. Run these steps, but abort when fetchParams is canceled: // Step 8.1: If request’s window is "no-window" and request’s redirect mode is "error", then set // httpFetchParams to fetchParams and httpRequest to request. let request_has_no_window = request.window == RequestWindow::NoWindow; let http_request = if request_has_no_window && request.redirect_mode == RedirectMode::Error { request - } else { + } + // Step 8.2 Otherwise: + else { // Step 8.2.1: Set httpRequest to a clone of request. http_request = request.clone(); + // TODO(#33616): Step 8.2.2-8.2.3 &mut http_request }; @@ -1141,34 +1135,47 @@ async fn http_network_or_cache_fetch( // Step 8.4: If Cross-Origin-Embedder-Policy allows credentials with request returns false, then // set includeCredentials to false. - // TODO: Requires request's client object + // TODO(#33616): Requires request's client object - // Step 8.5: Let contentLength be httpRequest’s body’s length, if httpRequest’s body is - // non-null; otherwise null. - let content_length_value = match http_request.body { - Some(ref http_request_body) => http_request_body.len().map(|size| size as u64), - // Step 8.7: If httpRequest’s body is null and httpRequest’s method is `POST` or `PUT`, then - // set contentLengthHeaderValue to `0`. - None => match http_request.method { - Method::POST | Method::PUT => Some(0), - _ => None, - }, - }; + // Step 8.5 Let contentLength be httpRequest’s body’s length, if httpRequest’s body is non-null; + // otherwise null. + let content_length = http_request + .body + .as_ref() + .map(|body| body.len().map(|size| size as u64)) + .flatten(); - // Step 8.9: If contentLengthHeaderValue is non-null, then append (`Content-Length`, - // contentLengthHeaderValue) to httpRequest’s header list. - if let Some(content_length_value) = content_length_value { - http_request - .headers - .typed_insert(ContentLength(content_length_value)); + // Step 8.6 Let contentLengthHeaderValue be null. + let mut content_length_header_value = None; + + // Step 8.7 If httpRequest’s body is null and httpRequest’s method is `POST` or `PUT`, + // then set contentLengthHeaderValue to `0`. + if http_request.body.is_none() && matches!(http_request.method, Method::POST | Method::PUT) { + content_length_header_value = Some(0); } - // Step 8.10: If contentLength is non-null and httpRequest’s keepalive is true, then: - // TODO Keepalive requires request's client object's fetch group + // Step 8.8 If contentLength is non-null, then set contentLengthHeaderValue to contentLength, + // serialized and isomorphic encoded. + // NOTE: The header will later be serialized using HeaderMap::typed_insert + if let Some(content_length) = content_length { + content_length_header_value = Some(content_length); + }; + + // Step 8.9 If contentLengthHeaderValue is non-null, then append (`Content-Length`, contentLengthHeaderValue) + // to httpRequest’s header list. + if let Some(content_length_header_value) = content_length_header_value { + http_request + .headers + .typed_insert(ContentLength(content_length_header_value)); + } + + // Step 8.10 If contentLength is non-null and httpRequest’s keepalive is true, then: + if content_length.is_some() && http_request.keep_alive { + // TODO(#33616) Keepalive requires request's client object's fetch group + } // Step 8.11: If httpRequest’s referrer is a URL, then: match http_request.referrer { - Referrer::NoReferrer => (), Referrer::ReferrerUrl(ref http_request_referrer) | Referrer::Client(ref http_request_referrer) => { // Step 8.11.1: Let referrerValue be httpRequest’s referrer, serialized and isomorphic @@ -1180,23 +1187,17 @@ async fn http_network_or_cache_fetch( // This error should only happen in cases where hyper and rust-url disagree // about how to parse a referer. // https://github.com/servo/servo/issues/24175 - error!("Failed to parse {} as referer", http_request_referrer); + error!("Failed to parse {} as referrer", http_request_referrer); } }, + _ => {}, }; - // Step 8.12: Append a request `Origin` header for httpRequest. - if cors_flag || (http_request.method != Method::GET && http_request.method != Method::HEAD) { - debug_assert_ne!(http_request.origin, Origin::Client); - if let Origin::Origin(ref url_origin) = http_request.origin { - if let Some(hyper_origin) = try_immutable_origin_to_hyper_origin(url_origin) { - http_request.headers.typed_insert(hyper_origin) - } - } - } + // Step 8.12 Append a request `Origin` header for httpRequest. + append_a_request_origin_header(http_request); - // Step 8.13: Append the Fetch metadata headers for httpRequest. - // TODO Implement Sec-Fetch-* headers + // Step 8.13 Append the Fetch metadata headers for httpRequest. + // TODO(#33616) Implement Sec-Fetch-* headers // Step 8.14: If httpRequest’s initiator is "prefetch", then set a structured field value given // (`Sec-Purpose`, the token "prefetch") in httpRequest’s header list. @@ -1317,8 +1318,7 @@ async fn http_network_or_cache_fetch( } } - // Step 5.18 - // TODO If there’s a proxy-authentication entry, use it as appropriate. + // TODO(#33616) Step 8.22 If there’s a proxy-authentication entry, use it as appropriate. // If the cache is not ready to construct a response, wait. // @@ -1355,11 +1355,16 @@ async fn http_network_or_cache_fetch( } } - // Step 5.19 + // TODO(#33616): Step 8.23 Set httpCache to the result of determining the + // HTTP cache partition, given httpRequest. if let Ok(http_cache) = context.state.http_cache.read() { - if let Some(response_from_cache) = - http_cache.construct_response(http_request, done_chan) - { + // Step 8.25.1 Set storedResponse to the result of selecting a response from the httpCache, + // possibly needing validation, as per the "Constructing Responses from Caches" + // chapter of HTTP Caching, if any. + let stored_response = http_cache.construct_response(http_request, done_chan); + + // Step 8.25.2 If storedResponse is non-null, then: + if let Some(response_from_cache) = stored_response { let response_headers = response_from_cache.response.headers.clone(); // Substep 1, 2, 3, 4 let (cached_response, needs_revalidation) = @@ -1376,6 +1381,7 @@ async fn http_network_or_cache_fetch( response_from_cache.needs_validation, ), }; + if needs_revalidation { revalidating_flag = true; // Substep 5 @@ -1472,12 +1478,11 @@ async fn http_network_or_cache_fetch( wait_for_cached_response(done_chan, &mut response).await; - // Step 6 - // TODO: https://infra.spec.whatwg.org/#if-aborted + // TODO(#33616): Step 9. If aborted, then return the appropriate network error for fetchParams. - // Step 7 + // Step 10. If response is null, then: if response.is_none() { - // Substep 1 + // Step 10.1 If httpRequest’s cache mode is "only-if-cached", then return a network error. if http_request.cache_mode == CacheMode::OnlyIfCached { // The cache will not be updated, // set its state to ready to construct. @@ -1486,20 +1491,22 @@ async fn http_network_or_cache_fetch( "Couldn't find response in cache".into(), )); } - } - // More Step 7 - if response.is_none() { - // Substep 2 + + // Step 10.2 Let forwardResponse be the result of running HTTP-network fetch given httpFetchParams, + // includeCredentials, and isNewConnectionFetch. let forward_response = http_network_fetch(http_request, include_credentials, done_chan, context).await; - // Substep 3 + + // Step 10.3 If httpRequest’s method is unsafe and forwardResponse’s status is in the range 200 to 399, + // inclusive, invalidate appropriate stored responses in httpCache, as per the + // "Invalidating Stored Responses" chapter of HTTP Caching, and set storedResponse to null. if forward_response.status.in_range(200..=399) && !http_request.method.is_safe() { if let Ok(mut http_cache) = context.state.http_cache.write() { http_cache.invalidate(http_request, &forward_response); } } - // Substep 4 + // Step 10.4 If the revalidatingFlag is set and forwardResponse’s status is 304, then: if revalidating_flag && forward_response.status == StatusCode::NOT_MODIFIED { if let Ok(mut http_cache) = context.state.http_cache.write() { // Ensure done_chan is None, @@ -1510,88 +1517,29 @@ async fn http_network_or_cache_fetch( wait_for_cached_response(done_chan, &mut response).await; } - // Substep 5 + // Step 10.5 If response is null, then: if response.is_none() { + // Step 10.5.1 Set response to forwardResponse. + let forward_response = response.insert(forward_response); + + // Per https://httpwg.org/specs/rfc9111.html#response.cacheability we must not cache responses + // if the No-Store directive is present if http_request.cache_mode != CacheMode::NoStore { - // Subsubstep 2, doing it first to avoid a clone of forward_response. + // Step 10.5.2 Store httpRequest and forwardResponse in httpCache, as per the + // "Storing Responses in Caches" chapter of HTTP Caching. if let Ok(mut http_cache) = context.state.http_cache.write() { - http_cache.store(http_request, &forward_response); + http_cache.store(http_request, forward_response); } } - // Subsubstep 1 - response = Some(forward_response); } } - - let mut response = response.unwrap(); - // The cache has been updated, set its state to ready to construct. update_http_cache_state(context, http_request); - // Step 8 - // TODO: if necessary set response's range-requested flag - - // Step 9 - // https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check - #[derive(PartialEq)] - enum CrossOriginResourcePolicy { - Allowed, - Blocked, - } - - fn cross_origin_resource_policy_check( - request: &Request, - response: &Response, - ) -> CrossOriginResourcePolicy { - // Step 1 - if request.mode != RequestMode::NoCors { - return CrossOriginResourcePolicy::Allowed; - } - - // Step 2 - let current_url_origin = request.current_url().origin(); - let same_origin = if let Origin::Origin(ref origin) = request.origin { - *origin == request.current_url().origin() - } else { - false - }; - - if same_origin { - return CrossOriginResourcePolicy::Allowed; - } - - // Step 3 - let policy = response - .headers - .get(HeaderName::from_static("cross-origin-resource-policy")) - .map(|h| h.to_str().unwrap_or("")) - .unwrap_or(""); - - // Step 4 - if policy == "same-origin" { - return CrossOriginResourcePolicy::Blocked; - } - - // Step 5 - if let Origin::Origin(ref request_origin) = request.origin { - let schemeless_same_origin = - is_schemelessy_same_site(request_origin, ¤t_url_origin); - if schemeless_same_origin && - (request_origin.scheme() == Some("https") || - response.https_state == HttpsState::None) - { - return CrossOriginResourcePolicy::Allowed; - } - }; - - // Step 6 - if policy == "same-site" { - return CrossOriginResourcePolicy::Blocked; - } - - CrossOriginResourcePolicy::Allowed - } + let mut response = response.unwrap(); + // FIXME: The spec doesn't tell us to do this *here*, but if we don't do it then + // tests fail. Where should we do it instead? See also #33615 if http_request.response_tainting != ResponseTainting::CorsTainting && cross_origin_resource_policy_check(http_request, &response) == CrossOriginResourcePolicy::Blocked @@ -1601,26 +1549,31 @@ async fn http_network_or_cache_fetch( )); } - // Step 10 - // FIXME: Figure out what to do with request window objects + // TODO(#33616): Step 11. Set response’s URL list to a clone of httpRequest’s URL list. + // TODO(#33616): Step 12. If httpRequest’s header list contains `Range`, + // then set response’s range-requested flag. + // TODO(#33616): Step 13 Set response’s request-includes-credentials to includeCredentials. + + // Step 14. If response’s status is 401, httpRequest’s response tainting is not "cors", + // includeCredentials is true, and request’s window is an environment settings object, then: + // TODO(#33616): Figure out what to do with request window objects if let (Some(StatusCode::UNAUTHORIZED), false, true) = (response.status.try_code(), cors_flag, include_credentials) { - // Substep 1 - // TODO: Spec says requires testing on multiple WWW-Authenticate headers + // TODO: Step 14.1 Spec says requires testing on multiple WWW-Authenticate headers - // Substep 2 + // Step 14.2 If request’s body is non-null, then: if http_request.body.is_some() { // TODO Implement body source } - // Substep 3 + // Step 14.3 If request’s use-URL-credentials flag is unset or isAuthenticationFetch is true, then: if !http_request.use_url_credentials || authentication_fetch_flag { - // FIXME: Prompt the user for username and password from the window + // TODO(#33616, #27439): Prompt the user for username and password from the window // Wrong, but will have to do until we are able to prompt the user // otherwise this creates an infinite loop - // We basically pretend that the user declined to enter credentials + // We basically pretend that the user declined to enter credentials (#33616) return response; } @@ -1628,7 +1581,7 @@ async fn http_network_or_cache_fetch( // since we're about to start a new `http_network_or_cache_fetch`. *done_chan = None; - // Substep 4 + // Step 14.4 Set response to the result of running HTTP-network-or-cache fetch given fetchParams and true. response = http_network_or_cache_fetch( http_request, true, /* authentication flag */ @@ -1639,40 +1592,109 @@ async fn http_network_or_cache_fetch( .await; } - // Step 11 + // Step 15. If response’s status is 407, then: if response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED { - // Step 1 + // Step 15.1 If request’s window is "no-window", then return a network error. + if request_has_no_window { return Response::network_error(NetworkError::Internal( "Can't find Window object".into(), )); } - // Step 2 - // TODO: Spec says requires testing on Proxy-Authenticate headers + // (Step 15.2 does not exist, requires testing on Proxy-Authenticate headers) - // Step 3 - // FIXME: Prompt the user for proxy authentication credentials + // TODO(#33616): Step 15.3 If fetchParams is canceled, then return + // the appropriate network error for fetchParams. + // TODO(#33616): Step 15.4 Prompt the end user as appropriate in request’s window and store the + // result as a proxy-authentication entry. + + // Step 15.5 Set response to the result of running HTTP-network-or-cache fetch given fetchParams. // Wrong, but will have to do until we are able to prompt the user // otherwise this creates an infinite loop - // We basically pretend that the user declined to enter credentials + // We basically pretend that the user declined to enter credentials (#33616) return response; - - // Step 4 - // return http_network_or_cache_fetch(request, authentication_fetch_flag, - // cors_flag, done_chan, context); } - // Step 12 + // TODO(#33616): Step 16. If all of the following are true: + // * response’s status is 421 + // * isNewConnectionFetch is false + // * request’s body is null, or request’s body is non-null and request’s body’s source is non-null + // then: [..] + + // Step 17. If isAuthenticationFetch is true, then create an authentication entry for request and the given realm. if authentication_fetch_flag { - // TODO Create the authentication entry for request and the given realm + // TODO(#33616) } - // Step 13 + // Step 18. Return response. response } +/// +/// +/// This is obtained from [cross_origin_resource_policy_check] +#[derive(PartialEq)] +enum CrossOriginResourcePolicy { + Allowed, + Blocked, +} + +// TODO(#33615): Judging from the name, this appears to be https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check, +// but the steps aren't even close to the spec. Perhaps this needs to be rewritten? +fn cross_origin_resource_policy_check( + request: &Request, + response: &Response, +) -> CrossOriginResourcePolicy { + // Step 1 + if request.mode != RequestMode::NoCors { + return CrossOriginResourcePolicy::Allowed; + } + + // Step 2 + let current_url_origin = request.current_url().origin(); + let same_origin = if let Origin::Origin(ref origin) = request.origin { + *origin == request.current_url().origin() + } else { + false + }; + + if same_origin { + return CrossOriginResourcePolicy::Allowed; + } + + // Step 3 + let policy = response + .headers + .get(HeaderName::from_static("cross-origin-resource-policy")) + .map(|h| h.to_str().unwrap_or("")) + .unwrap_or(""); + + // Step 4 + if policy == "same-origin" { + return CrossOriginResourcePolicy::Blocked; + } + + // Step 5 + if let Origin::Origin(ref request_origin) = request.origin { + let schemeless_same_origin = is_schemelessy_same_site(request_origin, ¤t_url_origin); + if schemeless_same_origin && + (request_origin.scheme() == Some("https") || + response.https_state == HttpsState::None) + { + return CrossOriginResourcePolicy::Allowed; + } + }; + + // Step 6 + if policy == "same-site" { + return CrossOriginResourcePolicy::Blocked; + } + + CrossOriginResourcePolicy::Allowed +} + // Convenience struct that implements Done, for setting responseEnd on function return struct ResponseEndTimer(Option>>); @@ -2223,3 +2245,115 @@ pub fn is_redirect_status(status: StatusCode) -> bool { StatusCode::PERMANENT_REDIRECT ) } + +/// +fn request_has_redirect_tainted_origin(request: &Request) -> bool { + // Step 1. Assert: request’s origin is not "client". + let Origin::Origin(request_origin) = &request.origin else { + panic!("origin cannot be \"client\" at this point in time"); + }; + + // Step 2. Let lastURL be null. + let mut last_url = None; + + // Step 3. For each url of request’s URL list: + for url in &request.url_list { + // Step 3.1 If lastURL is null, then set lastURL to url and continue. + let Some(last_url) = &mut last_url else { + last_url = Some(url); + continue; + }; + + // Step 3.2 If url’s origin is not same origin with lastURL’s origin and + // request’s origin is not same origin with lastURL’s origin, then return true. + if url.origin() != last_url.origin() && *request_origin != last_url.origin() { + return true; + } + + // Step 3.3 Set lastURL to url. + *last_url = url; + } + + // Step 4. Return false. + false +} + +/// +fn serialize_request_origin(request: &Request) -> headers::Origin { + // Step 1. Assert: request’s origin is not "client". + let Origin::Origin(origin) = &request.origin else { + panic!("origin cannot be \"client\" at this point in time"); + }; + + // Step 2. If request has a redirect-tainted origin, then return "null". + if request_has_redirect_tainted_origin(request) { + return headers::Origin::NULL; + } + + // Step 3. Return request’s origin, serialized. + match origin { + ImmutableOrigin::Opaque(_) => headers::Origin::NULL, + ImmutableOrigin::Tuple(scheme, host, port) => { + // TODO: Ensure that hyper/servo don't disagree about valid origin headers + headers::Origin::try_from_parts(scheme, &host.to_string(), *port) + .unwrap_or(headers::Origin::NULL) + }, + } +} + +/// +pub fn append_a_request_origin_header(request: &mut Request) { + // Step 1. Assert: request’s origin is not "client". + let Origin::Origin(request_origin) = &request.origin else { + panic!("origin cannot be \"client\" at this point in time"); + }; + + // Step 2. Let serializedOrigin be the result of byte-serializing a request origin with request. + let mut serialized_origin = serialize_request_origin(request); + + // Step 3. If request’s response tainting is "cors" or request’s mode is "websocket", + // then append (`Origin`, serializedOrigin) to request’s header list. + if request.response_tainting == ResponseTainting::CorsTainting || + matches!(request.mode, RequestMode::WebSocket { .. }) + { + request.headers.typed_insert(serialized_origin); + } + // Step 4. Otherwise, if request’s method is neither `GET` nor `HEAD`, then: + else if !matches!(request.method, Method::GET | Method::HEAD) { + // Step 4.1 If request’s mode is not "cors", then switch on request’s referrer policy: + if request.mode != RequestMode::CorsMode { + match request.referrer_policy { + Some(ReferrerPolicy::NoReferrer) => { + // Set serializedOrigin to `null`. + serialized_origin = headers::Origin::NULL; + }, + Some( + ReferrerPolicy::NoReferrerWhenDowngrade | + ReferrerPolicy::StrictOrigin | + ReferrerPolicy::StrictOriginWhenCrossOrigin, + ) => { + // If request’s origin is a tuple origin, its scheme is "https", and + // request’s current URL’s scheme is not "https", then set serializedOrigin to `null`. + if let ImmutableOrigin::Tuple(scheme, _, _) = &request_origin { + if scheme == "https" && request.current_url().scheme() != "https" { + serialized_origin = headers::Origin::NULL; + } + } + }, + Some(ReferrerPolicy::SameOrigin) => { + // If request’s origin is not same origin with request’s current URL’s origin, + // then set serializedOrigin to `null`. + if *request_origin != request.current_url().origin() { + serialized_origin = headers::Origin::NULL; + } + }, + _ => { + // Otherwise, do nothing. + }, + }; + } + + // Step 4.2. Append (`Origin`, serializedOrigin) to request’s header list. + request.headers.typed_insert(serialized_origin); + } +} diff --git a/components/script/fetch.rs b/components/script/fetch.rs index 9554bc46394..2b545e3daaf 100644 --- a/components/script/fetch.rs +++ b/components/script/fetch.rs @@ -138,7 +138,7 @@ fn request_init_from_request(request: NetTraitsRequest) -> RequestBuilder { } } -// https://fetch.spec.whatwg.org/#fetch-method +/// #[allow(crown::unrooted_must_root, non_snake_case)] pub fn Fetch( global: &GlobalScope, @@ -148,33 +148,47 @@ pub fn Fetch( ) -> Rc { let core_resource_thread = global.core_resource_thread(); - // Step 1 + // Step 1. Let p be a new promise. let promise = Promise::new_in_current_realm(comp); - let response = Response::new(global); - // Step 2 + // Step 7. Let responseObject be null. + // NOTE: We do initialize the object earlier earlier so we can use it to track errors + let response = Response::new(global); + response.Headers().set_guard(Guard::Immutable); + + // Step 2. Let requestObject be the result of invoking the initial value of Request as constructor + // with input and init as arguments. If this throws an exception, reject p with it and return p. let request = match Request::Constructor(global, None, CanGc::note(), input, init) { Err(e) => { response.error_stream(e.clone()); promise.reject_error(e); return promise; }, - Ok(r) => r.get_request(), + Ok(r) => { + // Step 3. Let request be requestObject’s request. + r.get_request() + }, }; let timing_type = request.timing_type(); let mut request_init = request_init_from_request(request); request_init.csp_list.clone_from(&global.get_csp_list()); - // Step 3 + // TODO: Step 4. If requestObject’s signal is aborted, then: [..] + + // Step 5. Let globalObject be request’s client’s global object. + // NOTE: We already get the global object as an argument + + // Step 6. If globalObject is a ServiceWorkerGlobalScope object, then set request’s + // service-workers mode to "none". if global.downcast::().is_some() { request_init.service_workers_mode = ServiceWorkersMode::None; } - // Step 4 - response.Headers().set_guard(Guard::Immutable); + // TODO: Steps 8-11, abortcontroller stuff - // Step 5 + // Step 12. Set controller to the result of calling fetch given request and + // processResponse given response being these steps: [..] let (action_sender, action_receiver) = ipc::channel().unwrap(); let fetch_context = Arc::new(Mutex::new(FetchContext { fetch_promise: Some(TrustedPromise::new(promise.clone())), @@ -200,6 +214,7 @@ pub fn Fetch( )) .unwrap(); + // Step 13. Return p. promise } @@ -263,6 +278,7 @@ impl FetchResponseListener for FetchContext { }, }, } + // Step 4.3 promise.resolve_native(&self.response_object.root()); self.fetch_promise = Some(TrustedPromise::new(promise)); diff --git a/tests/wpt/meta/fetch/origin/assorted.window.js.ini b/tests/wpt/meta/fetch/origin/assorted.window.js.ini index 44e55849ecd..2c63d36b6cd 100644 --- a/tests/wpt/meta/fetch/origin/assorted.window.js.ini +++ b/tests/wpt/meta/fetch/origin/assorted.window.js.ini @@ -1,13 +1,4 @@ [assorted.window.html] - [Origin header and 308 redirect] - expected: FAIL - - [Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy no-referrer] - expected: FAIL - - [Origin header and POST same-origin fetch no-cors mode with Referrer-Policy no-referrer] - expected: FAIL - [Origin header and POST cross-origin navigation with Referrer-Policy origin-when-cross-origin] expected: FAIL @@ -17,17 +8,8 @@ [Origin header and POST navigation] expected: FAIL - [Origin header and POST cross-origin navigation with Referrer-Policy no-referrer] - expected: FAIL - [Origin header and POST cross-origin navigation with Referrer-Policy same-origin] expected: FAIL - [Origin header and POST cross-origin fetch no-cors mode with Referrer-Policy same-origin] - expected: FAIL - - [Origin header and POST same-origin navigation with Referrer-Policy no-referrer] - expected: FAIL - [Origin header and POST cross-origin navigation with Referrer-Policy no-referrer-when-downgrade] expected: FAIL