diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp index 363cd05687..1dc0109084 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp @@ -492,6 +492,10 @@ after_step_6: Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; fetch_algorithms_input.process_response = [this, image_request, url_string](JS::NonnullGCPtr response) { + // FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See: + // https://github.com/whatwg/html/issues/9355 + response = response->unsafe_response(); + // 25. As soon as possible, jump to the first applicable entry from the following list: // FIXME: - If the resource type is multipart/x-mixed-replace @@ -500,30 +504,15 @@ after_step_6: // - The next task that is queued by the networking task source while the image is being fetched must run the following steps: queue_an_element_task(HTML::Task::Source::Networking, [this, response, image_request, url_string] { auto process_body = [response, image_request, url_string, this](ByteBuffer data) { - // FIXME: Another instance of the CORS cross-origin response workaround here: - auto response_with_headers = response; - if (response->is_cors_cross_origin() - && response->header_list()->is_empty() - && !response->unsafe_response()->header_list()->is_empty()) { - response_with_headers = response->unsafe_response(); - } - auto extracted_mime_type = response_with_headers->header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors(); + auto extracted_mime_type = response->header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors(); auto mime_type = extracted_mime_type.has_value() ? extracted_mime_type.value().essence().bytes_as_string_view() : StringView {}; handle_successful_fetch(url_string, mime_type, image_request, move(data)); }; auto process_body_error = [this](auto) { handle_failed_fetch(); }; - // FIXME: See HTMLLinkElement::default_fetch_and_process_linked_resource for thorough notes on the workaround - // added here for CORS cross-origin responses. The gist is that all cross-origin responses will have a - // null bodyBytes. So we must read the actual body from the unsafe response. - // https://github.com/whatwg/html/issues/9066 - if (response->is_cors_cross_origin() && !response->body().has_value() && response->unsafe_response()->body().has_value()) { - auto unsafe_response = static_cast(*response).internal_response(); - unsafe_response->body()->fully_read(realm(), move(process_body), move(process_body_error), JS::NonnullGCPtr { realm().global_object() }).release_value_but_fixme_should_propagate_errors(); - } else if (response->body().has_value()) { - response->body().value().fully_read(realm(), move(process_body), move(process_body_error), JS::NonnullGCPtr { realm().global_object() }).release_value_but_fixme_should_propagate_errors(); - } + + response->body().value().fully_read(realm(), move(process_body), move(process_body_error), JS::NonnullGCPtr { realm().global_object() }).release_value_but_fixme_should_propagate_errors(); }); }; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp index db12adb5c6..db7dfe0d35 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp @@ -279,72 +279,30 @@ void HTMLLinkElement::default_fetch_and_process_linked_resource() } // 7. Fetch request with processResponseConsumeBody set to the following steps given response response and null, failure, or a byte sequence bodyBytes: - Fetch::Fetching::fetch( - realm(), *request, - Fetch::Infrastructure::FetchAlgorithms::create(vm(), - { .process_request_body_chunk_length = {}, - .process_request_end_of_body = {}, - .process_early_hints_response = {}, - .process_response = {}, - .process_response_end_of_body = {}, - .process_response_consume_body = [this, hr = options](auto response, auto body_bytes) { - // 1. Let success be true. - bool success = true; + Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; + fetch_algorithms_input.process_response_consume_body = [this, hr = options](auto response, auto body_bytes) { + // FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See: + // https://github.com/whatwg/html/issues/9355 + response = response->unsafe_response(); - // 2. If either of the following conditions are met: - // - bodyBytes is null or failure; or - // - response's status is not an ok status, - // then set success to false. - // NOTE: content-specific errors, e.g., CSS parse errors or PNG decoding errors, do not affect success. - if (body_bytes.template has()) { - // CORS cross-origin responses in the No CORS request mode provide an opaque filtered response, which is the original response - // with certain attributes removed/changed. + // 1. Let success be true. + bool success = true; - // The relevant effect it has is setting the body to `null`, which means `body_bytes` has `Empty` here. This effectively - // disables cross-origin linked resources (e.g. stylesheets). + // 2. If either of the following conditions are met: + // - bodyBytes is null or failure; or + // - response's status is not an ok status, + if (body_bytes.template has() || body_bytes.template has() || !Fetch::Infrastructure::is_ok_status(response->status())) { + // then set success to false. + success = false; + } - // However, the web actually depends on this, especially for stylesheets retrieved from a cross-origin CDN. For example, - // Shopify websites request stylesheets from `cdn.shopify.com` and Substack websites request stylesheets from `substackcdn.com`. + // FIXME: 3. Otherwise, wait for the link resource's critical subresources to finish loading. - // This makes this a specification bug, as this code was written from it. + // 4. Process the linked resource given el, success, response, and bodyBytes. + process_linked_resource(success, response, body_bytes); + }; - // The workaround is to read the actual body from the unfiltered response and then call `process_linked_resource` from there. - - // This _should_ be safe to do, as linked resource fetches do not include credentials (i.e. cookies and the Authorization - // header), so it cannot provide personalized responses. - - // FIXME: Replace this workaround with a proper fix that has landed in the specification. - // See: https://github.com/whatwg/html/issues/9066 - if (is(response.ptr())) { - auto unsafe_response = static_cast(*response).internal_response(); - if (unsafe_response->body().has_value()) { - // NOTE: `this` and `unsafe_response` are protected by `fully_read` using JS::SafeFunction. - auto process_body = [this, unsafe_response](ByteBuffer bytes) { - process_linked_resource(true, unsafe_response, bytes); - }; - - // NOTE: `this` and `unsafe_response` are protected by `fully_read` using JS::SafeFunction. - auto process_body_error = [this, unsafe_response](auto) { - process_linked_resource(false, unsafe_response, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag {}); - }; - - unsafe_response->body()->fully_read(realm(), move(process_body), move(process_body_error), JS::NonnullGCPtr { realm().global_object() }).release_value_but_fixme_should_propagate_errors(); - return; - } else { - success = false; - } - } else { - success = false; - } - } else if (body_bytes.template has() || !Fetch::Infrastructure::is_ok_status(response->status())) { - success = false; - } - // FIXME: 3. Otherwise, wait for the link resource's critical subresources to finish loading. - - // 4. Process the linked resource given el, success, response, and bodyBytes. - process_linked_resource(success, response, body_bytes); - } })) - .release_value_but_fixme_should_propagate_errors(); + Fetch::Fetching::fetch(realm(), *request, Fetch::Infrastructure::FetchAlgorithms::create(vm(), move(fetch_algorithms_input))).release_value_but_fixme_should_propagate_errors(); } // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:process-the-linked-resource diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp index 0c4fe4483c..0ab52addac 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp @@ -840,6 +840,10 @@ WebIDL::ExceptionOr HTMLMediaElement::fetch_resource(AK::URL const& url_re fetch_algorithms_input.process_response = [this, byte_range = move(byte_range), failure_callback = move(failure_callback)](auto response) mutable { auto& realm = this->realm(); + // FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See: + // https://github.com/whatwg/html/issues/9355 + response = response->unsafe_response(); + // 1. Let global be the media element's node document's relevant global object. auto& global = document().realm().global_object(); @@ -882,14 +886,6 @@ WebIDL::ExceptionOr HTMLMediaElement::fetch_resource(AK::URL const& url_re // 5. Otherwise, incrementally read response's body given updateMedia, processEndOfMedia, an empty algorithm, and global. - // FIXME: Spec issue: If the response is CORS-cross-origin, we need to read from its internal response instead. - // https://github.com/whatwg/html/issues/3483 - // https://github.com/whatwg/html/issues/9066 - if (response->type() == Fetch::Infrastructure::Response::Type::Opaque || response->type() == Fetch::Infrastructure::Response::Type::OpaqueRedirect) { - auto& filtered_response = static_cast(*response); - response = filtered_response.internal_response(); - } - VERIFY(response->body().has_value()); auto empty_algorithm = [](auto) {}; diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp index 9bf122df4c..5171433e43 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp +++ b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp @@ -234,66 +234,6 @@ static void set_up_classic_script_request(Fetch::Infrastructure::Request& reques request.set_priority(options.fetch_priority); } -class ClassicScriptResponseHandler final : public RefCounted { -public: - ClassicScriptResponseHandler(JS::NonnullGCPtr element, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, String character_encoding, OnFetchScriptComplete on_complete) - : m_element(element) - , m_settings_object(settings_object) - , m_options(move(options)) - , m_character_encoding(move(character_encoding)) - , m_on_complete(move(on_complete)) - { - } - - // https://html.spec.whatwg.org/multipage/webappapis.html#fetching-scripts:concept-fetch-4 - void process_response(JS::NonnullGCPtr response, Fetch::Infrastructure::FetchAlgorithms::BodyBytes body_bytes) - { - // 1. Set response to response's unsafe response. - response = response->unsafe_response(); - - // 2. If either of the following conditions are met: - // - bodyBytes is null or failure; or - // - response's status is not an ok status, - if (body_bytes.template has() || body_bytes.template has() || !Fetch::Infrastructure::is_ok_status(response->status())) { - // then run onComplete given null, and abort these steps. - m_on_complete(nullptr); - return; - } - - // 3. Let potentialMIMETypeForEncoding be the result of extracting a MIME type given response's header list. - auto potential_mime_type_for_encoding = response->header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors(); - - // 4. Set character encoding to the result of legacy extracting an encoding given potentialMIMETypeForEncoding - // and character encoding. - auto character_encoding = Fetch::Infrastructure::legacy_extract_an_encoding(potential_mime_type_for_encoding, m_character_encoding); - - // 5. Let source text be the result of decoding bodyBytes to Unicode, using character encoding as the fallback - // encoding. - auto fallback_decoder = TextCodec::decoder_for(character_encoding); - VERIFY(fallback_decoder.has_value()); - - auto source_text = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*fallback_decoder, body_bytes.template get()).release_value_but_fixme_should_propagate_errors(); - - // 6. Let muted errors be true if response was CORS-cross-origin, and false otherwise. - auto muted_errors = response->is_cors_cross_origin() ? ClassicScript::MutedErrors::Yes : ClassicScript::MutedErrors::No; - - // 7. Let script be the result of creating a classic script given source text, settings object, response's URL, - // options, and muted errors. - // FIXME: Pass options. - auto script = ClassicScript::create(m_element->document().url().to_deprecated_string(), source_text, *m_settings_object, response->url().value_or({}), 1, muted_errors); - - // 8. Run onComplete given script. - m_on_complete(script); - } - -private: - JS::Handle m_element; - JS::Handle m_settings_object; - ScriptFetchOptions m_options; - String m_character_encoding; - OnFetchScriptComplete m_on_complete; -}; - // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script WebIDL::ExceptionOr fetch_classic_script(JS::NonnullGCPtr element, AK::URL const& url, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete) { @@ -314,26 +254,44 @@ WebIDL::ExceptionOr fetch_classic_script(JS::NonnullGCPtr(element, settings_object, move(options), move(character_encoding), move(on_complete)); - Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; - fetch_algorithms_input.process_response_consume_body = [&realm, response_handler = move(response_handler)](auto response, auto body_bytes) { - // FIXME: See HTMLLinkElement::default_fetch_and_process_linked_resource for thorough notes on the workaround - // added here for CORS cross-origin responses. The gist is that all cross-origin responses will have a - // null bodyBytes. So we must read the actual body from the unsafe response. - // https://github.com/whatwg/html/issues/9066 - if (response->is_cors_cross_origin() && body_bytes.template has() && response->unsafe_response()->body().has_value()) { - auto process_body = [response, response_handler](auto bytes) { - response_handler->process_response(response, move(bytes)); - }; - auto process_body_error = [response, response_handler](auto) { - response_handler->process_response(response, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag {}); - }; + fetch_algorithms_input.process_response_consume_body = [element, &settings_object, options = move(options), character_encoding = move(character_encoding), on_complete = move(on_complete)](auto response, auto body_bytes) { + // 1. Set response to response's unsafe response. + response = response->unsafe_response(); - response->unsafe_response()->body()->fully_read(realm, move(process_body), move(process_body_error), JS::NonnullGCPtr { realm.global_object() }).release_value_but_fixme_should_propagate_errors(); - } else { - response_handler->process_response(response, move(body_bytes)); + // 2. If either of the following conditions are met: + // - bodyBytes is null or failure; or + // - response's status is not an ok status, + if (body_bytes.template has() || body_bytes.template has() || !Fetch::Infrastructure::is_ok_status(response->status())) { + // then run onComplete given null, and abort these steps. + on_complete(nullptr); + return; } + + // 3. Let potentialMIMETypeForEncoding be the result of extracting a MIME type given response's header list. + auto potential_mime_type_for_encoding = response->header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors(); + + // 4. Set character encoding to the result of legacy extracting an encoding given potentialMIMETypeForEncoding + // and character encoding. + auto extracted_character_encoding = Fetch::Infrastructure::legacy_extract_an_encoding(potential_mime_type_for_encoding, character_encoding); + + // 5. Let source text be the result of decoding bodyBytes to Unicode, using character encoding as the fallback + // encoding. + auto fallback_decoder = TextCodec::decoder_for(extracted_character_encoding); + VERIFY(fallback_decoder.has_value()); + + auto source_text = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*fallback_decoder, body_bytes.template get()).release_value_but_fixme_should_propagate_errors(); + + // 6. Let muted errors be true if response was CORS-cross-origin, and false otherwise. + auto muted_errors = response->is_cors_cross_origin() ? ClassicScript::MutedErrors::Yes : ClassicScript::MutedErrors::No; + + // 7. Let script be the result of creating a classic script given source text, settings object, response's URL, + // options, and muted errors. + // FIXME: Pass options. + auto script = ClassicScript::create(element->document().url().to_deprecated_string(), source_text, settings_object, response->url().value_or({}), 1, muted_errors); + + // 8. Run onComplete given script. + on_complete(script); }; TRY(Fetch::Fetching::fetch(element->realm(), request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input))));