mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-01 21:59:07 +00:00
Everywhere: Hoist the Libraries folder to the top-level
This commit is contained in:
parent
950e819ee7
commit
93712b24bf
Notes:
github-actions[bot]
2024-11-10 11:51:52 +00:00
Author: https://github.com/trflynn89
Commit: 93712b24bf
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2256
Reviewed-by: https://github.com/sideshowbarker
4547 changed files with 104 additions and 113 deletions
166
Libraries/LibWeb/HTML/Scripting/ClassicScript.cpp
Normal file
166
Libraries/LibWeb/HTML/Scripting/ClassicScript.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Debug.h>
|
||||
#include <LibCore/ElapsedTimer.h>
|
||||
#include <LibJS/Bytecode/Interpreter.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
#include <LibWeb/HTML/Scripting/ClassicScript.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
||||
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
|
||||
#include <LibWeb/WebIDL/DOMException.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(ClassicScript);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-classic-script
|
||||
// https://whatpr.org/html/9893/webappapis.html#creating-a-classic-script
|
||||
JS::NonnullGCPtr<ClassicScript> ClassicScript::create(ByteString filename, StringView source, JS::Realm& realm, URL::URL base_url, size_t source_line_number, MutedErrors muted_errors)
|
||||
{
|
||||
auto& vm = realm.vm();
|
||||
|
||||
// 1. If muted errors is true, then set baseURL to about:blank.
|
||||
if (muted_errors == MutedErrors::Yes)
|
||||
base_url = "about:blank"sv;
|
||||
|
||||
// 2. If scripting is disabled for realm, then set source to the empty string.
|
||||
if (is_scripting_disabled(realm))
|
||||
source = ""sv;
|
||||
|
||||
// 3. Let script be a new classic script that this algorithm will subsequently initialize.
|
||||
// 4. Set script's realm to realm.
|
||||
// 5. Set script's base URL to baseURL.
|
||||
auto script = vm.heap().allocate_without_realm<ClassicScript>(move(base_url), move(filename), realm);
|
||||
|
||||
// FIXME: 6. Set script's fetch options to options.
|
||||
|
||||
// 7. Set script's muted errors to muted errors.
|
||||
script->m_muted_errors = muted_errors;
|
||||
|
||||
// 8. Set script's parse error and error to rethrow to null.
|
||||
script->set_parse_error(JS::js_null());
|
||||
script->set_error_to_rethrow(JS::js_null());
|
||||
|
||||
// FIXME: 9. Record classic script creation time given script and sourceURLForWindowScripts .
|
||||
|
||||
// 10. Let result be ParseScript(source, realm, script).
|
||||
auto parse_timer = Core::ElapsedTimer::start_new();
|
||||
auto result = JS::Script::parse(source, realm, script->filename(), script, source_line_number);
|
||||
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()) {
|
||||
auto& parse_error = result.error().first();
|
||||
dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Failed to parse: {}", parse_error.to_string());
|
||||
|
||||
// 1. Set script's parse error and its error to rethrow to result[0].
|
||||
script->set_parse_error(JS::SyntaxError::create(realm, parse_error.to_string()));
|
||||
script->set_error_to_rethrow(script->parse_error());
|
||||
|
||||
// 2. Return script.
|
||||
return script;
|
||||
}
|
||||
|
||||
// 12. Set script's record to result.
|
||||
script->m_script_record = *result.release_value();
|
||||
|
||||
// 13. Return script.
|
||||
return script;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#run-a-classic-script
|
||||
// https://whatpr.org/html/9893/webappapis.html#run-a-classic-script
|
||||
JS::Completion ClassicScript::run(RethrowErrors rethrow_errors, JS::GCPtr<JS::Environment> lexical_environment_override)
|
||||
{
|
||||
// 1. Let realm be the realm of script.
|
||||
auto& realm = this->realm();
|
||||
|
||||
// 2. Check if we can run script with realm. If this returns "do not run" then return NormalCompletion(empty).
|
||||
if (can_run_script(realm) == RunScriptDecision::DoNotRun)
|
||||
return JS::normal_completion({});
|
||||
|
||||
// 3. Prepare to run script given realm.
|
||||
prepare_to_run_script(realm);
|
||||
|
||||
// 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 (!error_to_rethrow().is_null()) {
|
||||
evaluation_status = JS::Completion { JS::Completion::Type::Throw, error_to_rethrow() };
|
||||
} else {
|
||||
auto timer = Core::ElapsedTimer::start_new();
|
||||
|
||||
// 6. Otherwise, set evaluationStatus to ScriptEvaluation(script's record).
|
||||
evaluation_status = vm().bytecode_interpreter().run(*m_script_record, lexical_environment_override);
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
// 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 realm.
|
||||
clean_up_after_running_script(realm);
|
||||
|
||||
// 2. Rethrow evaluationStatus.[[Value]].
|
||||
return JS::throw_completion(*evaluation_status.value());
|
||||
}
|
||||
|
||||
// 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 realm.
|
||||
clean_up_after_running_script(realm);
|
||||
|
||||
// 2. Throw a "NetworkError" DOMException.
|
||||
return throw_completion(WebIDL::NetworkError::create(realm, "Script error."_string));
|
||||
}
|
||||
|
||||
// 3. Otherwise, rethrow errors is false. Perform the following steps:
|
||||
VERIFY(rethrow_errors == RethrowErrors::No);
|
||||
|
||||
// 1. Report an exception given by evaluationStatus.[[Value]] for realms's global object.
|
||||
auto* window_or_worker = dynamic_cast<WindowOrWorkerGlobalScopeMixin*>(&realm.global_object());
|
||||
VERIFY(window_or_worker);
|
||||
window_or_worker->report_an_exception(*evaluation_status.value());
|
||||
|
||||
// 2. Clean up after running script with realm.
|
||||
clean_up_after_running_script(realm);
|
||||
|
||||
// 3. Return evaluationStatus.
|
||||
return evaluation_status;
|
||||
}
|
||||
|
||||
// 8. Clean up after running script with realm.
|
||||
clean_up_after_running_script(realm);
|
||||
|
||||
// 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(URL::URL base_url, ByteString filename, JS::Realm& realm)
|
||||
: Script(move(base_url), move(filename), realm)
|
||||
{
|
||||
}
|
||||
|
||||
ClassicScript::~ClassicScript() = default;
|
||||
|
||||
void ClassicScript::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_script_record);
|
||||
}
|
||||
|
||||
}
|
49
Libraries/LibWeb/HTML/Scripting/ClassicScript.h
Normal file
49
Libraries/LibWeb/HTML/Scripting/ClassicScript.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Script.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/Scripting/Script.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#classic-script
|
||||
class ClassicScript final : public Script {
|
||||
JS_CELL(ClassicScript, Script);
|
||||
JS_DECLARE_ALLOCATOR(ClassicScript);
|
||||
|
||||
public:
|
||||
virtual ~ClassicScript() override;
|
||||
|
||||
enum class MutedErrors {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
static JS::NonnullGCPtr<ClassicScript> create(ByteString filename, StringView source, JS::Realm&, URL::URL base_url, size_t source_line_number = 1, MutedErrors = MutedErrors::No);
|
||||
|
||||
JS::Script* script_record() { return m_script_record; }
|
||||
JS::Script const* script_record() const { return m_script_record; }
|
||||
|
||||
enum class RethrowErrors {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
JS::Completion run(RethrowErrors = RethrowErrors::No, JS::GCPtr<JS::Environment> lexical_environment_override = {});
|
||||
|
||||
MutedErrors muted_errors() const { return m_muted_errors; }
|
||||
|
||||
private:
|
||||
ClassicScript(URL::URL base_url, ByteString filename, JS::Realm&);
|
||||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
JS::GCPtr<JS::Script> m_script_record;
|
||||
MutedErrors m_muted_errors { MutedErrors::No };
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/Scripting/EnvironmentSettingsSnapshot.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(EnvironmentSettingsSnapshot);
|
||||
|
||||
EnvironmentSettingsSnapshot::EnvironmentSettingsSnapshot(NonnullOwnPtr<JS::ExecutionContext> execution_context, SerializedEnvironmentSettingsObject const& serialized_settings)
|
||||
: EnvironmentSettingsObject(move(execution_context))
|
||||
, m_api_url_character_encoding(serialized_settings.api_url_character_encoding)
|
||||
, m_url(serialized_settings.api_base_url)
|
||||
, m_origin(serialized_settings.origin)
|
||||
, m_policy_container(serialized_settings.policy_container)
|
||||
{
|
||||
// Why can't we put these in the init list? grandparent class members are strange it seems
|
||||
this->id = serialized_settings.id;
|
||||
this->creation_url = serialized_settings.creation_url;
|
||||
this->top_level_creation_url = serialized_settings.top_level_creation_url;
|
||||
this->top_level_creation_url = serialized_settings.top_level_creation_url;
|
||||
}
|
||||
|
||||
// Out of line to ensure this class has a key function
|
||||
EnvironmentSettingsSnapshot::~EnvironmentSettingsSnapshot() = default;
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/HTML/PolicyContainers.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/SerializedEnvironmentSettingsObject.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class EnvironmentSettingsSnapshot final
|
||||
: public EnvironmentSettingsObject {
|
||||
JS_CELL(EnvironmentSettingsSnapshot, EnvironmentSettingsObject);
|
||||
JS_DECLARE_ALLOCATOR(EnvironmentSettingsSnapshot);
|
||||
|
||||
public:
|
||||
EnvironmentSettingsSnapshot(NonnullOwnPtr<JS::ExecutionContext>, SerializedEnvironmentSettingsObject const&);
|
||||
|
||||
virtual ~EnvironmentSettingsSnapshot() override;
|
||||
|
||||
JS::GCPtr<DOM::Document> responsible_document() override { return nullptr; }
|
||||
String api_url_character_encoding() override { return m_api_url_character_encoding; }
|
||||
URL::URL api_base_url() override { return m_url; }
|
||||
URL::Origin origin() override { return m_origin; }
|
||||
PolicyContainer policy_container() override { return m_policy_container; }
|
||||
CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() override { return CanUseCrossOriginIsolatedAPIs::No; }
|
||||
|
||||
private:
|
||||
String m_api_url_character_encoding;
|
||||
URL::URL m_url;
|
||||
URL::Origin m_origin;
|
||||
HTML::PolicyContainer m_policy_container;
|
||||
};
|
||||
|
||||
}
|
527
Libraries/LibWeb/HTML/Scripting/Environments.cpp
Normal file
527
Libraries/LibWeb/HTML/Scripting/Environments.cpp
Normal file
|
@ -0,0 +1,527 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2022, networkException <networkexception@serenityos.org>
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/Bindings/PrincipalHostDefined.h>
|
||||
#include <LibWeb/Bindings/SyntheticHostDefined.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/FetchRecord.h>
|
||||
#include <LibWeb/HTML/PromiseRejectionEvent.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
||||
#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/HTML/WorkerGlobalScope.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/SecureContexts/AbstractOperations.h>
|
||||
#include <LibWeb/StorageAPI/StorageManager.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
Environment::~Environment() = default;
|
||||
|
||||
void Environment::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(target_browsing_context);
|
||||
}
|
||||
|
||||
EnvironmentSettingsObject::EnvironmentSettingsObject(NonnullOwnPtr<JS::ExecutionContext> realm_execution_context)
|
||||
: m_realm_execution_context(move(realm_execution_context))
|
||||
{
|
||||
m_realm_execution_context->context_owner = this;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
void EnvironmentSettingsObject::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
m_module_map = realm.heap().allocate_without_realm<ModuleMap>();
|
||||
}
|
||||
|
||||
void EnvironmentSettingsObject::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_responsible_event_loop);
|
||||
visitor.visit(m_module_map);
|
||||
m_realm_execution_context->visit_edges(visitor);
|
||||
visitor.visit(m_fetch_group);
|
||||
visitor.visit(m_storage_manager);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
JS::ExecutionContext const& EnvironmentSettingsObject::realm_execution_context() const
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
ModuleMap& EnvironmentSettingsObject::module_map()
|
||||
{
|
||||
return *m_module_map;
|
||||
}
|
||||
|
||||
// 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::Object& 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
|
||||
// https://whatpr.org/html/9893/webappapis.html#check-if-we-can-run-script
|
||||
RunScriptDecision can_run_script(JS::Realm const& realm)
|
||||
{
|
||||
// 1. If the global object specified by realm is a Window object whose Document object is not fully active, then return "do not run".
|
||||
if (is<HTML::Window>(realm.global_object()) && !verify_cast<HTML::Window>(realm.global_object()).associated_document().is_fully_active())
|
||||
return RunScriptDecision::DoNotRun;
|
||||
|
||||
// 2. If scripting is disabled for realm, then return "do not run".
|
||||
if (is_scripting_disabled(realm))
|
||||
return RunScriptDecision::DoNotRun;
|
||||
|
||||
// 3. Return "run".
|
||||
return RunScriptDecision::Run;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-script
|
||||
// https://whatpr.org/html/9893/b8ea975...df5706b/webappapis.html#prepare-to-run-script
|
||||
void prepare_to_run_script(JS::Realm& realm)
|
||||
{
|
||||
// 1. Push realms's execution context onto the JavaScript execution context stack; it is now the running JavaScript execution context.
|
||||
realm.global_object().vm().push_execution_context(execution_context_of_realm(realm));
|
||||
|
||||
// FIXME: 2. If realm is a principal realm, then:
|
||||
// FIXME: 2.1 Let settings be realm's settings object.
|
||||
// FIXME: 2.2 Add settings to the currently running task's script evaluation environment settings object set.
|
||||
}
|
||||
|
||||
// https://whatpr.org/html/9893/b8ea975...df5706b/webappapis.html#concept-realm-execution-context
|
||||
JS::ExecutionContext const& execution_context_of_realm(JS::Realm const& realm)
|
||||
{
|
||||
VERIFY(realm.host_defined());
|
||||
|
||||
// 1. If realm is a principal realm, then return the realm execution context of the environment settings object of realm.
|
||||
if (is<Bindings::PrincipalHostDefined>(*realm.host_defined()))
|
||||
return static_cast<Bindings::PrincipalHostDefined const&>(*realm.host_defined()).environment_settings_object->realm_execution_context();
|
||||
|
||||
// 2. Assert: realm is a synthetic realm.
|
||||
// 3. Return the execution context of the synthetic realm settings object of realm.
|
||||
return *verify_cast<Bindings::SyntheticHostDefined>(*realm.host_defined()).synthetic_realm_settings.execution_context;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script
|
||||
// https://whatpr.org/html/9893/webappapis.html#clean-up-after-running-script
|
||||
void clean_up_after_running_script(JS::Realm const& realm)
|
||||
{
|
||||
auto& vm = realm.global_object().vm();
|
||||
|
||||
// 1. Assert: realm's execution context is the running JavaScript execution context.
|
||||
VERIFY(&execution_context_of_realm(realm) == &vm.running_execution_context());
|
||||
|
||||
// 2. Remove realm's 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())
|
||||
main_thread_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 prepare_to_run_callback(JS::Realm& realm)
|
||||
{
|
||||
auto& vm = realm.global_object().vm();
|
||||
|
||||
// 1. Push realm 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_realm_stack(realm);
|
||||
|
||||
// 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/urls-and-fetching.html#parse-a-url
|
||||
URL::URL EnvironmentSettingsObject::parse_url(StringView url)
|
||||
{
|
||||
// 1. Let encoding be document's character encoding, if document was given, and environment settings object's API URL character encoding otherwise.
|
||||
// FIXME: Pass in environment settings object's API URL character encoding.
|
||||
|
||||
// 2. Let baseURL be document's base URL, if document was given, and environment settings object's API base URL otherwise.
|
||||
auto base_url = api_base_url();
|
||||
|
||||
// 3. Let urlRecord be the result of applying the URL parser to url, with baseURL and encoding.
|
||||
// 4. If urlRecord is failure, then return failure.
|
||||
// 5. Let urlString be the result of applying the URL serializer to urlRecord.
|
||||
// 6. Return urlString as the resulting URL string and urlRecord as the resulting URL record.
|
||||
return base_url.complete_url(url);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-a-callback
|
||||
// https://whatpr.org/html/9893/b8ea975...df5706b/webappapis.html#clean-up-after-running-a-callback
|
||||
void clean_up_after_running_callback(JS::Realm const& realm)
|
||||
{
|
||||
auto& vm = realm.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 realm stack is realm.
|
||||
auto& event_loop = HTML::main_thread_event_loop();
|
||||
VERIFY(&event_loop.top_of_backup_incumbent_realm_stack() == &realm);
|
||||
|
||||
// 4. Remove realm from the backup incumbent realm stack.
|
||||
event_loop.pop_backup_incumbent_realm_stack();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-script
|
||||
// https://whatpr.org/html/9893/webappapis.html#concept-environment-script
|
||||
bool is_scripting_enabled(JS::Realm const& realm)
|
||||
{
|
||||
// Scripting is enabled for a realm realm when all of the following conditions are true:
|
||||
// The user agent supports scripting.
|
||||
// NOTE: This is always true in LibWeb :^)
|
||||
|
||||
// FIXME: Do the right thing for workers.
|
||||
if (!is<HTML::Window>(realm.global_object()))
|
||||
return true;
|
||||
|
||||
// The user has not disabled scripting for realm at this time. (User agents may provide users with the option to disable scripting globally, or in a finer-grained manner, e.g., on a per-origin basis, down to the level of individual realms.)
|
||||
auto const& document = verify_cast<HTML::Window>(realm.global_object()).associated_document();
|
||||
if (!document.page().is_scripting_enabled())
|
||||
return false;
|
||||
|
||||
// FIXME: Either settings's global object is not a Window object, or settings's global object's associated Document's active sandboxing flag set does not have its sandboxed scripts browsing context flag set.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-noscript
|
||||
// https://whatpr.org/html/9893/webappapis.html#concept-environment-noscript
|
||||
bool is_scripting_disabled(JS::Realm const& realm)
|
||||
{
|
||||
// Scripting is disabled for a realm when scripting is not enabled for it, i.e., when any of the above conditions are false.
|
||||
return !is_scripting_enabled(realm);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#module-type-allowed
|
||||
// https://whatpr.org/html/9893/webappapis.html#module-type-allowed
|
||||
bool module_type_allowed(JS::Realm const&, StringView module_type)
|
||||
{
|
||||
// 1. If moduleType is not "javascript", "css", or "json", then return false.
|
||||
if (module_type != "javascript"sv && module_type != "css"sv && module_type != "json"sv)
|
||||
return false;
|
||||
|
||||
// FIXME: 2. If moduleType is "css" and the CSSStyleSheet interface is not exposed in realm, then return false.
|
||||
|
||||
// 3. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps
|
||||
// https://whatpr.org/html/9893/webappapis.html#disallow-further-import-maps
|
||||
void disallow_further_import_maps(JS::Realm& realm)
|
||||
{
|
||||
// 1. Let global be realm's global object.
|
||||
auto& global = realm.global_object();
|
||||
|
||||
// 2. If global does not implement Window, then return.
|
||||
if (!is<Window>(global))
|
||||
return;
|
||||
|
||||
// 3. Set global's import maps allowed to false.
|
||||
verify_cast<Window>(global).set_import_maps_allowed(false);
|
||||
}
|
||||
|
||||
// https://whatpr.org/html/9893/webappapis.html#concept-realm-module-map
|
||||
ModuleMap& module_map_of_realm(JS::Realm& realm)
|
||||
{
|
||||
VERIFY(realm.host_defined());
|
||||
|
||||
// 1. If realm is a principal realm, then return the module map of the environment settings object of realm.
|
||||
if (is<Bindings::PrincipalHostDefined>(*realm.host_defined()))
|
||||
return static_cast<Bindings::PrincipalHostDefined const&>(*realm.host_defined()).environment_settings_object->module_map();
|
||||
|
||||
// 2. Assert: realm is a synthetic realm.
|
||||
// 3. Return the module map of the synthetic realm settings object of realm.
|
||||
return *verify_cast<Bindings::SyntheticHostDefined>(*realm.host_defined()).synthetic_realm_settings.module_map;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-incumbent-realm
|
||||
// https://whatpr.org/html/9893/b8ea975...df5706b/webappapis.html#concept-incumbent-realm
|
||||
JS::Realm& incumbent_realm()
|
||||
{
|
||||
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.
|
||||
// 1. Assert: the backup incumbent realm stack is not empty.
|
||||
// NOTE: If this assertion fails, it's because the incumbent realm was used with no involvement of JavaScript.
|
||||
VERIFY(!event_loop.is_backup_incumbent_realm_stack_empty());
|
||||
|
||||
// 2. Return the topmost entry of the backup incumbent realm stack.
|
||||
return event_loop.top_of_backup_incumbent_realm_stack();
|
||||
}
|
||||
|
||||
// 3. Return context's Realm component.
|
||||
return *context->realm;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#incumbent-settings-object
|
||||
// https://whatpr.org/html/9893/b8ea975...df5706b/webappapis.html#incumbent-settings-object
|
||||
EnvironmentSettingsObject& incumbent_settings_object()
|
||||
{
|
||||
// Then, the incumbent settings object is the incumbent realm's principal realm settings object.
|
||||
return principal_realm_settings_object(principal_realm(incumbent_realm()));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-incumbent-global
|
||||
JS::Object& incumbent_global_object()
|
||||
{
|
||||
// Similarly, the incumbent global object is the global object of the incumbent settings object.
|
||||
return incumbent_settings_object().global_object();
|
||||
}
|
||||
|
||||
// https://whatpr.org/html/9893/webappapis.html#current-principal-realm
|
||||
JS::Realm& current_principal_realm()
|
||||
{
|
||||
auto& event_loop = HTML::main_thread_event_loop();
|
||||
auto& vm = event_loop.vm();
|
||||
|
||||
// The current principal realm is the principal realm of the current realm.
|
||||
return principal_realm(*vm.current_realm());
|
||||
}
|
||||
|
||||
// https://whatpr.org/html/9893/webappapis.html#concept-principal-realm-of-realm
|
||||
JS::Realm& principal_realm(JS::Realm& realm)
|
||||
{
|
||||
VERIFY(realm.host_defined());
|
||||
|
||||
// 1. If realm.[[HostDefined]] is a synthetic realm settings object, then:
|
||||
if (is<Bindings::SyntheticHostDefined>(*realm.host_defined())) {
|
||||
// 1. Assert: realm is a synthetic realm.
|
||||
// 2. Set realm to the principal realm of realm.[[HostDefined]].
|
||||
return static_cast<Bindings::SyntheticHostDefined const&>(*realm.host_defined()).synthetic_realm_settings.principal_realm;
|
||||
}
|
||||
|
||||
// 2. Assert: realm.[[HostDefined]] is an environment settings object and realm is a principal realm.
|
||||
VERIFY(is<Bindings::PrincipalHostDefined>(*realm.host_defined()));
|
||||
|
||||
// 3. Return realm.
|
||||
return realm;
|
||||
}
|
||||
|
||||
// https://whatpr.org/html/9893/webappapis.html#concept-realm-settings-object
|
||||
EnvironmentSettingsObject& principal_realm_settings_object(JS::Realm& realm)
|
||||
{
|
||||
// A principal realm has a [[HostDefined]] field, which contains the principal realm's settings object.
|
||||
return Bindings::principal_host_defined_environment_settings_object(realm);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#current-settings-object
|
||||
// https://whatpr.org/html/9893/webappapis.html#current-principal-settings-object
|
||||
EnvironmentSettingsObject& current_principal_settings_object()
|
||||
{
|
||||
// Then, the current principal settings object is the environment settings object of the current principal realm.
|
||||
return principal_realm_settings_object(current_principal_realm());
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#current-global-object
|
||||
// https://whatpr.org/html/9893/webappapis.html#current-principal-global-object
|
||||
JS::Object& current_principal_global_object()
|
||||
{
|
||||
// Similarly, the current principal global object is the global object of the current principal realm.
|
||||
return current_principal_realm().global_object();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-relevant-realm
|
||||
JS::Realm& relevant_realm(JS::Object const& object)
|
||||
{
|
||||
// The relevant Realm for a platform object is the value of its [[Realm]] field.
|
||||
return object.shape().realm();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#relevant-settings-object
|
||||
EnvironmentSettingsObject& relevant_settings_object(JS::Object const& object)
|
||||
{
|
||||
// Then, the relevant settings object for a platform object o is the environment settings object of the relevant Realm for o.
|
||||
return Bindings::principal_host_defined_environment_settings_object(relevant_realm(object));
|
||||
}
|
||||
|
||||
EnvironmentSettingsObject& relevant_settings_object(DOM::Node const& node)
|
||||
{
|
||||
// Then, the relevant settings object for a platform object o is the environment settings object of the relevant Realm for o.
|
||||
return const_cast<DOM::Document&>(node.document()).relevant_settings_object();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-relevant-global
|
||||
JS::Object& relevant_global_object(JS::Object const& object)
|
||||
{
|
||||
// Similarly, the relevant global object for a platform object o is the global object of the relevant Realm for o.
|
||||
return relevant_realm(object).global_object();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-entry-realm
|
||||
// https://whatpr.org/html/9893/webappapis.html#concept-entry-realm
|
||||
JS::Realm& entry_realm()
|
||||
{
|
||||
auto& event_loop = HTML::main_thread_event_loop();
|
||||
auto& vm = event_loop.vm();
|
||||
|
||||
// With this in hand, we define the entry execution context to be the most recently pushed item in the JavaScript execution context stack that is a realm execution context.
|
||||
// The entry realm is the principal realm of the entry execution context's Realm component.
|
||||
// NOTE: Currently all execution contexts in LibJS are realm execution contexts
|
||||
return principal_realm(*vm.running_execution_context().realm);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#entry-settings-object
|
||||
EnvironmentSettingsObject& entry_settings_object()
|
||||
{
|
||||
// Then, the entry settings object is the environment settings object of the entry realm.
|
||||
return Bindings::principal_host_defined_environment_settings_object(entry_realm());
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#entry-global-object
|
||||
JS::Object& entry_global_object()
|
||||
{
|
||||
// Similarly, the entry global object is the global object of the entry realm.
|
||||
return entry_realm().global_object();
|
||||
}
|
||||
|
||||
JS::VM& relevant_agent(JS::Object const& object)
|
||||
{
|
||||
// The relevant agent for a platform object platformObject is platformObject's relevant Realm's agent.
|
||||
// Spec Note: This pointer is not yet defined in the JavaScript specification; see tc39/ecma262#1357.
|
||||
return relevant_realm(object).vm();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#secure-context
|
||||
bool is_secure_context(Environment const& environment)
|
||||
{
|
||||
// 1. If environment is an environment settings object, then:
|
||||
if (is<EnvironmentSettingsObject>(environment)) {
|
||||
// 1. Let global be environment's global object.
|
||||
// FIXME: Add a const global_object() getter to ESO
|
||||
auto& global = static_cast<EnvironmentSettingsObject&>(const_cast<Environment&>(environment)).global_object();
|
||||
|
||||
// 2. If global is a WorkerGlobalScope, then:
|
||||
if (is<WorkerGlobalScope>(global)) {
|
||||
// FIXME: 1. If global's owner set[0]'s relevant settings object is a secure context, then return true.
|
||||
// NOTE: We only need to check the 0th item since they will necessarily all be consistent.
|
||||
|
||||
// 2. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: 3. If global is a WorkletGlobalScope, then return true.
|
||||
// NOTE: Worklets can only be created in secure contexts.
|
||||
}
|
||||
|
||||
// 2. If the result of Is url potentially trustworthy? given environment's top-level creation URL is "Potentially Trustworthy", then return true.
|
||||
if (SecureContexts::is_url_potentially_trustworthy(environment.top_level_creation_url) == SecureContexts::Trustworthiness::PotentiallyTrustworthy)
|
||||
return true;
|
||||
|
||||
// 3. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#non-secure-context
|
||||
bool is_non_secure_context(Environment const& environment)
|
||||
{
|
||||
// An environment is a non-secure context if it is not a secure context.
|
||||
return !is_secure_context(environment);
|
||||
}
|
||||
|
||||
SerializedEnvironmentSettingsObject EnvironmentSettingsObject::serialize()
|
||||
{
|
||||
SerializedEnvironmentSettingsObject object;
|
||||
|
||||
object.id = this->id;
|
||||
object.creation_url = this->creation_url;
|
||||
object.top_level_creation_url = this->top_level_creation_url;
|
||||
object.top_level_origin = this->top_level_origin;
|
||||
|
||||
object.api_url_character_encoding = api_url_character_encoding();
|
||||
object.api_base_url = api_base_url();
|
||||
object.origin = origin();
|
||||
object.policy_container = policy_container();
|
||||
object.cross_origin_isolated_capability = cross_origin_isolated_capability();
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
JS::NonnullGCPtr<StorageAPI::StorageManager> EnvironmentSettingsObject::storage_manager()
|
||||
{
|
||||
if (!m_storage_manager)
|
||||
m_storage_manager = realm().heap().allocate<StorageAPI::StorageManager>(realm(), realm());
|
||||
return *m_storage_manager;
|
||||
}
|
||||
|
||||
}
|
165
Libraries/LibWeb/HTML/Scripting/Environments.h
Normal file
165
Libraries/LibWeb/HTML/Scripting/Environments.h
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibURL/Origin.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||||
#include <LibWeb/HTML/Scripting/ModuleMap.h>
|
||||
#include <LibWeb/HTML/Scripting/SerializedEnvironmentSettingsObject.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#environment
|
||||
struct Environment : public JS::Cell {
|
||||
JS_CELL(Environment, JS::Cell);
|
||||
|
||||
public:
|
||||
virtual ~Environment() override;
|
||||
|
||||
// An id https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-id
|
||||
String id;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-creation-url
|
||||
URL::URL creation_url;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-creation-url
|
||||
URL::URL top_level_creation_url;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin
|
||||
URL::Origin top_level_origin;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-target-browsing-context
|
||||
JS::GCPtr<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 };
|
||||
|
||||
protected:
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
};
|
||||
|
||||
enum class RunScriptDecision {
|
||||
Run,
|
||||
DoNotRun,
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
|
||||
struct EnvironmentSettingsObject : public Environment {
|
||||
JS_CELL(EnvironmentSettingsObject, Environment);
|
||||
|
||||
public:
|
||||
virtual ~EnvironmentSettingsObject() override;
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-target-browsing-context
|
||||
JS::ExecutionContext& realm_execution_context();
|
||||
JS::ExecutionContext const& realm_execution_context() const;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-module-map
|
||||
ModuleMap& module_map();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#responsible-document
|
||||
virtual JS::GCPtr<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 URL::URL api_base_url() = 0;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin
|
||||
virtual URL::Origin origin() = 0;
|
||||
|
||||
// A policy container https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-policy-container
|
||||
virtual PolicyContainer policy_container() = 0;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-cross-origin-isolated-capability
|
||||
virtual CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() = 0;
|
||||
|
||||
URL::URL parse_url(StringView);
|
||||
|
||||
JS::Realm& realm();
|
||||
JS::Object& global_object();
|
||||
EventLoop& responsible_event_loop();
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-fetch-group
|
||||
Vector<JS::NonnullGCPtr<Fetch::Infrastructure::FetchRecord>>& fetch_group() { return m_fetch_group; }
|
||||
|
||||
SerializedEnvironmentSettingsObject serialize();
|
||||
|
||||
JS::NonnullGCPtr<StorageAPI::StorageManager> storage_manager();
|
||||
|
||||
[[nodiscard]] bool discarded() const { return m_discarded; }
|
||||
void set_discarded(bool b) { m_discarded = b; }
|
||||
|
||||
protected:
|
||||
explicit EnvironmentSettingsObject(NonnullOwnPtr<JS::ExecutionContext>);
|
||||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
private:
|
||||
NonnullOwnPtr<JS::ExecutionContext> m_realm_execution_context;
|
||||
JS::GCPtr<ModuleMap> m_module_map;
|
||||
|
||||
JS::GCPtr<EventLoop> m_responsible_event_loop;
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-fetch-record
|
||||
// A fetch group holds an ordered list of fetch records
|
||||
Vector<JS::NonnullGCPtr<Fetch::Infrastructure::FetchRecord>> m_fetch_group;
|
||||
|
||||
// https://storage.spec.whatwg.org/#api
|
||||
// Each environment settings object has an associated StorageManager object.
|
||||
JS::GCPtr<StorageAPI::StorageManager> m_storage_manager;
|
||||
|
||||
// https://w3c.github.io/ServiceWorker/#service-worker-client-discarded-flag
|
||||
// A service worker client has an associated discarded flag. It is initially unset.
|
||||
bool m_discarded { false };
|
||||
};
|
||||
|
||||
JS::ExecutionContext const& execution_context_of_realm(JS::Realm const&);
|
||||
inline JS::ExecutionContext& execution_context_of_realm(JS::Realm& realm) { return const_cast<JS::ExecutionContext&>(execution_context_of_realm(const_cast<JS::Realm const&>(realm))); }
|
||||
|
||||
RunScriptDecision can_run_script(JS::Realm const&);
|
||||
bool is_scripting_enabled(JS::Realm const&);
|
||||
bool is_scripting_disabled(JS::Realm const&);
|
||||
void prepare_to_run_script(JS::Realm&);
|
||||
void clean_up_after_running_script(JS::Realm const&);
|
||||
void prepare_to_run_callback(JS::Realm&);
|
||||
void clean_up_after_running_callback(JS::Realm const&);
|
||||
ModuleMap& module_map_of_realm(JS::Realm&);
|
||||
bool module_type_allowed(JS::Realm const&, StringView module_type);
|
||||
void disallow_further_import_maps(JS::Realm&);
|
||||
|
||||
EnvironmentSettingsObject& incumbent_settings_object();
|
||||
JS::Realm& incumbent_realm();
|
||||
JS::Object& incumbent_global_object();
|
||||
|
||||
JS::Realm& current_principal_realm();
|
||||
EnvironmentSettingsObject& principal_realm_settings_object(JS::Realm&);
|
||||
EnvironmentSettingsObject& current_principal_settings_object();
|
||||
|
||||
JS::Realm& principal_realm(JS::Realm&);
|
||||
JS::Object& current_principal_global_object();
|
||||
JS::Realm& relevant_realm(JS::Object const&);
|
||||
EnvironmentSettingsObject& relevant_settings_object(JS::Object const&);
|
||||
EnvironmentSettingsObject& relevant_settings_object(DOM::Node const&);
|
||||
JS::Object& relevant_global_object(JS::Object const&);
|
||||
JS::Realm& entry_realm();
|
||||
EnvironmentSettingsObject& entry_settings_object();
|
||||
JS::Object& entry_global_object();
|
||||
JS::VM& relevant_agent(JS::Object const&);
|
||||
[[nodiscard]] bool is_secure_context(Environment const&);
|
||||
[[nodiscard]] bool is_non_secure_context(Environment const&);
|
||||
|
||||
}
|
63
Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp
Normal file
63
Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibJS/Console.h>
|
||||
#include <LibJS/Runtime/ConsoleObject.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
||||
#include <LibWeb/WebIDL/DOMException.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
void report_exception_to_console(JS::Value value, JS::Realm& realm, ErrorInPromise error_in_promise)
|
||||
{
|
||||
auto& console = realm.intrinsics().console_object()->console();
|
||||
|
||||
if (value.is_object()) {
|
||||
auto& object = value.as_object();
|
||||
auto& vm = object.vm();
|
||||
auto name = object.get_without_side_effects(vm.names.name).value_or(JS::js_undefined());
|
||||
auto message = object.get_without_side_effects(vm.names.message).value_or(JS::js_undefined());
|
||||
if (name.is_accessor() || message.is_accessor()) {
|
||||
// The result is not going to be useful, let's just print the value. This affects DOMExceptions, for example.
|
||||
if (is<WebIDL::DOMException>(object)) {
|
||||
auto const& exception = static_cast<WebIDL::DOMException const&>(object);
|
||||
dbgln("\033[31;1mUnhandled JavaScript exception{}:\033[0m {}: {}", error_in_promise == ErrorInPromise::Yes ? " (in promise)" : "", exception.name(), exception.message());
|
||||
} else {
|
||||
dbgln("\033[31;1mUnhandled JavaScript exception{}:\033[0m {}", error_in_promise == ErrorInPromise::Yes ? " (in promise)" : "", JS::Value(&object));
|
||||
}
|
||||
} else {
|
||||
dbgln("\033[31;1mUnhandled JavaScript exception{}:\033[0m [{}] {}", error_in_promise == ErrorInPromise::Yes ? " (in promise)" : "", name, message);
|
||||
}
|
||||
if (is<JS::Error>(object)) {
|
||||
// FIXME: We should be doing this for DOMException as well
|
||||
// https://webidl.spec.whatwg.org/#js-DOMException-specialness
|
||||
// "Additionally, if an implementation gives native Error objects special powers or nonstandard properties (such as a stack property), it should also expose those on DOMException objects."
|
||||
auto const& error_value = static_cast<JS::Error const&>(object);
|
||||
dbgln("{}", error_value.stack_string(JS::CompactTraceback::Yes));
|
||||
console.report_exception(error_value, error_in_promise == ErrorInPromise::Yes);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
dbgln("\033[31;1mUnhandled JavaScript exception{}:\033[0m {}", error_in_promise == ErrorInPromise::Yes ? " (in promise)" : "", value);
|
||||
}
|
||||
|
||||
console.report_exception(*JS::Error::create(realm, value.to_string_without_side_effects()), error_in_promise == ErrorInPromise::Yes);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/#report-the-exception
|
||||
void report_exception(JS::Completion const& throw_completion, JS::Realm& realm)
|
||||
{
|
||||
VERIFY(throw_completion.type() == JS::Completion::Type::Throw);
|
||||
VERIFY(throw_completion.value().has_value());
|
||||
report_exception_to_console(*throw_completion.value(), realm, ErrorInPromise::No);
|
||||
}
|
||||
|
||||
}
|
28
Libraries/LibWeb/HTML/Scripting/ExceptionReporter.h
Normal file
28
Libraries/LibWeb/HTML/Scripting/ExceptionReporter.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
enum class ErrorInPromise {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
void report_exception_to_console(JS::Value, JS::Realm&, ErrorInPromise);
|
||||
void report_exception(JS::Completion const&, JS::Realm&);
|
||||
|
||||
template<typename T>
|
||||
inline void report_exception(JS::ThrowCompletionOr<T> const& result, JS::Realm& realm)
|
||||
{
|
||||
VERIFY(result.is_throw_completion());
|
||||
report_exception(result.throw_completion(), realm);
|
||||
}
|
||||
|
||||
}
|
1007
Libraries/LibWeb/HTML/Scripting/Fetching.cpp
Normal file
1007
Libraries/LibWeb/HTML/Scripting/Fetching.cpp
Normal file
File diff suppressed because it is too large
Load diff
107
Libraries/LibWeb/HTML/Scripting/Fetching.h
Normal file
107
Libraries/LibWeb/HTML/Scripting/Fetching.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, networkException <networkexception@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
|
||||
#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 {
|
||||
|
||||
enum class TopLevelModule {
|
||||
Yes,
|
||||
No
|
||||
};
|
||||
|
||||
using OnFetchScriptComplete = JS::NonnullGCPtr<JS::HeapFunction<void(JS::GCPtr<Script>)>>;
|
||||
using PerformTheFetchHook = JS::GCPtr<JS::HeapFunction<WebIDL::ExceptionOr<void>(JS::NonnullGCPtr<Fetch::Infrastructure::Request>, TopLevelModule, Fetch::Infrastructure::FetchAlgorithms::ProcessResponseConsumeBodyFunction)>>;
|
||||
|
||||
OnFetchScriptComplete create_on_fetch_script_complete(JS::Heap& heap, Function<void(JS::GCPtr<Script>)> function);
|
||||
PerformTheFetchHook create_perform_the_fetch_hook(JS::Heap& heap, Function<WebIDL::ExceptionOr<void>(JS::NonnullGCPtr<Fetch::Infrastructure::Request>, TopLevelModule, Fetch::Infrastructure::FetchAlgorithms::ProcessResponseConsumeBodyFunction)> function);
|
||||
|
||||
// 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
|
||||
Fetch::Infrastructure::Request::ParserMetadata parser_metadata { Fetch::Infrastructure::Request::ParserMetadata::NotParserInserted };
|
||||
|
||||
// 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
|
||||
ReferrerPolicy::ReferrerPolicy referrer_policy { ReferrerPolicy::ReferrerPolicy::EmptyString };
|
||||
|
||||
// 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 {};
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#default-script-fetch-options
|
||||
ScriptFetchOptions default_script_fetch_options();
|
||||
|
||||
class FetchContext : public JS::GraphLoadingState::HostDefined {
|
||||
JS_CELL(FetchContext, JS::GraphLoadingState::HostDefined);
|
||||
JS_DECLARE_ALLOCATOR(FetchContext);
|
||||
|
||||
public:
|
||||
JS::Value parse_error; // [[ParseError]]
|
||||
Fetch::Infrastructure::Request::Destination destination; // [[Destination]]
|
||||
PerformTheFetchHook perform_fetch; // [[PerformFetch]]
|
||||
JS::NonnullGCPtr<EnvironmentSettingsObject> fetch_client; // [[FetchClient]]
|
||||
|
||||
private:
|
||||
FetchContext(JS::Value parse_error, Fetch::Infrastructure::Request::Destination destination, PerformTheFetchHook perform_fetch, EnvironmentSettingsObject& fetch_client)
|
||||
: parse_error(parse_error)
|
||||
, destination(destination)
|
||||
, perform_fetch(perform_fetch)
|
||||
, fetch_client(fetch_client)
|
||||
{
|
||||
}
|
||||
|
||||
void visit_edges(Visitor& visitor) override
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(parse_error);
|
||||
visitor.visit(perform_fetch);
|
||||
visitor.visit(fetch_client);
|
||||
}
|
||||
};
|
||||
|
||||
ByteString module_type_from_module_request(JS::ModuleRequest const&);
|
||||
WebIDL::ExceptionOr<URL::URL> resolve_module_specifier(Optional<Script&> referring_script, ByteString const& specifier);
|
||||
WebIDL::ExceptionOr<Optional<URL::URL>> resolve_imports_match(ByteString const& normalized_specifier, Optional<URL::URL> as_url, ModuleSpecifierMap const&);
|
||||
Optional<URL::URL> resolve_url_like_module_specifier(ByteString const& specifier, URL::URL const& base_url);
|
||||
WebIDL::ExceptionOr<ScriptFetchOptions> get_descendant_script_fetch_options(ScriptFetchOptions const& original_options, URL::URL const& url, EnvironmentSettingsObject& settings_object);
|
||||
WebIDL::ExceptionOr<String> resolve_a_module_integrity_metadata(URL::URL const& url, EnvironmentSettingsObject& settings_object);
|
||||
WebIDL::ExceptionOr<void> fetch_classic_script(JS::NonnullGCPtr<HTMLScriptElement>, URL::URL const&, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete);
|
||||
WebIDL::ExceptionOr<void> fetch_classic_worker_script(URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook, OnFetchScriptComplete);
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<ClassicScript>> fetch_a_classic_worker_imported_script(URL::URL const&, HTML::EnvironmentSettingsObject&, PerformTheFetchHook = nullptr);
|
||||
WebIDL::ExceptionOr<void> fetch_module_worker_script_graph(URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook, OnFetchScriptComplete);
|
||||
WebIDL::ExceptionOr<void> fetch_worklet_module_worker_script_graph(URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook, OnFetchScriptComplete);
|
||||
void fetch_internal_module_script_graph(JS::Realm&, JS::ModuleRequest const& module_request, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination, ScriptFetchOptions const&, Script& referring_script, HashTable<ModuleLocationTuple> const& visited_set, PerformTheFetchHook, OnFetchScriptComplete on_complete);
|
||||
void fetch_external_module_script_graph(JS::Realm&, URL::URL const&, EnvironmentSettingsObject& settings_object, ScriptFetchOptions const&, OnFetchScriptComplete on_complete);
|
||||
void fetch_inline_module_script_graph(JS::Realm&, ByteString const& filename, ByteString const& source_text, URL::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete);
|
||||
void fetch_single_imported_module_script(JS::Realm&, URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, ScriptFetchOptions const&, JS::Realm& module_map_realm, Fetch::Infrastructure::Request::ReferrerType, JS::ModuleRequest const&, PerformTheFetchHook, OnFetchScriptComplete on_complete);
|
||||
|
||||
void fetch_descendants_of_a_module_script(JS::Realm&, JavaScriptModuleScript& module_script, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination, HashTable<ModuleLocationTuple> visited_set, PerformTheFetchHook, OnFetchScriptComplete callback);
|
||||
void fetch_descendants_of_and_link_a_module_script(JS::Realm&, JavaScriptModuleScript&, EnvironmentSettingsObject&, Fetch::Infrastructure::Request::Destination, PerformTheFetchHook, OnFetchScriptComplete on_complete);
|
||||
|
||||
Fetch::Infrastructure::Request::Destination fetch_destination_from_module_type(Fetch::Infrastructure::Request::Destination, ByteString const&);
|
||||
|
||||
void fetch_single_module_script(JS::Realm&, URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, ScriptFetchOptions const&, JS::Realm& module_map_realm, Web::Fetch::Infrastructure::Request::ReferrerType const&, Optional<JS::ModuleRequest> const&, TopLevelModule, PerformTheFetchHook, OnFetchScriptComplete callback);
|
||||
}
|
21
Libraries/LibWeb/HTML/Scripting/Fetching.idl
Normal file
21
Libraries/LibWeb/HTML/Scripting/Fetching.idl
Normal file
|
@ -0,0 +1,21 @@
|
|||
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attribute
|
||||
[InvalidValueDefault=anonymous]
|
||||
enum CORSSettingsAttribute {
|
||||
"anonymous",
|
||||
"use-credentials"
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attribute
|
||||
[MissingValueDefault=eager, InvalidValueDefault=eager]
|
||||
enum LazyLoadingAttribute {
|
||||
"lazy",
|
||||
"eager"
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes
|
||||
[MissingValueDefault=auto, InvalidValueDefault=auto]
|
||||
enum FetchPriorityAttribute {
|
||||
"high",
|
||||
"low",
|
||||
"auto"
|
||||
};
|
270
Libraries/LibWeb/HTML/Scripting/ImportMap.cpp
Normal file
270
Libraries/LibWeb/HTML/Scripting/ImportMap.cpp
Normal file
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Console.h>
|
||||
#include <LibJS/Runtime/ConsoleObject.h>
|
||||
#include <LibWeb/DOMURL/DOMURL.h>
|
||||
#include <LibWeb/HTML/Scripting/Fetching.h>
|
||||
#include <LibWeb/HTML/Scripting/ImportMap.h>
|
||||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
#include <LibWeb/Infra/JSON.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#parse-an-import-map-string
|
||||
WebIDL::ExceptionOr<ImportMap> parse_import_map_string(JS::Realm& realm, ByteString const& input, URL::URL base_url)
|
||||
{
|
||||
HTML::TemporaryExecutionContext execution_context { realm };
|
||||
|
||||
// 1. Let parsed be the result of parsing a JSON string to an Infra value given input.
|
||||
auto parsed = TRY(Infra::parse_json_string_to_javascript_value(realm, input));
|
||||
|
||||
// 2. If parsed is not an ordered map, then throw a TypeError indicating that the top-level value needs to be a JSON object.
|
||||
if (!parsed.is_object())
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The top-level value of an importmap needs to be a JSON object.").release_value_but_fixme_should_propagate_errors() };
|
||||
auto& parsed_object = parsed.as_object();
|
||||
|
||||
// 3. Let sortedAndNormalizedImports be an empty ordered map.
|
||||
ModuleSpecifierMap sorted_and_normalised_imports;
|
||||
|
||||
// 4. If parsed["imports"] exists, then:
|
||||
if (TRY(parsed_object.has_property("imports"))) {
|
||||
auto imports = TRY(parsed_object.get("imports"));
|
||||
|
||||
// If parsed["imports"] is not an ordered map, then throw a TypeError indicating that the value for the "imports" top-level key needs to be a JSON object.
|
||||
if (!imports.is_object())
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The 'imports' top-level value of an importmap needs to be a JSON object.").release_value_but_fixme_should_propagate_errors() };
|
||||
|
||||
// Set sortedAndNormalizedImports to the result of sorting and normalizing a module specifier map given parsed["imports"] and baseURL.
|
||||
sorted_and_normalised_imports = TRY(sort_and_normalise_module_specifier_map(realm, imports.as_object(), base_url));
|
||||
}
|
||||
|
||||
// 5. Let sortedAndNormalizedScopes be an empty ordered map.
|
||||
HashMap<URL::URL, ModuleSpecifierMap> sorted_and_normalised_scopes;
|
||||
|
||||
// 6. If parsed["scopes"] exists, then:
|
||||
if (TRY(parsed_object.has_property("scopes"))) {
|
||||
auto scopes = TRY(parsed_object.get("scopes"));
|
||||
|
||||
// If parsed["scopes"] is not an ordered map, then throw a TypeError indicating that the value for the "scopes" top-level key needs to be a JSON object.
|
||||
if (!scopes.is_object())
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The 'scopes' top-level value of an importmap needs to be a JSON object.").release_value_but_fixme_should_propagate_errors() };
|
||||
|
||||
// Set sortedAndNormalizedScopes to the result of sorting and normalizing scopes given parsed["scopes"] and baseURL.
|
||||
sorted_and_normalised_scopes = TRY(sort_and_normalise_scopes(realm, scopes.as_object(), base_url));
|
||||
}
|
||||
|
||||
// 7. Let normalizedIntegrity be an empty ordered map.
|
||||
ModuleIntegrityMap normalised_integrity;
|
||||
|
||||
// 8. If parsed["integrity"] exists, then:
|
||||
if (TRY(parsed_object.has_property("integrity"))) {
|
||||
auto integrity = TRY(parsed_object.get("integrity"));
|
||||
|
||||
// 1. If parsed["integrity"] is not an ordered map, then throw a TypeError indicating that the value for the "integrity" top-level key needs to be a JSON object.
|
||||
if (!integrity.is_object())
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The 'integrity' top-level value of an importmap needs to be a JSON object.").release_value_but_fixme_should_propagate_errors() };
|
||||
|
||||
// 2. Set normalizedIntegrity to the result of normalizing a module integrity map given parsed["integrity"] and baseURL.
|
||||
normalised_integrity = TRY(normalize_module_integrity_map(realm, integrity.as_object(), base_url));
|
||||
}
|
||||
|
||||
// 9. If parsed's keys contains any items besides "imports", "scopes", or "integrity", then the user agent should report a warning to the console indicating that an invalid top-level key was present in the import map.
|
||||
for (auto& key : parsed_object.shape().property_table().keys()) {
|
||||
if (key.as_string().is_one_of("imports", "scopes", "integrity"))
|
||||
continue;
|
||||
|
||||
auto& console = realm.intrinsics().console_object()->console();
|
||||
console.output_debug_message(JS::Console::LogLevel::Warn,
|
||||
TRY_OR_THROW_OOM(realm.vm(), String::formatted("An invalid top-level key ({}) was present in the import map", key.as_string())));
|
||||
}
|
||||
|
||||
// 10. Return an import map whose imports are sortedAndNormalizedImports, whose scopes are sortedAndNormalizedScopes, and whose integrity are normalizedIntegrity.
|
||||
ImportMap import_map;
|
||||
import_map.set_imports(sorted_and_normalised_imports);
|
||||
import_map.set_scopes(sorted_and_normalised_scopes);
|
||||
import_map.set_integrity(normalised_integrity);
|
||||
return import_map;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-a-specifier-key
|
||||
WebIDL::ExceptionOr<Optional<DeprecatedFlyString>> normalise_specifier_key(JS::Realm& realm, DeprecatedFlyString specifier_key, URL::URL base_url)
|
||||
{
|
||||
// 1. If specifierKey is the empty string, then:
|
||||
if (specifier_key.is_empty()) {
|
||||
// 1. The user agent may report a warning to the console indicating that specifier keys may not be the empty string.
|
||||
auto& console = realm.intrinsics().console_object()->console();
|
||||
console.output_debug_message(JS::Console::LogLevel::Warn,
|
||||
TRY_OR_THROW_OOM(realm.vm(), String::formatted("Specifier keys may not be empty")));
|
||||
|
||||
// 2. Return null.
|
||||
return Optional<DeprecatedFlyString> {};
|
||||
}
|
||||
|
||||
// 2. Let url be the result of resolving a URL-like module specifier, given specifierKey and baseURL.
|
||||
auto url = resolve_url_like_module_specifier(specifier_key, base_url);
|
||||
|
||||
// 3. If url is not null, then return the serialization of url.
|
||||
if (url.has_value())
|
||||
return url->serialize();
|
||||
|
||||
// 4. Return specifierKey.
|
||||
return specifier_key;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-a-module-specifier-map
|
||||
WebIDL::ExceptionOr<ModuleSpecifierMap> sort_and_normalise_module_specifier_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url)
|
||||
{
|
||||
// 1. Let normalized be an empty ordered map.
|
||||
ModuleSpecifierMap normalised;
|
||||
|
||||
// 2. For each specifierKey → value of originalMap:
|
||||
for (auto& specifier_key : original_map.shape().property_table().keys()) {
|
||||
auto value = TRY(original_map.get(specifier_key.as_string()));
|
||||
|
||||
// 1. Let normalizedSpecifierKey be the result of normalizing a specifier key given specifierKey and baseURL.
|
||||
auto normalised_specifier_key = TRY(normalise_specifier_key(realm, specifier_key.as_string(), base_url));
|
||||
|
||||
// 2. If normalizedSpecifierKey is null, then continue.
|
||||
if (!normalised_specifier_key.has_value())
|
||||
continue;
|
||||
|
||||
// 3. If value is not a string, then:
|
||||
if (!value.is_string()) {
|
||||
// 1. The user agent may report a warning to the console indicating that addresses need to be strings.
|
||||
auto& console = realm.intrinsics().console_object()->console();
|
||||
console.output_debug_message(JS::Console::LogLevel::Warn,
|
||||
TRY_OR_THROW_OOM(realm.vm(), String::formatted("Addresses need to be strings")));
|
||||
|
||||
// 2. Set normalized[normalizedSpecifierKey] to null.
|
||||
normalised.set(normalised_specifier_key.value(), {});
|
||||
|
||||
// 3. Continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. Let addressURL be the result of resolving a URL-like module specifier given value and baseURL.
|
||||
auto address_url = resolve_url_like_module_specifier(value.as_string().byte_string(), base_url);
|
||||
|
||||
// 5. If addressURL is null, then:
|
||||
if (!address_url.has_value()) {
|
||||
// 1. The user agent may report a warning to the console indicating that the address was invalid.
|
||||
auto& console = realm.intrinsics().console_object()->console();
|
||||
console.output_debug_message(JS::Console::LogLevel::Warn,
|
||||
TRY_OR_THROW_OOM(realm.vm(), String::formatted("Address was invalid")));
|
||||
|
||||
// 2. Set normalized[normalizedSpecifierKey] to null.
|
||||
normalised.set(normalised_specifier_key.value(), {});
|
||||
|
||||
// 3. Continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// 6. If specifierKey ends with U+002F (/), and the serialization of addressURL does not end with U+002F (/), then:
|
||||
if (specifier_key.as_string().ends_with("/"sv) && !address_url->serialize().ends_with("/"sv)) {
|
||||
// 1. The user agent may report a warning to the console indicating that an invalid address was given for the specifier key specifierKey; since specifierKey ends with a slash, the address needs to as well.
|
||||
auto& console = realm.intrinsics().console_object()->console();
|
||||
console.output_debug_message(JS::Console::LogLevel::Warn,
|
||||
TRY_OR_THROW_OOM(realm.vm(), String::formatted("An invalid address was given for the specifier key ({}); since specifierKey ends with a slash, the address needs to as well", specifier_key.as_string())));
|
||||
|
||||
// 2. Set normalized[normalizedSpecifierKey] to null.
|
||||
normalised.set(normalised_specifier_key.value(), {});
|
||||
|
||||
// 3. Continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// 7. Set normalized[normalizedSpecifierKey] to addressURL.
|
||||
normalised.set(normalised_specifier_key.value(), address_url.value());
|
||||
}
|
||||
|
||||
// 3. Return the result of sorting in descending order normalized, with an entry a being less than an entry b if a's key is code unit less than b's key.
|
||||
return normalised;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-scopes
|
||||
WebIDL::ExceptionOr<HashMap<URL::URL, ModuleSpecifierMap>> sort_and_normalise_scopes(JS::Realm& realm, JS::Object& original_map, URL::URL base_url)
|
||||
{
|
||||
// 1. Let normalized be an empty ordered map.
|
||||
HashMap<URL::URL, ModuleSpecifierMap> normalised;
|
||||
|
||||
// 2. For each scopePrefix → potentialSpecifierMap of originalMap:
|
||||
for (auto& scope_prefix : original_map.shape().property_table().keys()) {
|
||||
auto potential_specifier_map = TRY(original_map.get(scope_prefix.as_string()));
|
||||
|
||||
// 1. If potentialSpecifierMap is not an ordered map, then throw a TypeError indicating that the value of the scope with prefix scopePrefix needs to be a JSON object.
|
||||
if (!potential_specifier_map.is_object())
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("The value of the scope with the prefix '{}' needs to be a JSON object.", scope_prefix.as_string()).release_value_but_fixme_should_propagate_errors() };
|
||||
|
||||
// 2. Let scopePrefixURL be the result of URL parsing scopePrefix with baseURL.
|
||||
auto scope_prefix_url = DOMURL::parse(scope_prefix.as_string(), base_url);
|
||||
|
||||
// 3. If scopePrefixURL is failure, then:
|
||||
if (!scope_prefix_url.is_valid()) {
|
||||
// 1. The user agent may report a warning to the console that the scope prefix URL was not parseable.
|
||||
auto& console = realm.intrinsics().console_object()->console();
|
||||
console.output_debug_message(JS::Console::LogLevel::Warn,
|
||||
TRY_OR_THROW_OOM(realm.vm(), String::formatted("The scope prefix URL ({}) was not parseable", scope_prefix.as_string())));
|
||||
|
||||
// 2. Continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. Let normalizedScopePrefix be the serialization of scopePrefixURL.
|
||||
auto normalised_scope_prefix = scope_prefix_url.serialize();
|
||||
|
||||
// 5. Set normalized[normalizedScopePrefix] to the result of sorting and normalizing a module specifier map given potentialSpecifierMap and baseURL.
|
||||
normalised.set(normalised_scope_prefix, TRY(sort_and_normalise_module_specifier_map(realm, potential_specifier_map.as_object(), base_url)));
|
||||
}
|
||||
|
||||
// 3. Return the result of sorting in descending order normalized, with an entry a being less than an entry b if a's key is code unit less than b's key.
|
||||
return normalised;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-a-module-integrity-map
|
||||
WebIDL::ExceptionOr<ModuleIntegrityMap> normalize_module_integrity_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url)
|
||||
{
|
||||
// 1. Let normalized be an empty ordered map.
|
||||
ModuleIntegrityMap normalised;
|
||||
|
||||
// 2. For each key → value of originalMap:
|
||||
for (auto& key : original_map.shape().property_table().keys()) {
|
||||
auto value = TRY(original_map.get(key.as_string()));
|
||||
|
||||
// 1. Let resolvedURL be the result of resolving a URL-like module specifier given key and baseURL.
|
||||
auto resolved_url = resolve_url_like_module_specifier(key.as_string(), base_url);
|
||||
|
||||
// 2. If resolvedURL is null, then:
|
||||
if (!resolved_url.has_value()) {
|
||||
// 1. The user agent may report a warning to the console indicating that the key failed to resolve.
|
||||
auto& console = realm.intrinsics().console_object()->console();
|
||||
console.output_debug_message(JS::Console::LogLevel::Warn,
|
||||
TRY_OR_THROW_OOM(realm.vm(), String::formatted("Failed to resolve key ({})", key.as_string())));
|
||||
|
||||
// 2. Continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. If value is not a string, then:
|
||||
if (!value.is_string()) {
|
||||
// 1. The user agent may report a warning to the console indicating that integrity metadata values need to be strings.
|
||||
auto& console = realm.intrinsics().console_object()->console();
|
||||
console.output_debug_message(JS::Console::LogLevel::Warn,
|
||||
TRY_OR_THROW_OOM(realm.vm(), String::formatted("Integrity metadata value for '{}' needs to be a string", key.as_string())));
|
||||
|
||||
// 2. Continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. Set normalized[resolvedURL] to value.
|
||||
normalised.set(resolved_url.release_value(), value.as_string().byte_string());
|
||||
}
|
||||
|
||||
// 3. Return normalized.
|
||||
return normalised;
|
||||
}
|
||||
|
||||
}
|
48
Libraries/LibWeb/HTML/Scripting/ImportMap.h
Normal file
48
Libraries/LibWeb/HTML/Scripting/ImportMap.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2022, networkException <networkexception@serenityos.org>
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
using ModuleSpecifierMap = HashMap<ByteString, Optional<URL::URL>>;
|
||||
using ModuleIntegrityMap = HashMap<URL::URL, ByteString>;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#import-map
|
||||
class ImportMap {
|
||||
public:
|
||||
ImportMap() = default;
|
||||
|
||||
ModuleSpecifierMap const& imports() const { return m_imports; }
|
||||
ModuleSpecifierMap& imports() { return m_imports; }
|
||||
void set_imports(ModuleSpecifierMap const& imports) { m_imports = imports; }
|
||||
|
||||
HashMap<URL::URL, ModuleSpecifierMap> const& scopes() const { return m_scopes; }
|
||||
HashMap<URL::URL, ModuleSpecifierMap>& scopes() { return m_scopes; }
|
||||
void set_scopes(HashMap<URL::URL, ModuleSpecifierMap> const& scopes) { m_scopes = scopes; }
|
||||
|
||||
ModuleIntegrityMap const& integrity() const { return m_integrity; }
|
||||
ModuleIntegrityMap integrity() { return m_integrity; }
|
||||
void set_integrity(ModuleIntegrityMap const& integrity) { m_integrity = integrity; }
|
||||
|
||||
private:
|
||||
ModuleSpecifierMap m_imports;
|
||||
HashMap<URL::URL, ModuleSpecifierMap> m_scopes;
|
||||
ModuleIntegrityMap m_integrity;
|
||||
};
|
||||
|
||||
WebIDL::ExceptionOr<ImportMap> parse_import_map_string(JS::Realm& realm, ByteString const& input, URL::URL base_url);
|
||||
WebIDL::ExceptionOr<Optional<DeprecatedFlyString>> normalise_specifier_key(JS::Realm& realm, DeprecatedFlyString specifier_key, URL::URL base_url);
|
||||
WebIDL::ExceptionOr<ModuleSpecifierMap> sort_and_normalise_module_specifier_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url);
|
||||
WebIDL::ExceptionOr<HashMap<URL::URL, ModuleSpecifierMap>> sort_and_normalise_scopes(JS::Realm& realm, JS::Object& original_map, URL::URL base_url);
|
||||
WebIDL::ExceptionOr<ModuleIntegrityMap> normalize_module_integrity_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url);
|
||||
|
||||
}
|
84
Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp
Normal file
84
Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/ModuleRequest.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
||||
#include <LibWeb/HTML/Scripting/ImportMapParseResult.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/WebIDL/DOMException.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(ImportMapParseResult);
|
||||
|
||||
ImportMapParseResult::ImportMapParseResult() = default;
|
||||
|
||||
ImportMapParseResult::~ImportMapParseResult() = default;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#create-an-import-map-parse-result
|
||||
JS::NonnullGCPtr<ImportMapParseResult> ImportMapParseResult::create(JS::Realm& realm, ByteString const& input, URL::URL base_url)
|
||||
{
|
||||
// 1. Let result be an import map parse result whose import map is null and whose error to rethrow is null.
|
||||
auto result = realm.heap().allocate<ImportMapParseResult>(realm);
|
||||
|
||||
// 2. Parse an import map string given input and baseURL, catching any exceptions.
|
||||
auto import_map = parse_import_map_string(realm, input, base_url);
|
||||
|
||||
// 2.1. If this threw an exception, then set result's error to rethrow to that exception.
|
||||
if (import_map.is_exception())
|
||||
result->set_error_to_rethrow(import_map.exception());
|
||||
|
||||
// 2.2. Otherwise, set result's import map to the return value.
|
||||
else
|
||||
result->set_import_map(import_map.release_value());
|
||||
|
||||
// 3. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
void ImportMapParseResult::visit_host_defined_self(Visitor& visitor)
|
||||
{
|
||||
visitor.visit(*this);
|
||||
}
|
||||
|
||||
void ImportMapParseResult::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
if (m_error_to_rethrow.has_value()) {
|
||||
m_error_to_rethrow.value().visit(
|
||||
[&](WebIDL::SimpleException const&) {
|
||||
// ignore
|
||||
},
|
||||
[&](JS::NonnullGCPtr<WebIDL::DOMException> exception) {
|
||||
visitor.visit(exception);
|
||||
},
|
||||
[&](JS::Completion const& completion) {
|
||||
if (completion.value().has_value())
|
||||
visitor.visit(completion.value().value());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
|
||||
void ImportMapParseResult::register_import_map(Window& global)
|
||||
{
|
||||
// 1. If result's error to rethrow is not null, then report the exception given by result's error to rethrow and return.
|
||||
if (m_error_to_rethrow.has_value()) {
|
||||
auto completion = Web::Bindings::dom_exception_to_throw_completion(global.vm(), m_error_to_rethrow.value());
|
||||
HTML::report_exception(completion, global.realm());
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Assert: global's import map is an empty import map.
|
||||
VERIFY(global.import_map().imports().is_empty() && global.import_map().scopes().is_empty());
|
||||
|
||||
// 3. Set global's import map to result's import map.
|
||||
VERIFY(m_import_map.has_value());
|
||||
global.set_import_map(m_import_map.value());
|
||||
}
|
||||
|
||||
}
|
53
Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.h
Normal file
53
Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibJS/Script.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/Scripting/ImportMap.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#import-map-parse-result
|
||||
class ImportMapParseResult
|
||||
: public JS::Cell
|
||||
, public JS::Script::HostDefined {
|
||||
JS_CELL(ImportMapParseResult, JS::Cell);
|
||||
JS_DECLARE_ALLOCATOR(ImportMapParseResult);
|
||||
|
||||
public:
|
||||
virtual ~ImportMapParseResult() override;
|
||||
|
||||
static JS::NonnullGCPtr<ImportMapParseResult> create(JS::Realm& realm, ByteString const& input, URL::URL base_url);
|
||||
|
||||
[[nodiscard]] Optional<ImportMap> const& import_map() const { return m_import_map; }
|
||||
void set_import_map(ImportMap const& value) { m_import_map = value; }
|
||||
|
||||
[[nodiscard]] Optional<WebIDL::Exception> const& error_to_rethrow() const { return m_error_to_rethrow; }
|
||||
void set_error_to_rethrow(WebIDL::Exception const& value) { m_error_to_rethrow = value; }
|
||||
|
||||
void register_import_map(Window& global);
|
||||
|
||||
protected:
|
||||
ImportMapParseResult();
|
||||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
private:
|
||||
virtual void visit_host_defined_self(Visitor&) override;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#impr-import-map
|
||||
Optional<ImportMap> m_import_map;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#impr-error-to-rethrow
|
||||
Optional<WebIDL::Exception> m_error_to_rethrow;
|
||||
};
|
||||
|
||||
}
|
70
Libraries/LibWeb/HTML/Scripting/ModuleMap.cpp
Normal file
70
Libraries/LibWeb/HTML/Scripting/ModuleMap.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, networkException <networkexception@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/Scripting/ModuleMap.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(ModuleMap);
|
||||
|
||||
void ModuleMap::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
for (auto& it : m_values)
|
||||
visitor.visit(it.value.module_script);
|
||||
|
||||
for (auto const& it : m_callbacks)
|
||||
visitor.visit(it.value);
|
||||
}
|
||||
|
||||
bool ModuleMap::is_fetching(URL::URL const& url, ByteString const& type) const
|
||||
{
|
||||
return is(url, type, EntryType::Fetching);
|
||||
}
|
||||
|
||||
bool ModuleMap::is_failed(URL::URL const& url, ByteString const& type) const
|
||||
{
|
||||
return is(url, type, EntryType::Failed);
|
||||
}
|
||||
|
||||
bool ModuleMap::is(URL::URL const& url, ByteString const& type, EntryType entry_type) const
|
||||
{
|
||||
auto value = m_values.get({ url, type });
|
||||
if (!value.has_value())
|
||||
return false;
|
||||
|
||||
return value->type == entry_type;
|
||||
}
|
||||
|
||||
Optional<ModuleMap::Entry> ModuleMap::get(URL::URL const& url, ByteString const& type) const
|
||||
{
|
||||
return m_values.get({ url, type });
|
||||
}
|
||||
|
||||
AK::HashSetResult ModuleMap::set(URL::URL const& url, ByteString const& type, Entry entry)
|
||||
{
|
||||
// NOTE: Re-entering this function while firing wait_for_change callbacks is not allowed.
|
||||
VERIFY(!m_firing_callbacks);
|
||||
|
||||
auto value = m_values.set({ url, type }, entry);
|
||||
|
||||
auto callbacks = m_callbacks.get({ url, type });
|
||||
if (callbacks.has_value()) {
|
||||
m_firing_callbacks = true;
|
||||
for (auto const& callback : *callbacks)
|
||||
callback->function()(entry);
|
||||
m_firing_callbacks = false;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void ModuleMap::wait_for_change(JS::Heap& heap, URL::URL const& url, ByteString const& type, Function<void(Entry)> callback)
|
||||
{
|
||||
m_callbacks.ensure({ url, type }).append(JS::create_heap_function(heap, move(callback)));
|
||||
}
|
||||
|
||||
}
|
91
Libraries/LibWeb/HTML/Scripting/ModuleMap.h
Normal file
91
Libraries/LibWeb/HTML/Scripting/ModuleMap.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, networkException <networkexception@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibJS/Heap/HeapFunction.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/HTML/Scripting/ModuleScript.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class ModuleLocationTuple {
|
||||
public:
|
||||
ModuleLocationTuple(URL::URL url, ByteString type)
|
||||
: m_url(move(url))
|
||||
, m_type(move(type))
|
||||
{
|
||||
}
|
||||
|
||||
URL::URL const& url() const { return m_url; }
|
||||
ByteString const& type() const { return m_type; }
|
||||
|
||||
bool operator==(ModuleLocationTuple const& other) const
|
||||
{
|
||||
return other.url() == m_url && other.type() == m_type;
|
||||
}
|
||||
|
||||
private:
|
||||
URL::URL m_url;
|
||||
ByteString m_type;
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#module-map
|
||||
class ModuleMap final : public JS::Cell {
|
||||
JS_CELL(ModuleMap, JS::Cell);
|
||||
JS_DECLARE_ALLOCATOR(ModuleMap);
|
||||
|
||||
public:
|
||||
ModuleMap() = default;
|
||||
~ModuleMap() = default;
|
||||
|
||||
enum class EntryType {
|
||||
Fetching,
|
||||
Failed,
|
||||
ModuleScript
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
EntryType type;
|
||||
JS::GCPtr<JavaScriptModuleScript> module_script;
|
||||
};
|
||||
|
||||
using CallbackFunction = JS::NonnullGCPtr<JS::HeapFunction<void(Entry)>>;
|
||||
|
||||
bool is_fetching(URL::URL const& url, ByteString const& type) const;
|
||||
bool is_failed(URL::URL const& url, ByteString const& type) const;
|
||||
|
||||
bool is(URL::URL const& url, ByteString const& type, EntryType) const;
|
||||
|
||||
Optional<Entry> get(URL::URL const& url, ByteString const& type) const;
|
||||
|
||||
AK::HashSetResult set(URL::URL const& url, ByteString const& type, Entry);
|
||||
|
||||
void wait_for_change(JS::Heap&, URL::URL const& url, ByteString const& type, Function<void(Entry)> callback);
|
||||
|
||||
private:
|
||||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||||
|
||||
HashMap<ModuleLocationTuple, Entry> m_values;
|
||||
HashMap<ModuleLocationTuple, Vector<CallbackFunction>> m_callbacks;
|
||||
|
||||
bool m_firing_callbacks { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
|
||||
template<>
|
||||
struct Traits<Web::HTML::ModuleLocationTuple> : public DefaultTraits<Web::HTML::ModuleLocationTuple> {
|
||||
static unsigned hash(Web::HTML::ModuleLocationTuple const& tuple)
|
||||
{
|
||||
return pair_int_hash(tuple.url().to_byte_string().hash(), tuple.type().hash());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
186
Libraries/LibWeb/HTML/Scripting/ModuleScript.cpp
Normal file
186
Libraries/LibWeb/HTML/Scripting/ModuleScript.cpp
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (c) 2022, networkException <networkexception@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/ModuleRequest.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/Fetching.h>
|
||||
#include <LibWeb/HTML/Scripting/ModuleScript.h>
|
||||
#include <LibWeb/WebIDL/DOMException.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(JavaScriptModuleScript);
|
||||
|
||||
ModuleScript::~ModuleScript() = default;
|
||||
|
||||
ModuleScript::ModuleScript(URL::URL base_url, ByteString filename, JS::Realm& realm)
|
||||
: Script(move(base_url), move(filename), realm)
|
||||
{
|
||||
}
|
||||
|
||||
JavaScriptModuleScript::~JavaScriptModuleScript() = default;
|
||||
|
||||
JavaScriptModuleScript::JavaScriptModuleScript(URL::URL base_url, ByteString filename, JS::Realm& realm)
|
||||
: ModuleScript(move(base_url), move(filename), realm)
|
||||
{
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-javascript-module-script
|
||||
// https://whatpr.org/html/9893/webappapis.html#creating-a-javascript-module-script
|
||||
WebIDL::ExceptionOr<JS::GCPtr<JavaScriptModuleScript>> JavaScriptModuleScript::create(ByteString const& filename, StringView source, JS::Realm& realm, URL::URL base_url)
|
||||
{
|
||||
// 1. If scripting is disabled for realm, then set source to the empty string.
|
||||
if (HTML::is_scripting_disabled(realm))
|
||||
source = ""sv;
|
||||
|
||||
// 2. Let script be a new module script that this algorithm will subsequently initialize.
|
||||
// 3. Set script's realm to realm.
|
||||
// 4. Set script's base URL to baseURL.
|
||||
auto script = realm.heap().allocate<JavaScriptModuleScript>(realm, move(base_url), filename, realm);
|
||||
|
||||
// FIXME: 5. Set script's fetch options to options.
|
||||
|
||||
// 6. Set script's parse error and error to rethrow to null.
|
||||
script->set_parse_error(JS::js_null());
|
||||
script->set_error_to_rethrow(JS::js_null());
|
||||
|
||||
// 7. Let result be ParseModule(source, realm, script).
|
||||
auto result = JS::SourceTextModule::parse(source, realm, filename.view(), script);
|
||||
|
||||
// 8. If result is a list of errors, then:
|
||||
if (result.is_error()) {
|
||||
auto& parse_error = result.error().first();
|
||||
dbgln("JavaScriptModuleScript: Failed to parse: {}", parse_error.to_string());
|
||||
|
||||
// 1. Set script's parse error to result[0].
|
||||
script->set_parse_error(JS::SyntaxError::create(realm, parse_error.to_string()));
|
||||
|
||||
// 2. Return script.
|
||||
return script;
|
||||
}
|
||||
|
||||
// 9. For each ModuleRequest record requested of result.[[RequestedModules]]:
|
||||
for (auto const& requested : result.value()->requested_modules()) {
|
||||
// FIXME: Clarify if this should be checked for all requested before running the steps below.
|
||||
// 1. If requested.[[Attributes]] contains a Record entry such that entry.[[Key]] is not "type", then:
|
||||
for (auto const& attribute : requested.attributes) {
|
||||
if (attribute.key != "type"sv) {
|
||||
// 1. Let error be a new SyntaxError exception.
|
||||
auto error = JS::SyntaxError::create(realm, "Module request attributes must only contain a type attribute"_string);
|
||||
|
||||
// 2. Set script's parse error to error.
|
||||
script->set_parse_error(error);
|
||||
|
||||
// 3. Return script.
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Let url be the result of resolving a module specifier given script and requested.[[Specifier]], catching any exceptions.
|
||||
auto url = resolve_module_specifier(*script, requested.module_specifier);
|
||||
|
||||
// 3. If the previous step threw an exception, then:
|
||||
if (url.is_exception()) {
|
||||
// FIXME: 1. Set script's parse error to that exception.
|
||||
|
||||
// 2. Return script.
|
||||
return script;
|
||||
}
|
||||
|
||||
// 4. Let moduleType be the result of running the module type from module request steps given requested.
|
||||
auto module_type = module_type_from_module_request(requested);
|
||||
|
||||
// 5. If the result of running the module type allowed steps given moduleType and realm is false, then:
|
||||
if (!module_type_allowed(realm, module_type)) {
|
||||
// FIXME: 1. Let error be a new TypeError exception.
|
||||
|
||||
// FIXME: 2. Set script's parse error to error.
|
||||
|
||||
// 3. Return script.
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Set script's record to result.
|
||||
script->m_record = result.value();
|
||||
|
||||
// 11. Return script.
|
||||
return script;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#run-a-module-script
|
||||
// https://whatpr.org/html/9893/webappapis.html#run-a-module-script
|
||||
JS::Promise* JavaScriptModuleScript::run(PreventErrorReporting)
|
||||
{
|
||||
// 1. Let realm be the realm of script.
|
||||
auto& realm = this->realm();
|
||||
|
||||
// 2. Check if we can run script with realm. If this returns "do not run", then return a promise resolved with undefined.
|
||||
if (can_run_script(realm) == RunScriptDecision::DoNotRun) {
|
||||
auto promise = JS::Promise::create(realm);
|
||||
promise->fulfill(JS::js_undefined());
|
||||
return promise;
|
||||
}
|
||||
|
||||
// 3. Prepare to run script given realm.
|
||||
prepare_to_run_script(realm);
|
||||
|
||||
// 4. Let evaluationPromise be null.
|
||||
JS::Promise* evaluation_promise = nullptr;
|
||||
|
||||
// 5. If script's error to rethrow is not null, then set evaluationPromise to a promise rejected with script's error to rethrow.
|
||||
if (!error_to_rethrow().is_null()) {
|
||||
evaluation_promise = JS::Promise::create(realm);
|
||||
evaluation_promise->reject(error_to_rethrow());
|
||||
}
|
||||
// 6. Otherwise:
|
||||
else {
|
||||
// 1. Let record be script's record.
|
||||
auto record = m_record;
|
||||
VERIFY(record);
|
||||
|
||||
// NON-STANDARD: To ensure that LibJS can find the module on the stack, we push a new execution context.
|
||||
auto module_execution_context = JS::ExecutionContext::create();
|
||||
module_execution_context->realm = &realm;
|
||||
module_execution_context->script_or_module = JS::NonnullGCPtr<JS::Module> { *record };
|
||||
vm().push_execution_context(*module_execution_context);
|
||||
|
||||
// 2. Set evaluationPromise to record.Evaluate().
|
||||
auto elevation_promise_or_error = record->evaluate(vm());
|
||||
|
||||
// NOTE: This step will recursively evaluate all of the module's dependencies.
|
||||
// If Evaluate fails to complete as a result of the user agent aborting the running script,
|
||||
// then set evaluationPromise to a promise rejected with a new "QuotaExceededError" DOMException.
|
||||
if (elevation_promise_or_error.is_error()) {
|
||||
auto promise = JS::Promise::create(realm);
|
||||
promise->reject(WebIDL::QuotaExceededError::create(realm, "Failed to evaluate module script"_string).ptr());
|
||||
|
||||
evaluation_promise = promise;
|
||||
} else {
|
||||
evaluation_promise = elevation_promise_or_error.value();
|
||||
}
|
||||
|
||||
// NON-STANDARD: Pop the execution context mentioned above.
|
||||
vm().pop_execution_context();
|
||||
}
|
||||
|
||||
// FIXME: 7. If preventErrorReporting is false, then upon rejection of evaluationPromise with reason, report the exception given by reason for script.
|
||||
|
||||
// 8. Clean up after running script with realm.
|
||||
clean_up_after_running_script(realm);
|
||||
|
||||
// 9. Return evaluationPromise.
|
||||
return evaluation_promise;
|
||||
}
|
||||
|
||||
void JavaScriptModuleScript::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_record);
|
||||
}
|
||||
|
||||
}
|
58
Libraries/LibWeb/HTML/Scripting/ModuleScript.h
Normal file
58
Libraries/LibWeb/HTML/Scripting/ModuleScript.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2022, networkException <networkexception@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/SourceTextModule.h>
|
||||
#include <LibWeb/HTML/Scripting/Script.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#module-script
|
||||
class ModuleScript : public Script {
|
||||
JS_CELL(ModuleScript, Script);
|
||||
|
||||
public:
|
||||
virtual ~ModuleScript() override;
|
||||
|
||||
protected:
|
||||
ModuleScript(URL::URL base_url, ByteString filename, JS::Realm&);
|
||||
};
|
||||
|
||||
class JavaScriptModuleScript final : public ModuleScript {
|
||||
JS_CELL(JavaScriptModuleScript, ModuleScript);
|
||||
JS_DECLARE_ALLOCATOR(JavaScriptModuleScript);
|
||||
|
||||
public:
|
||||
virtual ~JavaScriptModuleScript() override;
|
||||
|
||||
static WebIDL::ExceptionOr<JS::GCPtr<JavaScriptModuleScript>> create(ByteString const& filename, StringView source, JS::Realm&, URL::URL base_url);
|
||||
|
||||
enum class PreventErrorReporting {
|
||||
Yes,
|
||||
No
|
||||
};
|
||||
|
||||
JS::Promise* run(PreventErrorReporting = PreventErrorReporting::No);
|
||||
|
||||
JS::SourceTextModule const* record() const { return m_record.ptr(); }
|
||||
JS::SourceTextModule* record() { return m_record.ptr(); }
|
||||
|
||||
protected:
|
||||
JavaScriptModuleScript(URL::URL base_url, ByteString filename, JS::Realm&);
|
||||
|
||||
private:
|
||||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||||
|
||||
JS::GCPtr<JS::SourceTextModule> m_record;
|
||||
|
||||
size_t m_fetch_internal_request_count { 0 };
|
||||
size_t m_completed_fetch_internal_request_count { 0 };
|
||||
|
||||
Function<void(JavaScriptModuleScript const*)> m_completed_fetch_internal_callback;
|
||||
};
|
||||
|
||||
}
|
43
Libraries/LibWeb/HTML/Scripting/Script.cpp
Normal file
43
Libraries/LibWeb/HTML/Scripting/Script.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/Script.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(Script);
|
||||
|
||||
Script::Script(URL::URL base_url, ByteString filename, JS::Realm& realm)
|
||||
: m_base_url(move(base_url))
|
||||
, m_filename(move(filename))
|
||||
, m_realm(realm)
|
||||
{
|
||||
}
|
||||
|
||||
Script::~Script() = default;
|
||||
|
||||
// https://whatpr.org/html/9893/webappapis.html#settings-object
|
||||
EnvironmentSettingsObject& Script::settings_object()
|
||||
{
|
||||
// The settings object of a script is the settings object of the principal realm of the script's realm.
|
||||
return principal_realm_settings_object(principal_realm(realm()));
|
||||
}
|
||||
|
||||
void Script::visit_host_defined_self(JS::Cell::Visitor& visitor)
|
||||
{
|
||||
visitor.visit(*this);
|
||||
}
|
||||
|
||||
void Script::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_realm);
|
||||
visitor.visit(m_parse_error);
|
||||
visitor.visit(m_error_to_rethrow);
|
||||
}
|
||||
|
||||
}
|
58
Libraries/LibWeb/HTML/Scripting/Script.h
Normal file
58
Libraries/LibWeb/HTML/Scripting/Script.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibJS/Script.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-script
|
||||
// https://whatpr.org/html/9893/webappapis.html#concept-script
|
||||
class Script
|
||||
: public JS::Cell
|
||||
, public JS::Script::HostDefined {
|
||||
JS_CELL(Script, JS::Cell);
|
||||
JS_DECLARE_ALLOCATOR(Script);
|
||||
|
||||
public:
|
||||
virtual ~Script() override;
|
||||
|
||||
URL::URL const& base_url() const { return m_base_url; }
|
||||
ByteString const& filename() const { return m_filename; }
|
||||
|
||||
JS::Realm& realm() { return m_realm; }
|
||||
EnvironmentSettingsObject& settings_object();
|
||||
|
||||
[[nodiscard]] JS::Value error_to_rethrow() const { return m_error_to_rethrow; }
|
||||
void set_error_to_rethrow(JS::Value value) { m_error_to_rethrow = value; }
|
||||
|
||||
[[nodiscard]] JS::Value parse_error() const { return m_parse_error; }
|
||||
void set_parse_error(JS::Value value) { m_parse_error = value; }
|
||||
|
||||
protected:
|
||||
Script(URL::URL base_url, ByteString filename, JS::Realm&);
|
||||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
private:
|
||||
virtual void visit_host_defined_self(JS::Cell::Visitor&) override;
|
||||
|
||||
URL::URL m_base_url;
|
||||
ByteString m_filename;
|
||||
JS::NonnullGCPtr<JS::Realm> m_realm;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-parse-error
|
||||
JS::Value m_parse_error;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-error-to-rethrow
|
||||
JS::Value m_error_to_rethrow;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibIPC/Decoder.h>
|
||||
#include <LibIPC/Encoder.h>
|
||||
#include <LibWeb/HTML/Scripting/SerializedEnvironmentSettingsObject.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Web::HTML::SerializedEnvironmentSettingsObject const& object)
|
||||
{
|
||||
TRY(encoder.encode(object.id));
|
||||
TRY(encoder.encode(object.creation_url));
|
||||
TRY(encoder.encode(object.top_level_creation_url));
|
||||
TRY(encoder.encode(object.top_level_origin));
|
||||
TRY(encoder.encode(object.api_url_character_encoding));
|
||||
TRY(encoder.encode(object.api_base_url));
|
||||
TRY(encoder.encode(object.origin));
|
||||
TRY(encoder.encode(object.policy_container));
|
||||
TRY(encoder.encode(object.cross_origin_isolated_capability));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::SerializedEnvironmentSettingsObject> decode(Decoder& decoder)
|
||||
{
|
||||
Web::HTML::SerializedEnvironmentSettingsObject object {};
|
||||
|
||||
object.id = TRY(decoder.decode<String>());
|
||||
object.creation_url = TRY(decoder.decode<URL::URL>());
|
||||
object.top_level_creation_url = TRY(decoder.decode<URL::URL>());
|
||||
object.top_level_origin = TRY(decoder.decode<URL::Origin>());
|
||||
object.api_url_character_encoding = TRY(decoder.decode<String>());
|
||||
object.api_base_url = TRY(decoder.decode<URL::URL>());
|
||||
object.origin = TRY(decoder.decode<URL::Origin>());
|
||||
object.policy_container = TRY(decoder.decode<Web::HTML::PolicyContainer>());
|
||||
object.cross_origin_isolated_capability = TRY(decoder.decode<Web::HTML::CanUseCrossOriginIsolatedAPIs>());
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
#include <LibURL/Origin.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/HTML/PolicyContainers.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
enum class CanUseCrossOriginIsolatedAPIs {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
struct SerializedEnvironmentSettingsObject {
|
||||
String id;
|
||||
URL::URL creation_url;
|
||||
URL::URL top_level_creation_url;
|
||||
URL::Origin top_level_origin;
|
||||
|
||||
String api_url_character_encoding;
|
||||
URL::URL api_base_url;
|
||||
URL::Origin origin;
|
||||
PolicyContainer policy_container;
|
||||
CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder&, Web::HTML::SerializedEnvironmentSettingsObject const&);
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::SerializedEnvironmentSettingsObject> decode(Decoder&);
|
||||
|
||||
}
|
20
Libraries/LibWeb/HTML/Scripting/SyntheticRealmSettings.cpp
Normal file
20
Libraries/LibWeb/HTML/Scripting/SyntheticRealmSettings.cpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/Scripting/ModuleMap.h>
|
||||
#include <LibWeb/HTML/Scripting/SyntheticRealmSettings.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
void SyntheticRealmSettings::visit_edges(JS::Cell::Visitor& visitor)
|
||||
{
|
||||
execution_context->visit_edges(visitor);
|
||||
visitor.visit(principal_realm);
|
||||
visitor.visit(underlying_realm);
|
||||
visitor.visit(module_map);
|
||||
}
|
||||
|
||||
}
|
37
Libraries/LibWeb/HTML/Scripting/SyntheticRealmSettings.h
Normal file
37
Libraries/LibWeb/HTML/Scripting/SyntheticRealmSettings.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Heap/GCPtr.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://whatpr.org/html/9893/webappapis.html#synthetic-realm-settings-objects
|
||||
// Each synthetic realm has an associated synthetic realm settings object with the following fields:
|
||||
struct SyntheticRealmSettings {
|
||||
// An execution context
|
||||
// The JavaScript execution context for the scripts within this realm.
|
||||
NonnullOwnPtr<JS::ExecutionContext> execution_context;
|
||||
|
||||
// A principal realm
|
||||
// The principal realm which this synthetic realm exists within.
|
||||
JS::NonnullGCPtr<JS::Realm> principal_realm;
|
||||
|
||||
// An underlying realm
|
||||
// The synthetic realm which this settings object represents.
|
||||
JS::NonnullGCPtr<JS::Realm> underlying_realm;
|
||||
|
||||
// A module map
|
||||
// A module map that is used when importing JavaScript modules.
|
||||
JS::NonnullGCPtr<ModuleMap> module_map;
|
||||
|
||||
void visit_edges(JS::Cell::Visitor&);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
TemporaryExecutionContext::TemporaryExecutionContext(JS::Realm& realm, CallbacksEnabled callbacks_enabled)
|
||||
: m_realm(realm)
|
||||
, m_callbacks_enabled(callbacks_enabled)
|
||||
{
|
||||
prepare_to_run_script(m_realm);
|
||||
if (m_callbacks_enabled == CallbacksEnabled::Yes)
|
||||
prepare_to_run_callback(m_realm);
|
||||
}
|
||||
|
||||
TemporaryExecutionContext::~TemporaryExecutionContext()
|
||||
{
|
||||
clean_up_after_running_script(m_realm);
|
||||
if (m_callbacks_enabled == CallbacksEnabled::Yes)
|
||||
clean_up_after_running_callback(m_realm);
|
||||
}
|
||||
|
||||
}
|
32
Libraries/LibWeb/HTML/Scripting/TemporaryExecutionContext.h
Normal file
32
Libraries/LibWeb/HTML/Scripting/TemporaryExecutionContext.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Heap/GCPtr.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// When JS is run from outside the context of any user script, we currently do not have a running execution context.
|
||||
// This results in a crash when we access VM::running_execution_context(). This is a spec issue. Until it is resolved,
|
||||
// this is a workaround to temporarily push an execution context.
|
||||
class TemporaryExecutionContext {
|
||||
public:
|
||||
enum class CallbacksEnabled {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
explicit TemporaryExecutionContext(JS::Realm&, CallbacksEnabled = CallbacksEnabled::No);
|
||||
~TemporaryExecutionContext();
|
||||
|
||||
private:
|
||||
JS::NonnullGCPtr<JS::Realm> m_realm;
|
||||
CallbacksEnabled m_callbacks_enabled { CallbacksEnabled::No };
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/PrincipalHostDefined.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(WindowEnvironmentSettingsObject);
|
||||
|
||||
WindowEnvironmentSettingsObject::WindowEnvironmentSettingsObject(Window& window, NonnullOwnPtr<JS::ExecutionContext> execution_context)
|
||||
: EnvironmentSettingsObject(move(execution_context))
|
||||
, m_window(window)
|
||||
{
|
||||
}
|
||||
|
||||
WindowEnvironmentSettingsObject::~WindowEnvironmentSettingsObject() = default;
|
||||
|
||||
void WindowEnvironmentSettingsObject::visit_edges(JS::Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_window);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/window-object.html#set-up-a-window-environment-settings-object
|
||||
void WindowEnvironmentSettingsObject::setup(Page& page, URL::URL const& creation_url, NonnullOwnPtr<JS::ExecutionContext> execution_context, JS::GCPtr<Environment> reserved_environment, URL::URL top_level_creation_url, URL::Origin top_level_origin)
|
||||
{
|
||||
// 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.
|
||||
auto& window = verify_cast<HTML::Window>(realm->global_object());
|
||||
|
||||
// 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 = realm->heap().allocate<WindowEnvironmentSettingsObject>(*realm, window, move(execution_context));
|
||||
|
||||
// 4. If reservedEnvironment is non-null, then:
|
||||
if (reserved_environment) {
|
||||
// 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.
|
||||
settings_object->id = reserved_environment->id;
|
||||
settings_object->target_browsing_context = reserved_environment->target_browsing_context;
|
||||
|
||||
// 2. Set reservedEnvironment's id to the empty string.
|
||||
reserved_environment->id = String {};
|
||||
}
|
||||
|
||||
// 5. Otherwise, ...
|
||||
else {
|
||||
// FIXME: ...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.
|
||||
static i64 next_id = 1;
|
||||
settings_object->id = String::number(next_id++);
|
||||
settings_object->target_browsing_context = nullptr;
|
||||
}
|
||||
|
||||
// 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;
|
||||
settings_object->top_level_creation_url = move(top_level_creation_url);
|
||||
settings_object->top_level_origin = move(top_level_origin);
|
||||
|
||||
// 7. Set realm's [[HostDefined]] field to settings object.
|
||||
// Non-Standard: We store the ESO next to the web intrinsics in a custom HostDefined object
|
||||
auto intrinsics = realm->heap().allocate<Bindings::Intrinsics>(*realm, *realm);
|
||||
auto host_defined = make<Bindings::PrincipalHostDefined>(settings_object, intrinsics, page);
|
||||
realm->set_host_defined(move(host_defined));
|
||||
|
||||
// Non-Standard: We cannot fully initialize window object until *after* the we set up
|
||||
// the realm's [[HostDefined]] internal slot as the internal slot contains the web platform intrinsics
|
||||
MUST(window.initialize_web_interfaces({}));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:responsible-document
|
||||
JS::GCPtr<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
|
||||
URL::URL WindowEnvironmentSettingsObject::api_base_url()
|
||||
{
|
||||
// Return the current base URL of window's associated Document.
|
||||
return m_window->associated_document().base_url();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:concept-settings-object-origin
|
||||
URL::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-policy-container
|
||||
PolicyContainer WindowEnvironmentSettingsObject::policy_container()
|
||||
{
|
||||
// Return the policy container of window's associated Document.
|
||||
return m_window->associated_document().policy_container();
|
||||
}
|
||||
|
||||
// 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.
|
||||
return CanUseCrossOriginIsolatedAPIs::Yes;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class WindowEnvironmentSettingsObject final : public EnvironmentSettingsObject {
|
||||
JS_CELL(WindowEnvironmentSettingsObject, EnvironmentSettingsObject);
|
||||
JS_DECLARE_ALLOCATOR(WindowEnvironmentSettingsObject);
|
||||
|
||||
public:
|
||||
static void setup(Page&, URL::URL const& creation_url, NonnullOwnPtr<JS::ExecutionContext>, JS::GCPtr<Environment>, URL::URL top_level_creation_url, URL::Origin top_level_origin);
|
||||
|
||||
virtual ~WindowEnvironmentSettingsObject() override;
|
||||
|
||||
virtual JS::GCPtr<DOM::Document> responsible_document() override;
|
||||
virtual String api_url_character_encoding() override;
|
||||
virtual URL::URL api_base_url() override;
|
||||
virtual URL::Origin origin() override;
|
||||
virtual PolicyContainer policy_container() override;
|
||||
virtual CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() override;
|
||||
|
||||
private:
|
||||
WindowEnvironmentSettingsObject(Window&, NonnullOwnPtr<JS::ExecutionContext>);
|
||||
|
||||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||||
|
||||
JS::GCPtr<Window> m_window;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Ledbetter <timledbetter@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/PrincipalHostDefined.h>
|
||||
#include <LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.h>
|
||||
#include <LibWeb/HTML/WorkerGlobalScope.h>
|
||||
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
|
||||
#include <WebWorker/DedicatedWorkerHost.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(WorkerEnvironmentSettingsObject);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/workers.html#set-up-a-worker-environment-settings-object
|
||||
JS::NonnullGCPtr<WorkerEnvironmentSettingsObject> WorkerEnvironmentSettingsObject::setup(JS::NonnullGCPtr<Page> page, NonnullOwnPtr<JS::ExecutionContext> execution_context, SerializedEnvironmentSettingsObject const& outside_settings, HighResolutionTime::DOMHighResTimeStamp unsafe_worker_creation_time)
|
||||
{
|
||||
(void)unsafe_worker_creation_time;
|
||||
|
||||
// 1. Let inherited origin be outside settings's origin.
|
||||
auto inherited_origin = outside_settings.origin;
|
||||
|
||||
// 2. Let realm be the value of execution context's Realm component.
|
||||
auto realm = execution_context->realm;
|
||||
VERIFY(realm);
|
||||
|
||||
// 3. Let worker global scope be realm's global object.
|
||||
auto& worker = verify_cast<HTML::WorkerGlobalScope>(realm->global_object());
|
||||
|
||||
// 4. 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 = realm->heap().allocate<WorkerEnvironmentSettingsObject>(*realm, move(execution_context), worker);
|
||||
settings_object->target_browsing_context = nullptr;
|
||||
settings_object->m_origin = move(inherited_origin);
|
||||
|
||||
// FIXME: 5. Set settings object's id to a new unique opaque string, creation URL to worker global scope's url, top-level creation URL to null, target browsing context to null, and active service worker to null.
|
||||
// 6. If worker global scope is a DedicatedWorkerGlobalScope object, then set settings object's top-level origin to outside settings's top-level origin.
|
||||
if (is<WebWorker::DedicatedWorkerHost>(worker)) {
|
||||
settings_object->top_level_origin = outside_settings.top_level_origin;
|
||||
}
|
||||
// FIXME: 7. Otherwise, set settings object's top-level origin to an implementation-defined value.
|
||||
|
||||
// 8. Set realm's [[HostDefined]] field to settings object.
|
||||
auto intrinsics = realm->heap().allocate<Bindings::Intrinsics>(*realm, *realm);
|
||||
auto host_defined = make<Bindings::PrincipalHostDefined>(settings_object, intrinsics, page);
|
||||
realm->set_host_defined(move(host_defined));
|
||||
|
||||
// Non-Standard: We cannot fully initialize worker object until *after* the we set up
|
||||
// the realm's [[HostDefined]] internal slot as the internal slot contains the web platform intrinsics
|
||||
worker.initialize_web_interfaces({});
|
||||
|
||||
// 9. Return settings object.
|
||||
return settings_object;
|
||||
}
|
||||
|
||||
URL::URL WorkerEnvironmentSettingsObject::api_base_url()
|
||||
{
|
||||
// Return worker global scope's url.
|
||||
return m_global_scope->url();
|
||||
}
|
||||
|
||||
URL::Origin WorkerEnvironmentSettingsObject::origin()
|
||||
{
|
||||
// FIXME: Return a unique opaque origin if worker global scope's url's scheme is "data", and inherited origin otherwise.
|
||||
return m_origin;
|
||||
}
|
||||
|
||||
PolicyContainer WorkerEnvironmentSettingsObject::policy_container()
|
||||
{
|
||||
// Return worker global scope's policy container.
|
||||
return m_global_scope->policy_container();
|
||||
}
|
||||
|
||||
CanUseCrossOriginIsolatedAPIs WorkerEnvironmentSettingsObject::cross_origin_isolated_capability()
|
||||
{
|
||||
// FIXME: Return worker global scope's cross-origin isolated capability.
|
||||
return CanUseCrossOriginIsolatedAPIs::No;
|
||||
}
|
||||
|
||||
void WorkerEnvironmentSettingsObject::visit_edges(JS::Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_global_scope);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Ben Abraham <ben.d.abraham@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class WorkerEnvironmentSettingsObject final
|
||||
: public EnvironmentSettingsObject {
|
||||
JS_CELL(WorkerEnvironmentSettingsObject, EnvironmentSettingsObject);
|
||||
JS_DECLARE_ALLOCATOR(WorkerEnvironmentSettingsObject);
|
||||
|
||||
public:
|
||||
WorkerEnvironmentSettingsObject(NonnullOwnPtr<JS::ExecutionContext> execution_context, JS::NonnullGCPtr<WorkerGlobalScope> global_scope)
|
||||
: EnvironmentSettingsObject(move(execution_context))
|
||||
, m_global_scope(global_scope)
|
||||
{
|
||||
}
|
||||
|
||||
static JS::NonnullGCPtr<WorkerEnvironmentSettingsObject> setup(JS::NonnullGCPtr<Page> page, NonnullOwnPtr<JS::ExecutionContext> execution_context, SerializedEnvironmentSettingsObject const& outside_settings, HighResolutionTime::DOMHighResTimeStamp unsafe_worker_creation_time);
|
||||
|
||||
virtual ~WorkerEnvironmentSettingsObject() override = default;
|
||||
|
||||
JS::GCPtr<DOM::Document> responsible_document() override { return nullptr; }
|
||||
String api_url_character_encoding() override { return m_api_url_character_encoding; }
|
||||
URL::URL api_base_url() override;
|
||||
URL::Origin origin() override;
|
||||
PolicyContainer policy_container() override;
|
||||
CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() override;
|
||||
|
||||
private:
|
||||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||||
|
||||
String m_api_url_character_encoding;
|
||||
URL::Origin m_origin;
|
||||
|
||||
JS::NonnullGCPtr<WorkerGlobalScope> m_global_scope;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue