1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 09:24:57 +00:00

LibWeb: Propagate Realm instead of VM more through Fetch

This makes Fetch rely less on using main_thread_vm().current_realm(),
which relies on the dummy execution context if no JavaScript is
currently running.
This commit is contained in:
Luke Wilde 2023-02-28 17:45:49 +00:00 committed by Linus Groh
parent f7ff1fd985
commit 9acc542059
19 changed files with 62 additions and 49 deletions

View file

@ -132,7 +132,7 @@ WebIDL::ExceptionOr<JS::Value> package_data(JS::Realm& realm, ByteBuffer bytes,
}
case PackageDataType::JSON:
// Return the result of running parse JSON from bytes on bytes.
return Infra::parse_json_bytes_to_javascript_value(vm, bytes);
return Infra::parse_json_bytes_to_javascript_value(realm, bytes);
case PackageDataType::Text:
// Return the result of running UTF-8 decode on bytes.
return JS::PrimitiveString::create(vm, TRY_OR_THROW_OOM(vm, String::from_utf8(bytes)));

View file

@ -8,6 +8,7 @@
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/HostDefined.h>
#include <LibWeb/DOM/AbortSignal.h>
#include <LibWeb/Fetch/FetchMethod.h>
#include <LibWeb/Fetch/Fetching/Fetching.h>
@ -36,7 +37,7 @@ JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, R
if (exception_or_request_object.is_exception()) {
// FIXME: We should probably make this a public API?
auto throw_completion = Bindings::Detail::dom_exception_to_throw_completion(vm, exception_or_request_object.release_error());
WebIDL::reject_promise(vm, promise_capability, *throw_completion.value());
WebIDL::reject_promise(realm, promise_capability, *throw_completion.value());
return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
}
auto request_object = exception_or_request_object.release_value();
@ -47,7 +48,7 @@ JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, R
// 4. If requestObjects signal is aborted, then:
if (request_object->signal()->aborted()) {
// 1. Abort the fetch() call with p, request, null, and requestObjects signals abort reason.
abort_fetch(vm, promise_capability, request, nullptr, request_object->signal()->reason());
abort_fetch(realm, promise_capability, request, nullptr, request_object->signal()->reason());
// 2. Return p.
return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
@ -79,11 +80,21 @@ JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, R
// 12. Set controller to the result of calling fetch given request and processResponse given response being these
// steps:
auto process_response = [&vm, locally_aborted, promise_capability, request, response_object_handle, &relevant_realm](JS::NonnullGCPtr<Infrastructure::Response> response) mutable {
auto process_response = [locally_aborted, promise_capability, request, response_object_handle, &relevant_realm](JS::NonnullGCPtr<Infrastructure::Response> response) mutable {
// 1. If locallyAborted is true, then abort these steps.
if (locally_aborted->value())
return;
// NOTE: Not part of the spec, but we need to have an execution context on the stack to call native functions.
// (In this case, Promise functions)
auto& environment_settings_object = Bindings::host_defined_environment_settings_object(relevant_realm);
environment_settings_object.prepare_to_run_script();
ScopeGuard guard = [&]() {
// See above NOTE.
environment_settings_object.clean_up_after_running_script();
};
// 2. If responses aborted flag is set, then:
if (response->aborted()) {
// FIXME: 1. Let deserializedError be the result of deserialize a serialized abort reason given controllers
@ -91,7 +102,7 @@ JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, R
auto deserialized_error = JS::js_undefined();
// 2. Abort the fetch() call with p, request, responseObject, and deserializedError.
abort_fetch(vm, promise_capability, request, response_object_handle.cell(), deserialized_error);
abort_fetch(relevant_realm, promise_capability, request, response_object_handle.cell(), deserialized_error);
// 3. Abort these steps.
return;
@ -100,7 +111,7 @@ JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, R
// 3. If response is a network error, then reject p with a TypeError and abort these steps.
if (response->is_network_error()) {
auto message = response->network_error_message().value_or("Response is a network error"sv);
WebIDL::reject_promise(vm, promise_capability, JS::TypeError::create(relevant_realm, message).release_allocated_value_but_fixme_should_propagate_errors());
WebIDL::reject_promise(relevant_realm, promise_capability, JS::TypeError::create(relevant_realm, message).release_allocated_value_but_fixme_should_propagate_errors());
return;
}
@ -110,7 +121,7 @@ JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, R
response_object_handle = JS::make_handle(response_object);
// 5. Resolve p with responseObject.
WebIDL::resolve_promise(vm, promise_capability, response_object);
WebIDL::resolve_promise(relevant_realm, promise_capability, response_object);
};
controller = MUST(Fetching::fetch(
realm,
@ -126,7 +137,7 @@ JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, R
})));
// 11. Add the following abort steps to requestObjects signal:
request_object->signal()->add_abort_algorithm([&vm, locally_aborted, request, controller, promise_capability_handle = JS::make_handle(*promise_capability), request_object_handle = JS::make_handle(*request_object), response_object_handle] {
request_object->signal()->add_abort_algorithm([locally_aborted, request, controller, promise_capability_handle = JS::make_handle(*promise_capability), request_object_handle = JS::make_handle(*request_object), response_object_handle, &relevant_realm] {
dbgln_if(WEB_FETCH_DEBUG, "Fetch: Request object signal's abort algorithm called");
auto& promise_capability = *promise_capability_handle;
@ -140,10 +151,10 @@ JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, R
VERIFY(controller);
// 3. Abort controller with requestObjects signals abort reason.
controller->abort(vm, request_object.signal()->reason());
controller->abort(relevant_realm, request_object.signal()->reason());
// 4. Abort the fetch() call with p, request, responseObject, and requestObjects signals abort reason.
abort_fetch(vm, promise_capability, request, response_object, request_object.signal()->reason());
abort_fetch(relevant_realm, promise_capability, request, response_object, request_object.signal()->reason());
});
// 13. Return p.
@ -151,13 +162,13 @@ JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, R
}
// https://fetch.spec.whatwg.org/#abort-fetch
void abort_fetch(JS::VM& vm, WebIDL::Promise const& promise, JS::NonnullGCPtr<Infrastructure::Request> request, JS::GCPtr<Response> response_object, JS::Value error)
void abort_fetch(JS::Realm& realm, WebIDL::Promise const& promise, JS::NonnullGCPtr<Infrastructure::Request> request, JS::GCPtr<Response> response_object, JS::Value error)
{
dbgln_if(WEB_FETCH_DEBUG, "Fetch: Aborting fetch with: request @ {}, error = {}", request.ptr(), error);
// 1. Reject promise with error.
// NOTE: This is a no-op if promise has already fulfilled.
WebIDL::reject_promise(vm, promise, error);
WebIDL::reject_promise(realm, promise, error);
// 2. If requests body is non-null and is readable, then cancel requests body with error.
if (auto* body = request->body().get_pointer<Infrastructure::Body>(); body != nullptr && body->stream()->is_readable()) {

View file

@ -15,6 +15,6 @@
namespace Web::Fetch {
JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM&, RequestInfo const& input, RequestInit const& init = {});
void abort_fetch(JS::VM&, WebIDL::Promise const&, JS::NonnullGCPtr<Infrastructure::Request>, JS::GCPtr<Response>, JS::Value error);
void abort_fetch(JS::Realm&, WebIDL::Promise const&, JS::NonnullGCPtr<Infrastructure::Request>, JS::GCPtr<Response>, JS::Value error);
}

View file

@ -759,7 +759,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<PendingResponse>> http_fetch(JS::Realm& rea
// 4. If requests service-workers mode is "all", then:
if (request->service_workers_mode() == Infrastructure::Request::ServiceWorkersMode::All) {
// 1. Let requestForServiceWorker be a clone of request.
auto request_for_service_worker = TRY(request->clone(vm));
auto request_for_service_worker = TRY(request->clone(realm));
// 2. If requestForServiceWorkers body is non-null, then:
if (!request_for_service_worker->body().has<Empty>()) {
@ -1147,7 +1147,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<PendingResponse>> http_network_or_cache_fet
// NOTE: Implementations are encouraged to avoid teeing requests bodys stream when requests bodys
// source is null as only a single body is needed in that case. E.g., when requests bodys source
// is null, redirects and authentication will end up failing the fetch.
http_request = TRY(request->clone(vm));
http_request = TRY(request->clone(realm));
// 2. Set httpFetchParams to a copy of fetchParams.
// 3. Set httpFetchParamss request to httpRequest.

View file

@ -55,10 +55,8 @@ JS::NonnullGCPtr<FetchTimingInfo> FetchController::extract_full_timing_info() co
}
// https://fetch.spec.whatwg.org/#fetch-controller-abort
void FetchController::abort(JS::VM& vm, Optional<JS::Value> error)
void FetchController::abort(JS::Realm& realm, Optional<JS::Value> error)
{
auto& realm = *vm.current_realm();
// 1. Set controllers state to "aborted".
m_state = State::Aborted;

View file

@ -36,7 +36,7 @@ public:
void report_timing(JS::Object const&) const;
void process_next_manual_redirect() const;
[[nodiscard]] JS::NonnullGCPtr<FetchTimingInfo> extract_full_timing_info() const;
void abort(JS::VM&, Optional<JS::Value>);
void abort(JS::Realm&, Optional<JS::Value>);
void terminate();
private:

View file

@ -25,16 +25,12 @@ Body::Body(JS::Handle<Streams::ReadableStream> stream, SourceType source, Option
}
// https://fetch.spec.whatwg.org/#concept-body-clone
WebIDL::ExceptionOr<Body> Body::clone() const
WebIDL::ExceptionOr<Body> Body::clone(JS::Realm& realm) const
{
// To clone a body body, run these steps:
auto& vm = Bindings::main_thread_vm();
auto& realm = *vm.current_realm();
// FIXME: 1. Let « out1, out2 » be the result of teeing bodys stream.
// FIXME: 2. Set bodys stream to out1.
auto out2 = MUST_OR_THROW_OOM(vm.heap().allocate<Streams::ReadableStream>(realm, realm));
auto out2 = MUST_OR_THROW_OOM(realm.heap().allocate<Streams::ReadableStream>(realm, realm));
// 3. Return a body whose stream is out2 and other members are copied from body.
return Body { JS::make_handle(out2), m_source, m_length };

View file

@ -31,7 +31,7 @@ public:
[[nodiscard]] SourceType const& source() const { return m_source; }
[[nodiscard]] Optional<u64> const& length() const { return m_length; }
WebIDL::ExceptionOr<Body> clone() const;
WebIDL::ExceptionOr<Body> clone(JS::Realm&) const;
WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> fully_read_as_promise() const;

View file

@ -195,9 +195,10 @@ ErrorOr<ByteBuffer> Request::byte_serialize_origin() const
}
// https://fetch.spec.whatwg.org/#concept-request-clone
WebIDL::ExceptionOr<JS::NonnullGCPtr<Request>> Request::clone(JS::VM& vm) const
WebIDL::ExceptionOr<JS::NonnullGCPtr<Request>> Request::clone(JS::Realm& realm) const
{
// To clone a request request, run these steps:
auto& vm = realm.vm();
// 1. Let newRequest be a copy of request, except for its body.
auto new_request = Infrastructure::Request::create(vm);
@ -242,7 +243,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Request>> Request::clone(JS::VM& vm) const
// 2. If requests body is non-null, set newRequests body to the result of cloning requests body.
if (auto const* body = m_body.get_pointer<Body>())
new_request->set_body(TRY(body->clone()));
new_request->set_body(TRY(body->clone(realm)));
// 3. Return newRequest.
return new_request;

View file

@ -297,7 +297,7 @@ public:
[[nodiscard]] ErrorOr<String> serialize_origin() const;
[[nodiscard]] ErrorOr<ByteBuffer> byte_serialize_origin() const;
[[nodiscard]] WebIDL::ExceptionOr<JS::NonnullGCPtr<Request>> clone(JS::VM&) const;
[[nodiscard]] WebIDL::ExceptionOr<JS::NonnullGCPtr<Request>> clone(JS::Realm&) const;
[[nodiscard]] ErrorOr<void> add_range_header(u64 first, Optional<u64> const& last);
[[nodiscard]] ErrorOr<void> add_origin_header();

View file

@ -127,13 +127,14 @@ ErrorOr<Optional<AK::URL>> Response::location_url(Optional<String> const& reques
}
// https://fetch.spec.whatwg.org/#concept-response-clone
WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::clone(JS::VM& vm) const
WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::clone(JS::Realm& realm) const
{
// To clone a response response, run these steps:
auto& vm = realm.vm();
// 1. If response is a filtered response, then return a new identical filtered response whose internal response is a clone of responses internal response.
if (is<FilteredResponse>(*this)) {
auto internal_response = TRY(static_cast<FilteredResponse const&>(*this).internal_response()->clone(vm));
auto internal_response = TRY(static_cast<FilteredResponse const&>(*this).internal_response()->clone(realm));
if (is<BasicFilteredResponse>(*this))
return TRY_OR_THROW_OOM(vm, BasicFilteredResponse::create(vm, internal_response));
if (is<CORSFilteredResponse>(*this))
@ -164,7 +165,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::clone(JS::VM& vm) cons
// 3. If responses body is non-null, then set newResponses body to the result of cloning responses body.
if (m_body.has_value())
new_response->set_body(TRY(m_body->clone()));
new_response->set_body(TRY(m_body->clone(realm)));
// 4. Return newResponse.
return new_response;

View file

@ -106,7 +106,7 @@ public:
[[nodiscard]] Optional<AK::URL const&> url() const;
[[nodiscard]] ErrorOr<Optional<AK::URL>> location_url(Optional<String> const& request_fragment) const;
[[nodiscard]] WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> clone(JS::VM&) const;
[[nodiscard]] WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> clone(JS::Realm&) const;
// Non-standard
[[nodiscard]] Optional<StringView> network_error_message() const;

View file

@ -634,14 +634,14 @@ Bindings::RequestDuplex Request::duplex() const
// https://fetch.spec.whatwg.org/#dom-request-clone
WebIDL::ExceptionOr<JS::NonnullGCPtr<Request>> Request::clone() const
{
auto& vm = this->vm();
auto& realm = this->realm();
// 1. If this is unusable, then throw a TypeError.
if (is_unusable())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Request is unusable"sv };
// 2. Let clonedRequest be the result of cloning thiss request.
auto cloned_request = TRY(m_request->clone(vm));
auto cloned_request = TRY(m_request->clone(realm));
// 3. Let clonedRequestObject be the result of creating a Request object, given clonedRequest, thiss headerss guard, and thiss relevant Realm.
auto cloned_request_object = TRY(Request::create(HTML::relevant_realm(*this), cloned_request, m_headers->guard()));

View file

@ -286,14 +286,14 @@ JS::NonnullGCPtr<Headers> Response::headers() const
// https://fetch.spec.whatwg.org/#dom-response-clone
WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::clone() const
{
auto& vm = this->vm();
auto& realm = this->realm();
// 1. If this is unusable, then throw a TypeError.
if (is_unusable())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Response is unusable"sv };
// 2. Let clonedResponse be the result of cloning thiss response.
auto cloned_response = TRY(m_response->clone(vm));
auto cloned_response = TRY(m_response->clone(realm));
// 3. Return the result of creating a Response object, given clonedResponse, thiss headerss guard, and thiss relevant Realm.
return TRY(Response::create(HTML::relevant_realm(*this), cloned_response, m_headers->guard()));

View file

@ -15,23 +15,25 @@
namespace Web::Infra {
// https://infra.spec.whatwg.org/#parse-a-json-string-to-a-javascript-value
WebIDL::ExceptionOr<JS::Value> parse_json_string_to_javascript_value(JS::VM& vm, StringView string)
WebIDL::ExceptionOr<JS::Value> parse_json_string_to_javascript_value(JS::Realm& realm, StringView string)
{
auto& realm = *vm.current_realm();
auto& vm = realm.vm();
// 1. Return ? Call(%JSON.parse%, undefined, « string »).
return TRY(JS::call(vm, realm.intrinsics().json_parse_function(), JS::js_undefined(), MUST_OR_THROW_OOM(JS::PrimitiveString::create(vm, string))));
}
// https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value
WebIDL::ExceptionOr<JS::Value> parse_json_bytes_to_javascript_value(JS::VM& vm, ReadonlyBytes bytes)
WebIDL::ExceptionOr<JS::Value> parse_json_bytes_to_javascript_value(JS::Realm& realm, ReadonlyBytes bytes)
{
auto& vm = realm.vm();
// 1. Let string be the result of running UTF-8 decode on bytes.
TextCodec::UTF8Decoder decoder;
auto string = TRY_OR_THROW_OOM(vm, decoder.to_utf8(bytes));
// 2. Return the result of parsing a JSON string to an Infra value given string.
return parse_json_string_to_javascript_value(vm, string);
return parse_json_string_to_javascript_value(realm, string);
}
// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string

View file

@ -12,8 +12,8 @@
namespace Web::Infra {
WebIDL::ExceptionOr<JS::Value> parse_json_string_to_javascript_value(JS::VM&, StringView);
WebIDL::ExceptionOr<JS::Value> parse_json_bytes_to_javascript_value(JS::VM&, ReadonlyBytes);
WebIDL::ExceptionOr<JS::Value> parse_json_string_to_javascript_value(JS::Realm&, StringView);
WebIDL::ExceptionOr<JS::Value> parse_json_bytes_to_javascript_value(JS::Realm&, ReadonlyBytes);
WebIDL::ExceptionOr<String> serialize_javascript_value_to_json_string(JS::VM&, JS::Value);
WebIDL::ExceptionOr<ByteBuffer> serialize_javascript_value_to_json_bytes(JS::VM&, JS::Value);

View file

@ -10,6 +10,7 @@
#include <LibJS/Runtime/PromiseConstructor.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/HostDefined.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Promise.h>
@ -69,20 +70,23 @@ JS::NonnullGCPtr<Promise> create_rejected_promise(JS::Realm& realm, JS::Value re
}
// https://webidl.spec.whatwg.org/#resolve
void resolve_promise(JS::VM& vm, Promise const& promise, JS::Value value)
void resolve_promise(JS::Realm& realm, Promise const& promise, JS::Value value)
{
auto& vm = realm.vm();
// 1. If x is not given, then let it be the undefined value.
// NOTE: This is done via the default argument.
// 2. Let value be the result of converting x to an ECMAScript value.
// 3. Perform ! Call(p.[[Resolve]], undefined, « value »).
MUST(JS::call(vm, *promise.resolve(), JS::js_undefined(), value));
}
// https://webidl.spec.whatwg.org/#reject
void reject_promise(JS::VM& vm, Promise const& promise, JS::Value reason)
void reject_promise(JS::Realm& realm, Promise const& promise, JS::Value reason)
{
auto& vm = realm.vm();
// 1. Perform ! Call(p.[[Reject]], undefined, « r »).
MUST(JS::call(vm, *promise.reject(), JS::js_undefined(), reason));
}

View file

@ -22,8 +22,8 @@ using Promise = JS::PromiseCapability;
JS::NonnullGCPtr<Promise> create_promise(JS::Realm&);
JS::NonnullGCPtr<Promise> create_resolved_promise(JS::Realm&, JS::Value);
JS::NonnullGCPtr<Promise> create_rejected_promise(JS::Realm&, JS::Value);
void resolve_promise(JS::VM&, Promise const&, JS::Value = JS::js_undefined());
void reject_promise(JS::VM&, Promise const&, JS::Value);
void resolve_promise(JS::Realm&, Promise const&, JS::Value = JS::js_undefined());
void reject_promise(JS::Realm&, Promise const&, JS::Value);
JS::NonnullGCPtr<JS::Promise> react_to_promise(Promise const&, Optional<ReactionSteps> on_fulfilled_callback, Optional<ReactionSteps> on_rejected_callback);
JS::NonnullGCPtr<JS::Promise> upon_fulfillment(Promise const&, ReactionSteps);
JS::NonnullGCPtr<JS::Promise> upon_rejection(Promise const&, ReactionSteps);

View file

@ -182,7 +182,7 @@ WebIDL::ExceptionOr<JS::Value> XMLHttpRequest::response()
return JS::js_null();
// 3. Let jsonObject be the result of running parse JSON from bytes on thiss received bytes. If that threw an exception, then return null.
auto json_object_result = Infra::parse_json_bytes_to_javascript_value(vm, m_received_bytes);
auto json_object_result = Infra::parse_json_bytes_to_javascript_value(realm(), m_received_bytes);
if (json_object_result.is_error())
return JS::js_null();