瀏覽代碼

LibWeb: Implement the fetch a classic script AO

Note that this unfortunately requires the same workaround as <link>
elements to handle CORS cross-origin responses.
Timothy Flynn 2 年之前
父節點
當前提交
12976b74ca

+ 130 - 0
Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp

@@ -6,6 +6,16 @@
 
 #include <AK/URLParser.h>
 #include <LibJS/Runtime/ModuleRequest.h>
+#include <LibTextCodec/Decoder.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/Fetch/Fetching/Fetching.h>
+#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
+#include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h>
+#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
+#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
+#include <LibWeb/HTML/HTMLScriptElement.h>
+#include <LibWeb/HTML/PotentialCORSRequest.h>
+#include <LibWeb/HTML/Scripting/ClassicScript.h>
 #include <LibWeb/HTML/Scripting/Environments.h>
 #include <LibWeb/HTML/Scripting/Fetching.h>
 #include <LibWeb/HTML/Scripting/ModuleScript.h>
@@ -210,6 +220,126 @@ Optional<AK::URL> resolve_url_like_module_specifier(DeprecatedString const& spec
     return url;
 }
 
+// https://html.spec.whatwg.org/multipage/webappapis.html#set-up-the-classic-script-request
+static void set_up_classic_script_request(Fetch::Infrastructure::Request& request, ScriptFetchOptions const& options)
+{
+    // Set request's cryptographic nonce metadata to options's cryptographic nonce, its integrity metadata to options's
+    // integrity metadata, its parser metadata to options's parser metadata, its referrer policy to options's referrer
+    // policy, its render-blocking to options's render-blocking, and its priority to options's fetch priority.
+    request.set_cryptographic_nonce_metadata(options.cryptographic_nonce);
+    request.set_integrity_metadata(options.integrity_metadata);
+    request.set_parser_metadata(options.parser_metadata);
+    request.set_referrer_policy(options.referrer_policy);
+    request.set_render_blocking(options.render_blocking);
+    request.set_priority(options.fetch_priority);
+}
+
+class ClassicScriptResponseHandler final : public RefCounted<ClassicScriptResponseHandler> {
+public:
+    ClassicScriptResponseHandler(JS::NonnullGCPtr<HTMLScriptElement> 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<Fetch::Infrastructure::Response> 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<Empty>() || body_bytes.template has<Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag>() || !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<ByteBuffer>()).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<HTMLScriptElement> m_element;
+    JS::Handle<EnvironmentSettingsObject> 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<void> fetch_classic_script(JS::NonnullGCPtr<HTMLScriptElement> element, AK::URL const& url, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete)
+{
+    auto& realm = element->realm();
+    auto& vm = realm.vm();
+
+    // 1. Let request be the result of creating a potential-CORS request given url, "script", and CORS setting.
+    auto request = create_potential_CORS_request(vm, url, Fetch::Infrastructure::Request::Destination::Script, cors_setting);
+
+    // 2. Set request's client to settings object.
+    request->set_client(&settings_object);
+
+    // 3. Set request's initiator type to "script".
+    request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Script);
+
+    // 4. Set up the classic script request given request and options.
+    set_up_classic_script_request(*request, options);
+
+    // 5. Fetch request with the following processResponseConsumeBody steps given response response and null, failure,
+    //    or a byte sequence bodyBytes:
+    auto response_handler = make_ref_counted<ClassicScriptResponseHandler>(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<Empty>() && 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 {});
+            };
+
+            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));
+        }
+    };
+
+    TRY(Fetch::Fetching::fetch(element->realm(), request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input))));
+    return {};
+}
+
 // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure
 void fetch_internal_module_script_graph(JS::ModuleRequest const& module_request, EnvironmentSettingsObject& fetch_client_settings_object, StringView destination, Script& referring_script, HashTable<ModuleLocationTuple> const& visited_set, OnFetchScriptComplete on_complete)
 {

+ 28 - 0
Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h

@@ -6,14 +6,41 @@
 
 #pragma once
 
+#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
+#include <LibWeb/HTML/CORSSettingAttribute.h>
 #include <LibWeb/HTML/Scripting/ImportMap.h>
 #include <LibWeb/HTML/Scripting/ModuleMap.h>
 #include <LibWeb/HTML/Scripting/ModuleScript.h>
+#include <LibWeb/ReferrerPolicy/ReferrerPolicy.h>
 
 namespace Web::HTML {
 
 using OnFetchScriptComplete = JS::SafeFunction<void(JS::GCPtr<Script>)>;
 
+// https://html.spec.whatwg.org/multipage/webappapis.html#script-fetch-options
+struct ScriptFetchOptions {
+    // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-nonce
+    String cryptographic_nonce {};
+
+    // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-integrity
+    String integrity_metadata {};
+
+    // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-parser
+    Optional<Fetch::Infrastructure::Request::ParserMetadata> parser_metadata;
+
+    // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-credentials
+    Fetch::Infrastructure::Request::CredentialsMode credentials_mode { Fetch::Infrastructure::Request::CredentialsMode::SameOrigin };
+
+    // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-referrer-policy
+    Optional<ReferrerPolicy::ReferrerPolicy> referrer_policy;
+
+    // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-render-blocking
+    bool render_blocking { false };
+
+    // https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-fetch-options-fetch-priority
+    Fetch::Infrastructure::Request::Priority fetch_priority {};
+};
+
 class DescendantFetchingContext : public RefCounted<DescendantFetchingContext> {
 public:
     static NonnullRefPtr<DescendantFetchingContext> create() { return adopt_ref(*new DescendantFetchingContext); }
@@ -44,6 +71,7 @@ WebIDL::ExceptionOr<AK::URL> resolve_module_specifier(Optional<Script&> referrin
 WebIDL::ExceptionOr<Optional<AK::URL>> resolve_imports_match(DeprecatedString const& normalized_specifier, Optional<AK::URL> as_url, ModuleSpecifierMap const&);
 Optional<AK::URL> resolve_url_like_module_specifier(DeprecatedString const& specifier, AK::URL const& base_url);
 
+WebIDL::ExceptionOr<void> fetch_classic_script(JS::NonnullGCPtr<HTMLScriptElement>, AK::URL const&, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete);
 void fetch_internal_module_script_graph(JS::ModuleRequest const& module_request, EnvironmentSettingsObject& fetch_client_settings_object, StringView destination, Script& referring_script, HashTable<ModuleLocationTuple> const& visited_set, OnFetchScriptComplete on_complete);
 void fetch_external_module_script_graph(AK::URL const&, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete);
 void fetch_inline_module_script_graph(DeprecatedString const& filename, DeprecatedString const& source_text, AK::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete);