1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-22 19:45:08 +00:00

LibWeb: Introduce the Environment Settings Object

The environment settings object is effectively the context a piece of
script is running under, for example, it contains the origin,
responsible document, realm, global object and event loop for the
current context. This effectively replaces ScriptExecutionContext, but
it cannot be removed in this commit as EventTarget still depends on it.

https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
This commit is contained in:
Luke Wilde 2021-10-14 16:12:53 +01:00 committed by Linus Groh
parent 4db5406d62
commit f71f404e0c
29 changed files with 883 additions and 86 deletions

View file

@ -7,13 +7,16 @@
#include <AK/Debug.h>
#include <LibCore/ElapsedTimer.h>
#include <LibJS/Interpreter.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/DOM/DOMException.h>
#include <LibWeb/HTML/Scripting/ClassicScript.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-classic-script
NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView source, JS::Realm& realm, AK::URL base_url, MutedErrors muted_errors)
NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView source, EnvironmentSettingsObject& environment_settings_object, AK::URL base_url, MutedErrors muted_errors)
{
// 1. If muted errors was not provided, let it be false. (NOTE: This is taken care of by the default argument.)
@ -24,9 +27,9 @@ NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView s
// FIXME: 3. If scripting is disabled for settings, then set source to the empty string.
// 4. Let script be a new classic script that this algorithm will subsequently initialize.
auto script = adopt_ref(*new ClassicScript(move(base_url), move(filename)));
auto script = adopt_ref(*new ClassicScript(move(base_url), move(filename), environment_settings_object));
// FIXME: 5. Set script's settings object to settings.
// 5. Set script's settings object to settings. (NOTE: This was already done when constructing.)
// 6. Set script's base URL to baseURL. (NOTE: This was already done when constructing.)
@ -36,16 +39,21 @@ NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView s
script->m_muted_errors = muted_errors;
// FIXME: 9. Set script's parse error and error to rethrow to null.
// NOTE: Error to rethrow was set to null in the construction of ClassicScript. We do not have parse error as it would currently go unused.
// 10. Let result be ParseScript(source, settings's Realm, script).
auto parse_timer = Core::ElapsedTimer::start_new();
auto result = JS::Script::parse(source, realm, script->filename());
auto result = JS::Script::parse(source, environment_settings_object.realm(), script->filename());
dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Parsed {} in {}ms", script->filename(), parse_timer.elapsed());
// 11. If result is a list of errors, then:
if (result.is_error()) {
// FIXME: 1. Set script's parse error and its error to rethrow to result[0].
auto& parse_error = result.error().first();
dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Failed to parse: {}", parse_error.to_string());
// FIXME: 1. Set script's parse error and its error to rethrow to result[0].
// We do not have parse error as it would currently go unused.
script->m_error_to_rethrow = parse_error;
// 2. Return script.
return script;
@ -59,37 +67,88 @@ NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView s
}
// https://html.spec.whatwg.org/multipage/webappapis.html#run-a-classic-script
JS::Value ClassicScript::run(RethrowErrors rethrow_errors)
JS::Completion ClassicScript::run(RethrowErrors rethrow_errors)
{
if (!m_script_record) {
// FIXME: Throw a SyntaxError per the spec.
dbgln("ClassicScript: Unable to run script {}", filename());
return {};
auto& global_object = m_settings_object.global_object();
auto& vm = global_object.vm();
// 1. Let settings be the settings object of script. (NOTE: Not necessary)
// 2. Check if we can run script with settings. If this returns "do not run" then return NormalCompletion(empty).
if (m_settings_object.can_run_script() == RunScriptDecision::DoNotRun)
return JS::normal_completion({});
// 3. Prepare to run script given settings.
m_settings_object.prepare_to_run_script();
// 4. Let evaluationStatus be null.
JS::Completion evaluation_status;
// 5. If script's error to rethrow is not null, then set evaluationStatus to Completion { [[Type]]: throw, [[Value]]: script's error to rethrow, [[Target]]: empty }.
if (m_error_to_rethrow.has_value()) {
evaluation_status = vm.throw_completion<JS::SyntaxError>(global_object, m_error_to_rethrow.value().to_string());
} else {
auto timer = Core::ElapsedTimer::start_new();
// 6. Otherwise, set evaluationStatus to ScriptEvaluation(script's record).
auto interpreter = JS::Interpreter::create_with_existing_realm(m_script_record->realm());
evaluation_status = interpreter->run(*m_script_record);
// FIXME: If ScriptEvaluation does not complete because the user agent has aborted the running script, leave evaluationStatus as null.
dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Finished running script {}, Duration: {}ms", filename(), timer.elapsed());
}
dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Running script {}", filename());
(void)rethrow_errors;
// 7. If evaluationStatus is an abrupt completion, then:
if (evaluation_status.is_abrupt()) {
// 1. If rethrow errors is true and script's muted errors is false, then:
if (rethrow_errors == RethrowErrors::Yes && m_muted_errors == MutedErrors::No) {
// 1. Clean up after running script with settings.
m_settings_object.clean_up_after_running_script();
auto timer = Core::ElapsedTimer::start_new();
// 2. Rethrow evaluationStatus.[[Value]].
return JS::throw_completion(*evaluation_status.value());
}
// 6. Otherwise, set evaluationStatus to ScriptEvaluation(script's record).
auto interpreter = JS::Interpreter::create_with_existing_realm(m_script_record->realm());
// 2. If rethrow errors is true and script's muted errors is true, then:
if (rethrow_errors == RethrowErrors::Yes && m_muted_errors == MutedErrors::Yes) {
// 1. Clean up after running script with settings.
m_settings_object.clean_up_after_running_script();
auto evaluation_status = interpreter->run(*m_script_record);
// 2. Throw a "NetworkError" DOMException.
return Bindings::throw_dom_exception_if_needed(global_object, [] {
return DOM::NetworkError::create("Script error.");
}).release_error();
}
// FIXME: If ScriptEvaluation does not complete because the user agent has aborted the running script, leave evaluationStatus as null.
// 3. Otherwise, rethrow errors is false. Perform the following steps:
VERIFY(rethrow_errors == RethrowErrors::No);
dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Finished running script {}, Duration: {}ms", filename(), timer.elapsed());
if (evaluation_status.is_error()) {
// FIXME: Propagate error according to the spec.
// 1. Report the exception given by evaluationStatus.[[Value]] for script.
report_exception(evaluation_status);
return {};
// 2. Clean up after running script with settings.
m_settings_object.clean_up_after_running_script();
// 3. Return evaluationStatus.
return evaluation_status;
}
return evaluation_status.value();
// 8. Clean up after running script with settings.
m_settings_object.clean_up_after_running_script();
// 9. If evaluationStatus is a normal completion, then return evaluationStatus.
VERIFY(!evaluation_status.is_abrupt());
return evaluation_status;
// FIXME: 10. If we've reached this point, evaluationStatus was left as null because the script was aborted prematurely during evaluation.
// Return Completion { [[Type]]: throw, [[Value]]: a new "QuotaExceededError" DOMException, [[Target]]: empty }.
}
ClassicScript::ClassicScript(AK::URL base_url, String filename)
ClassicScript::ClassicScript(AK::URL base_url, String filename, EnvironmentSettingsObject& environment_settings_object)
: Script(move(base_url), move(filename))
, m_settings_object(environment_settings_object)
{
}

View file

@ -7,6 +7,7 @@
#pragma once
#include <LibJS/Script.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/Scripting/Script.h>
namespace Web::HTML {
@ -20,22 +21,26 @@ public:
No,
Yes,
};
static NonnullRefPtr<ClassicScript> create(String filename, StringView source, JS::Realm&, AK::URL base_url, MutedErrors = MutedErrors::No);
static NonnullRefPtr<ClassicScript> create(String filename, StringView source, EnvironmentSettingsObject&, AK::URL base_url, MutedErrors = MutedErrors::No);
JS::Script* script_record() { return m_script_record; }
JS::Script const* script_record() const { return m_script_record; }
EnvironmentSettingsObject& settings_object() { return m_settings_object; }
enum class RethrowErrors {
No,
Yes,
};
JS::Value run(RethrowErrors = RethrowErrors::No);
JS::Completion run(RethrowErrors = RethrowErrors::No);
private:
ClassicScript(AK::URL base_url, String filename);
ClassicScript(AK::URL base_url, String filename, EnvironmentSettingsObject& environment_settings_object);
EnvironmentSettingsObject& m_settings_object;
RefPtr<JS::Script> m_script_record;
MutedErrors m_muted_errors { MutedErrors::No };
Optional<JS::Parser::Error> m_error_to_rethrow;
};
}

View file

@ -0,0 +1,273 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Window.h>
#include <LibWeb/HTML/PromiseRejectionEvent.h>
#include <LibWeb/HTML/Scripting/Environments.h>
namespace Web::HTML {
EnvironmentSettingsObject::EnvironmentSettingsObject(JS::ExecutionContext& realm_execution_context)
: m_realm_execution_context(realm_execution_context)
{
// Register with the responsible event loop so we can perform step 4 of "perform a microtask checkpoint".
responsible_event_loop().register_environment_settings_object({}, *this);
}
EnvironmentSettingsObject::~EnvironmentSettingsObject()
{
responsible_event_loop().unregister_environment_settings_object({}, *this);
}
JS::ExecutionContext& EnvironmentSettingsObject::realm_execution_context()
{
// NOTE: All environment settings objects are created with a realm execution context, so it's stored and returned here in the base class.
return m_realm_execution_context;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object%27s-realm
JS::Realm& EnvironmentSettingsObject::realm()
{
// An environment settings object's realm execution context's Realm component is the environment settings object's Realm.
return *realm_execution_context().realm;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-global
JS::GlobalObject& EnvironmentSettingsObject::global_object()
{
// An environment settings object's Realm then has a [[GlobalObject]] field, which contains the environment settings object's global object.
return realm().global_object();
}
// https://html.spec.whatwg.org/multipage/webappapis.html#responsible-event-loop
EventLoop& EnvironmentSettingsObject::responsible_event_loop()
{
// An environment settings object's responsible event loop is its global object's relevant agent's event loop.
// This is here in case the realm that is holding onto this ESO is destroyed before the ESO is. The responsible event loop pointer is needed in the ESO destructor to deregister from the event loop.
// FIXME: Figure out why the realm can be destroyed before the ESO, as the realm is holding onto this with an OwnPtr, but the heap block deallocator calls the ESO destructor directly instead of through the realm destructor.
if (m_responsible_event_loop)
return *m_responsible_event_loop;
auto& vm = global_object().vm();
auto& event_loop = verify_cast<Bindings::WebEngineCustomData>(vm.custom_data())->event_loop;
m_responsible_event_loop = &event_loop;
return event_loop;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#check-if-we-can-run-script
RunScriptDecision EnvironmentSettingsObject::can_run_script()
{
// 1. If the global object specified by settings is a Window object whose Document object is not fully active, then return "do not run".
if (is<Bindings::WindowObject>(global_object()) && !verify_cast<Bindings::WindowObject>(global_object()).impl().associated_document().is_fully_active())
return RunScriptDecision::DoNotRun;
// FIXME: 2. If scripting is disabled for settings, then return "do not run".
// 3. Return "run".
return RunScriptDecision::Run;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-script
void EnvironmentSettingsObject::prepare_to_run_script()
{
// 1. Push settings's realm execution context onto the JavaScript execution context stack; it is now the running JavaScript execution context.
global_object().vm().push_execution_context(realm_execution_context(), global_object());
// FIXME: 2. Add settings to the currently running task's script evaluation environment settings object set.
}
// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script
void EnvironmentSettingsObject::clean_up_after_running_script()
{
auto& vm = global_object().vm();
// 1. Assert: settings's realm execution context is the running JavaScript execution context.
VERIFY(&realm_execution_context() == &vm.running_execution_context());
// 2. Remove settings's realm execution context from the JavaScript execution context stack.
vm.pop_execution_context();
// 3. If the JavaScript execution context stack is now empty, perform a microtask checkpoint. (If this runs scripts, these algorithms will be invoked reentrantly.)
if (vm.execution_context_stack().is_empty())
responsible_event_loop().perform_a_microtask_checkpoint();
}
static JS::ExecutionContext* top_most_script_having_execution_context(JS::VM& vm)
{
// Here, the topmost script-having execution context is the topmost entry of the JavaScript execution context stack that has a non-null ScriptOrModule component,
// or null if there is no such entry in the JavaScript execution context stack.
auto execution_context = vm.execution_context_stack().last_matching([&](JS::ExecutionContext* context) {
return !context->script_or_module.has<Empty>();
});
if (!execution_context.has_value())
return nullptr;
return execution_context.value();
}
// https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-a-callback
void EnvironmentSettingsObject::prepare_to_run_callback()
{
auto& vm = global_object().vm();
// 1. Push settings onto the backup incumbent settings object stack.
// NOTE: The spec doesn't say which event loop's stack to put this on. However, all the examples of the incumbent settings object use iframes and cross browsing context communication to demonstrate the concept.
// This means that it must rely on some global state that can be accessed by all browsing contexts, which is the main thread event loop.
HTML::main_thread_event_loop().push_onto_backup_incumbent_settings_object_stack({}, *this);
// 2. Let context be the topmost script-having execution context.
auto* context = top_most_script_having_execution_context(vm);
// 3. If context is not null, increment context's skip-when-determining-incumbent counter.
if (context)
context->skip_when_determining_incumbent_counter++;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-a-callback
void EnvironmentSettingsObject::clean_up_after_running_callback()
{
auto& vm = global_object().vm();
// 1. Let context be the topmost script-having execution context.
auto* context = top_most_script_having_execution_context(vm);
// 2. If context is not null, decrement context's skip-when-determining-incumbent counter.
if (context)
context->skip_when_determining_incumbent_counter--;
// 3. Assert: the topmost entry of the backup incumbent settings object stack is settings.
auto& event_loop = HTML::main_thread_event_loop();
VERIFY(&event_loop.top_of_backup_incumbent_settings_object_stack() == this);
// 4. Remove settings from the backup incumbent settings object stack.
event_loop.pop_backup_incumbent_settings_object_stack({});
}
void EnvironmentSettingsObject::push_onto_outstanding_rejected_promises_weak_set(JS::Promise* promise)
{
m_outstanding_rejected_promises_weak_set.append(promise);
}
bool EnvironmentSettingsObject::remove_from_outstanding_rejected_promises_weak_set(JS::Promise* promise)
{
return m_outstanding_rejected_promises_weak_set.remove_first_matching([&](JS::Promise* promise_in_set) {
return promise == promise_in_set;
});
}
void EnvironmentSettingsObject::push_onto_about_to_be_notified_rejected_promises_list(JS::Handle<JS::Promise> promise)
{
m_about_to_be_notified_rejected_promises_list.append(move(promise));
}
bool EnvironmentSettingsObject::remove_from_about_to_be_notified_rejected_promises_list(JS::Promise* promise)
{
return m_about_to_be_notified_rejected_promises_list.remove_first_matching([&](JS::Handle<JS::Promise> promise_in_list) {
return promise == promise_in_list.cell();
});
}
// https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
void EnvironmentSettingsObject::notify_about_rejected_promises(Badge<EventLoop>)
{
// 1. Let list be a copy of settings object's about-to-be-notified rejected promises list.
auto list = m_about_to_be_notified_rejected_promises_list;
// 2. If list is empty, return.
if (list.is_empty())
return;
// 3. Clear settings object's about-to-be-notified rejected promises list.
m_about_to_be_notified_rejected_promises_list.clear();
// 4. Let global be settings object's global object.
auto& global = global_object();
// 5. Queue a global task on the DOM manipulation task source given global to run the following substep:
queue_global_task(Task::Source::DOMManipulation, global, [this, global = JS::make_handle(&global), list = move(list)]() mutable {
// 1. For each promise p in list:
for (auto promise_handle : list) {
auto& promise = *promise_handle.cell();
// 1. If p's [[PromiseIsHandled]] internal slot is true, continue to the next iteration of the loop.
if (promise.is_handled())
continue;
// 2. Let notHandled be the result of firing an event named unhandledrejection at global, using PromiseRejectionEvent, with the cancelable attribute initialized to true,
// the promise attribute initialized to p, and the reason attribute initialized to the value of p's [[PromiseResult]] internal slot.
PromiseRejectionEventInit event_init {
{
.bubbles = false,
.cancelable = true,
.composed = false,
},
// Sadly we can't use .promise and .reason here, as we can't use the designator on the initialization of DOM::EventInit above.
/* .promise = */ promise_handle,
/* .reason = */ promise.result(),
};
auto promise_rejection_event = PromiseRejectionEvent::create(HTML::EventNames::unhandledrejection, event_init);
// FIXME: This currently assumes that global is a WindowObject.
auto& window = verify_cast<Bindings::WindowObject>(*global.cell());
bool not_handled = window.impl().dispatch_event(move(promise_rejection_event));
// 3. If notHandled is false, then the promise rejection is handled. Otherwise, the promise rejection is not handled.
// 4. If p's [[PromiseIsHandled]] internal slot is false, add p to settings object's outstanding rejected promises weak set.
if (!promise.is_handled())
m_outstanding_rejected_promises_weak_set.append(&promise);
// This algorithm results in promise rejections being marked as handled or not handled. These concepts parallel handled and not handled script errors.
// If a rejection is still not handled after this, then the rejection may be reported to a developer console.
if (not_handled)
dbgln("WARNING: A promise was rejected without any handlers. promise={:p}, result={}", &promise, promise.result().to_string_without_side_effects());
}
});
}
// https://html.spec.whatwg.org/multipage/webappapis.html#incumbent-settings-object
EnvironmentSettingsObject& incumbent_settings_object()
{
auto& event_loop = HTML::main_thread_event_loop();
auto& vm = event_loop.vm();
// 1. Let context be the topmost script-having execution context.
auto* context = top_most_script_having_execution_context(vm);
// 2. If context is null, or if context's skip-when-determining-incumbent counter is greater than zero, then:
if (!context || context->skip_when_determining_incumbent_counter > 0) {
// 1. Assert: the backup incumbent settings object stack is not empty.
// NOTE: If this assertion fails, it's because the incumbent settings object was used with no involvement of JavaScript.
VERIFY(!event_loop.is_backup_incumbent_settings_object_stack_empty());
// 2. Return the topmost entry of the backup incumbent settings object stack.
return event_loop.top_of_backup_incumbent_settings_object_stack();
}
// 3. Return context's Realm component's settings object.
return verify_cast<EnvironmentSettingsObject>(*context->realm->host_defined());
}
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-incumbent-realm
JS::Realm& incumbent_realm()
{
// Then, the incumbent Realm is the Realm of the incumbent settings object.
return incumbent_settings_object().realm();
}
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-incumbent-global
JS::GlobalObject& incumbent_global_object()
{
// Similarly, the incumbent global object is the global object of the incumbent settings object.
return incumbent_settings_object().global_object();
}
}

View file

@ -0,0 +1,121 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/URL.h>
#include <LibJS/Runtime/ExecutionContext.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/Origin.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/webappapis.html#environment
struct Environment {
// FIXME: An id https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-id
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-creation-url
AK::URL creation_url;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-creation-url
AK::URL top_level_creation_url;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin
Origin top_level_origin;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-target-browsing-context
RefPtr<BrowsingContext> target_browsing_context;
// FIXME: An active service worker https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-active-service-worker
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-execution-ready-flag
bool execution_ready { false };
};
enum class CanUseCrossOriginIsolatedAPIs {
No,
Yes,
};
enum class RunScriptDecision {
Run,
DoNotRun,
};
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
struct EnvironmentSettingsObject
: public Environment
, public JS::Realm::HostDefined {
virtual ~EnvironmentSettingsObject() override;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-target-browsing-context
JS::ExecutionContext& realm_execution_context();
// FIXME: A module map https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-module-map
// https://html.spec.whatwg.org/multipage/webappapis.html#responsible-document
virtual RefPtr<DOM::Document> responsible_document() = 0;
// https://html.spec.whatwg.org/multipage/webappapis.html#api-url-character-encoding
virtual String api_url_character_encoding() = 0;
// https://html.spec.whatwg.org/multipage/webappapis.html#api-base-url
virtual AK::URL api_base_url() = 0;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin
virtual Origin origin() = 0;
// FIXME: A policy container https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-policy-container
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-cross-origin-isolated-capability
virtual CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() = 0;
JS::Realm& realm();
JS::GlobalObject& global_object();
EventLoop& responsible_event_loop();
RunScriptDecision can_run_script();
void prepare_to_run_script();
void clean_up_after_running_script();
void prepare_to_run_callback();
void clean_up_after_running_callback();
void push_onto_outstanding_rejected_promises_weak_set(JS::Promise*);
// Returns true if removed, false otherwise.
bool remove_from_outstanding_rejected_promises_weak_set(JS::Promise*);
void push_onto_about_to_be_notified_rejected_promises_list(JS::Handle<JS::Promise>);
// Returns true if removed, false otherwise.
bool remove_from_about_to_be_notified_rejected_promises_list(JS::Promise*);
void notify_about_rejected_promises(Badge<EventLoop>);
protected:
explicit EnvironmentSettingsObject(JS::ExecutionContext& realm_execution_context);
private:
JS::ExecutionContext& m_realm_execution_context;
EventLoop* m_responsible_event_loop { nullptr };
// https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set
// The outstanding rejected promises weak set must not create strong references to any of its members, and implementations are free to limit its size, e.g. by removing old entries from it when new ones are added.
Vector<JS::Promise*> m_outstanding_rejected_promises_weak_set;
// https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
Vector<JS::Handle<JS::Promise>> m_about_to_be_notified_rejected_promises_list;
};
EnvironmentSettingsObject& incumbent_settings_object();
JS::Realm& incumbent_realm();
JS::GlobalObject& incumbent_global_object();
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
namespace Web::HTML {
WindowEnvironmentSettingsObject::WindowEnvironmentSettingsObject(DOM::Window& window, JS::ExecutionContext& execution_context)
: EnvironmentSettingsObject(execution_context)
, m_window(window)
{
}
// https://html.spec.whatwg.org/multipage/window-object.html#set-up-a-window-environment-settings-object
void WindowEnvironmentSettingsObject::setup(AK::URL& creation_url, JS::ExecutionContext& execution_context)
{
// 1. Let realm be the value of execution context's Realm component.
auto* realm = execution_context.realm;
VERIFY(realm);
// 2. Let window be realm's global object.
// NOTE: We want to store the Window impl rather than the WindowObject.
auto& window = verify_cast<Bindings::WindowObject>(realm->global_object()).impl();
// 3. Let settings object be a new environment settings object whose algorithms are defined as follows:
// NOTE: See the functions defined for this class.
auto settings_object = adopt_own(*new WindowEnvironmentSettingsObject(window, execution_context));
// FIXME: 4. If reservedEnvironment is non-null, then:
// FIXME: 1. Set settings object's id to reservedEnvironment's id, target browsing context to reservedEnvironment's target browsing context, and active service worker to reservedEnvironment's active service worker.
// FIXME: 2. Set reservedEnvironment's id to the empty string.
// FIXME: 5. Otherwise, set settings object's id to a new unique opaque string, settings object's target browsing context to null, and settings object's active service worker to null.
settings_object->target_browsing_context = nullptr;
// FIXME: 6. Set settings object's creation URL to creationURL, settings object's top-level creation URL to topLevelCreationURL, and settings object's top-level origin to topLevelOrigin.
settings_object->creation_url = creation_url;
// 7. Set realm's [[HostDefined]] field to settings object.
realm->set_host_defined(move(settings_object));
}
// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:responsible-document
RefPtr<DOM::Document> WindowEnvironmentSettingsObject::responsible_document()
{
// Return window's associated Document.
return m_window->associated_document();
}
// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:api-url-character-encoding
String WindowEnvironmentSettingsObject::api_url_character_encoding()
{
// Return the current character encoding of window's associated Document.
return m_window->associated_document().encoding_or_default();
}
// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:api-base-url
AK::URL WindowEnvironmentSettingsObject::api_base_url()
{
// FIXME: Return the current base URL of window's associated Document.
// (This currently just returns the current document URL, not accounting for <base> elements and such)
return m_window->associated_document().url();
}
// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:concept-settings-object-origin
Origin WindowEnvironmentSettingsObject::origin()
{
// Return the origin of window's associated Document.
return m_window->associated_document().origin();
}
// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:concept-settings-object-cross-origin-isolated-capability
CanUseCrossOriginIsolatedAPIs WindowEnvironmentSettingsObject::cross_origin_isolated_capability()
{
// FIXME: Return true if both of the following hold, and false otherwise:
// 1. realm's agent cluster's cross-origin-isolation mode is "concrete", and
// 2. window's associated Document is allowed to use the "cross-origin-isolated" feature.
TODO();
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/DOM/Window.h>
#include <LibWeb/HTML/Scripting/Environments.h>
namespace Web::HTML {
class WindowEnvironmentSettingsObject final : public EnvironmentSettingsObject {
public:
static void setup(AK::URL& creation_url, JS::ExecutionContext& execution_context /* FIXME: null or an environment reservedEnvironment, a URL topLevelCreationURL, and an origin topLevelOrigin */);
virtual ~WindowEnvironmentSettingsObject() override = default;
virtual RefPtr<DOM::Document> responsible_document() override;
virtual String api_url_character_encoding() override;
virtual AK::URL api_base_url() override;
virtual Origin origin() override;
virtual CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() override;
private:
WindowEnvironmentSettingsObject(DOM::Window&, JS::ExecutionContext& execution_context);
NonnullRefPtr<DOM::Window> m_window;
};
}