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.
This commit is contained in:
Timothy Flynn 2023-05-10 17:03:45 -04:00 committed by Andreas Kling
parent 00fa23237a
commit 12976b74ca
Notes: sideshowbarker 2024-07-17 03:27:40 +09:00
2 changed files with 158 additions and 0 deletions

View file

@ -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)
{