Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibWeb+LibJS: Implement support for module scripts #15275

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Userland/Libraries/LibJS/CyclicModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

namespace JS {

CyclicModule::CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<ModuleRequest> requested_modules)
: Module(realm, filename)
CyclicModule::CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<ModuleRequest> requested_modules, Script::HostDefined* host_defined)
: Module(realm, filename, host_defined)
, m_requested_modules(move(requested_modules))
, m_has_top_level_await(has_top_level_await)
{
Expand Down
4 changes: 3 additions & 1 deletion Userland/Libraries/LibJS/CyclicModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ class CyclicModule : public Module {
virtual ThrowCompletionOr<void> link(VM& vm) override;
virtual ThrowCompletionOr<Promise*> evaluate(VM& vm) override;

Vector<ModuleRequest> const& requested_modules() const { return m_requested_modules; }

protected:
CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<ModuleRequest> requested_modules);
CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<ModuleRequest> requested_modules, Script::HostDefined* host_defined);

virtual void visit_edges(Cell::Visitor&) override;

Expand Down
5 changes: 4 additions & 1 deletion Userland/Libraries/LibJS/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@

namespace JS {

Module::Module(Realm& realm, String filename)
Module::Module(Realm& realm, String filename, Script::HostDefined* host_defined)
: m_realm(realm)
, m_host_defined(host_defined)
, m_filename(move(filename))
{
}
Expand All @@ -25,6 +26,8 @@ void Module::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_realm);
visitor.visit(m_environment);
visitor.visit(m_namespace);
if (m_host_defined)
m_host_defined->visit_host_defined_self(visitor);
}

// 16.2.1.5.1.1 InnerModuleLinking ( module, stack, index ), https://tc39.es/ecma262/#sec-InnerModuleLinking
Expand Down
12 changes: 8 additions & 4 deletions Userland/Libraries/LibJS/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <LibJS/Heap/GCPtr.h>
#include <LibJS/Runtime/Environment.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Script.h>

namespace JS {

Expand Down Expand Up @@ -68,6 +69,8 @@ class Module : public Cell {

Environment* environment() { return m_environment; }

Script::HostDefined* host_defined() const { return m_host_defined; }

ThrowCompletionOr<Object*> get_module_namespace(VM& vm);

virtual ThrowCompletionOr<void> link(VM& vm) = 0;
Expand All @@ -80,7 +83,7 @@ class Module : public Cell {
virtual ThrowCompletionOr<u32> inner_module_evaluation(VM& vm, Vector<Module*>& stack, u32 index);

protected:
Module(Realm&, String filename);
Module(Realm&, String filename, Script::HostDefined* host_defined = nullptr);

virtual void visit_edges(Cell::Visitor&) override;

Expand All @@ -97,9 +100,10 @@ class Module : public Cell {
// destroy the VM but keep the modules this should not happen. Because VM
// stores modules with a RefPtr we cannot just store the VM as that leads to
// cycles.
GCPtr<Realm> m_realm; // [[Realm]]
GCPtr<Environment> m_environment; // [[Environment]]
GCPtr<Object> m_namespace; // [[Namespace]]
GCPtr<Realm> m_realm; // [[Realm]]
GCPtr<Environment> m_environment; // [[Environment]]
GCPtr<Object> m_namespace; // [[Namespace]]
Script::HostDefined* m_host_defined { nullptr }; // [[HostDefined]]
networkException marked this conversation as resolved.
Show resolved Hide resolved

// Needed for potential lookups of modules.
String m_filename;
Expand Down
7 changes: 7 additions & 0 deletions Userland/Libraries/LibJS/Runtime/ModuleEnvironment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,11 @@ Optional<ModuleEnvironment::BindingAndIndex> ModuleEnvironment::find_binding_and
return DeclarativeEnvironment::find_binding_and_index(name);
}

void ModuleEnvironment::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
for (auto& indirect_binding : m_indirect_bindings)
visitor.visit(indirect_binding.module);
}

}
2 changes: 2 additions & 0 deletions Userland/Libraries/LibJS/Runtime/ModuleEnvironment.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class ModuleEnvironment final : public DeclarativeEnvironment {
private:
explicit ModuleEnvironment(Environment* outer_environment);

virtual void visit_edges(Visitor&) override;

struct IndirectBinding {
FlyString name;
Module* module;
Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibJS/Script.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Script final : public Cell {
Realm& realm() { return *m_realm; }
Program const& parse_node() const { return *m_parse_node; }

HostDefined* host_defined() { return m_host_defined; }
HostDefined* host_defined() const { return m_host_defined; }
StringView filename() const { return m_filename; }

private:
Expand Down
7 changes: 4 additions & 3 deletions Userland/Libraries/LibJS/SourceTextModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ static Vector<ModuleRequest> module_requests(Program& program, Vector<String> co
return requested_modules_in_source_order;
}

SourceTextModule::SourceTextModule(Realm& realm, StringView filename, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<ModuleRequest> requested_modules,
SourceTextModule::SourceTextModule(Realm& realm, StringView filename, Script::HostDefined* host_defined, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<ModuleRequest> requested_modules,
Vector<ImportEntry> import_entries, Vector<ExportEntry> local_export_entries,
Vector<ExportEntry> indirect_export_entries, Vector<ExportEntry> star_export_entries,
RefPtr<ExportStatement> default_export)
: CyclicModule(realm, filename, has_top_level_await, move(requested_modules))
: CyclicModule(realm, filename, has_top_level_await, move(requested_modules), host_defined)
, m_ecmascript_code(move(body))
, m_execution_context(realm.heap())
, m_import_entries(move(import_entries))
Expand All @@ -120,7 +120,7 @@ void SourceTextModule::visit_edges(Cell::Visitor& visitor)
}

// 16.2.1.6.1 ParseModule ( sourceText, realm, hostDefined ), https://tc39.es/ecma262/#sec-parsemodule
Result<NonnullGCPtr<SourceTextModule>, Vector<Parser::Error>> SourceTextModule::parse(StringView source_text, Realm& realm, StringView filename)
Result<NonnullGCPtr<SourceTextModule>, Vector<Parser::Error>> SourceTextModule::parse(StringView source_text, Realm& realm, StringView filename, Script::HostDefined* host_defined)
{
// 1. Let body be ParseText(sourceText, Module).
auto parser = Parser(Lexer(source_text, filename), Program::Type::Module);
Expand Down Expand Up @@ -248,6 +248,7 @@ Result<NonnullGCPtr<SourceTextModule>, Vector<Parser::Error>> SourceTextModule::
return NonnullGCPtr(*realm.heap().allocate_without_realm<SourceTextModule>(
realm,
filename,
host_defined,
async,
move(body),
move(requested_modules),
Expand Down
4 changes: 2 additions & 2 deletions Userland/Libraries/LibJS/SourceTextModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class SourceTextModule final : public CyclicModule {
using ImportEntry = ImportStatement::ImportEntry;
using ExportEntry = ExportStatement::ExportEntry;

static Result<NonnullGCPtr<SourceTextModule>, Vector<Parser::Error>> parse(StringView source_text, Realm&, StringView filename = {});
static Result<NonnullGCPtr<SourceTextModule>, Vector<Parser::Error>> parse(StringView source_text, Realm&, StringView filename = {}, Script::HostDefined* host_defined = nullptr);

Program const& parse_node() const { return *m_ecmascript_code; }

Expand All @@ -37,7 +37,7 @@ class SourceTextModule final : public CyclicModule {
virtual ThrowCompletionOr<void> execute_module(VM& vm, GCPtr<PromiseCapability> capability) override;

private:
SourceTextModule(Realm&, StringView filename, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<ModuleRequest> requested_modules,
SourceTextModule(Realm&, StringView filename, Script::HostDefined* host_defined, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<ModuleRequest> requested_modules,
Vector<ImportEntry> import_entries, Vector<ExportEntry> local_export_entries,
Vector<ExportEntry> indirect_export_entries, Vector<ExportEntry> star_export_entries,
RefPtr<ExportStatement> default_export);
Expand Down
58 changes: 55 additions & 3 deletions Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2021-2022, Andreas Kling <[email protected]>
* Copyright (c) 2021, Luke Wilde <[email protected]>
* Copyright (c) 2022, networkException <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Expand All @@ -20,6 +21,7 @@
#include <LibWeb/HTML/Scripting/ClassicScript.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
#include <LibWeb/HTML/Scripting/Fetching.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/WindowProxy.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
Expand Down Expand Up @@ -306,10 +308,60 @@ JS::VM& main_thread_vm()
// FIXME: Implement 8.1.5.5.1 HostGetImportMetaProperties(moduleRecord), https://html.spec.whatwg.org/multipage/webappapis.html#hostgetimportmetaproperties
// FIXME: Implement 8.1.5.5.2 HostImportModuleDynamically(referencingScriptOrModule, moduleRequest, promiseCapability), https://html.spec.whatwg.org/multipage/webappapis.html#hostimportmoduledynamically(referencingscriptormodule,-modulerequest,-promisecapability)
// FIXME: Implement 8.1.5.5.3 HostResolveImportedModule(referencingScriptOrModule, moduleRequest), https://html.spec.whatwg.org/multipage/webappapis.html#hostresolveimportedmodule(referencingscriptormodule,-modulerequest)
// FIXME: Implement 8.1.5.5.4 HostGetSupportedImportAssertions(), https://html.spec.whatwg.org/multipage/webappapis.html#hostgetsupportedimportassertions

vm->host_resolve_imported_module = [](JS::ScriptOrModule, JS::ModuleRequest const&) -> JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Module>> {
return vm->throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "Modules in the browser");
// 8.1.5.5.4 HostGetSupportedImportAssertions(), https://html.spec.whatwg.org/multipage/webappapis.html#hostgetsupportedimportassertions
vm->host_get_supported_import_assertions = []() -> Vector<String> {
// 1. Return « "type" ».
return { "type"sv };
};

// 8.1.5.5.3 HostResolveImportedModule(referencingScriptOrModule, moduleRequest), https://html.spec.whatwg.org/multipage/webappapis.html#hostresolveimportedmodule(referencingscriptormodule,-modulerequest)
vm->host_resolve_imported_module = [](JS::ScriptOrModule const& referencing_string_or_module, JS::ModuleRequest const& module_request) -> JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Module>> {
// 1. Let settings object be the current settings object.
auto* settings_object = &HTML::current_settings_object();

// 2. Let base URL be settings object's API base URL.
auto base_url = settings_object->api_base_url();

// 3. If referencingScriptOrModule is not null, then:
if (!referencing_string_or_module.has<Empty>()) {
// 1. Let referencing script be referencingScriptOrModule.[[HostDefined]].
auto const& referencing_script = verify_cast<HTML::Script>(referencing_string_or_module.has<JS::NonnullGCPtr<JS::Script>>() ? referencing_string_or_module.get<JS::NonnullGCPtr<JS::Script>>()->host_defined() : referencing_string_or_module.get<JS::NonnullGCPtr<JS::Module>>()->host_defined());

// 2. Set settings object to referencing script's settings object.
settings_object = &referencing_script->settings_object();

// 3. Set base URL to referencing script's base URL.
base_url = referencing_script->base_url();

// 4. Assert: base URL is not null, as referencing script is a classic script or a JavaScript module script.
VERIFY(base_url.is_valid());
}

// 4. Let moduleMap be settings object's module map.
auto& module_map = settings_object->module_map();

// 5. Let url be the result of resolving a module specifier given base URL and moduleRequest.[[Specifier]].
auto url = HTML::resolve_module_specifier(module_request, base_url);

// 6. Assert: url is never failure, because resolving a module specifier must have been previously successful
// with these same two arguments (either while creating the corresponding module script, or in fetch an import() module script graph).
VERIFY(url.is_valid());

// 7. Let moduleType be the result of running the module type from module request steps given moduleRequest.
auto module_type = HTML::module_type_from_module_request(module_request);

// 8. Let resolved module script be moduleMap[(url, moduleType)]. (This entry must exist for us to have gotten to this point.)
auto resolved_module = module_map.get(url, module_type).value();

// 9. Assert: resolved module script is a module script (i.e., is not null or "fetching").
VERIFY(resolved_module.type == HTML::ModuleMap::EntryType::ModuleScript);

// 10. Assert: resolved module script's record is not null.
VERIFY(resolved_module.module_script->record());

// 11. Return resolved module script's record.
return JS::NonnullGCPtr(*resolved_module.module_script->record());
};

// NOTE: We push a dummy execution context onto the JS execution context stack,
Expand Down
3 changes: 3 additions & 0 deletions Userland/Libraries/LibWeb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ set(SOURCES
HTML/Scripting/ClassicScript.cpp
HTML/Scripting/Environments.cpp
HTML/Scripting/ExceptionReporter.cpp
HTML/Scripting/Fetching.cpp
HTML/Scripting/ModuleMap.cpp
HTML/Scripting/ModuleScript.cpp
HTML/Scripting/Script.cpp
HTML/Scripting/WindowEnvironmentSettingsObject.cpp
HTML/Storage.cpp
Expand Down
50 changes: 30 additions & 20 deletions Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <[email protected]>
* Copyright (c) 2022, networkException <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Expand All @@ -15,8 +16,10 @@
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/HTMLScriptElement.h>
#include <LibWeb/HTML/Scripting/ClassicScript.h>
#include <LibWeb/HTML/Scripting/Fetching.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/MimeSniff/MimeType.h>

namespace Web::HTML {

Expand Down Expand Up @@ -46,6 +49,8 @@ void HTMLScriptElement::begin_delaying_document_load_event(DOM::Document& docume
// https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block
void HTMLScriptElement::execute_script()
{
// FIXME: Update the steps to match current spec.

// 1. Let document be scriptElement's node document.
JS::NonnullGCPtr<DOM::Document> node_document = document();

Expand Down Expand Up @@ -95,8 +100,8 @@ void HTMLScriptElement::execute_script()
// 1. Assert: document's currentScript attribute is null.
VERIFY(!document().current_script());

// FIXME: 2. Run the module script given by the script's script for scriptElement.
TODO();
// 2. Run the module script given by the script's script for scriptElement.
(void)verify_cast<JavaScriptModuleScript>(*m_script).run();
}

// 6. Decrement the ignore-destructive-writes counter of document, if it was incremented in the earlier step.
Expand All @@ -108,16 +113,11 @@ void HTMLScriptElement::execute_script()
dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::load));
}

// https://mimesniff.spec.whatwg.org/#javascript-mime-type-essence-match
static bool is_javascript_mime_type_essence_match(String const& string)
{
auto lowercase_string = string.to_lowercase();
return lowercase_string.is_one_of("application/ecmascript", "application/javascript", "application/x-ecmascript", "application/x-javascript", "text/ecmascript", "text/javascript", "text/javascript1.0", "text/javascript1.1", "text/javascript1.2", "text/javascript1.3", "text/javascript1.4", "text/javascript1.5", "text/jscript", "text/livescript", "text/x-ecmascript", "text/x-javascript");
}

// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
void HTMLScriptElement::prepare_script()
{
// FIXME: Update the steps to match current spec.

// 1. If the script element is marked as having "already started", then return. The script is not executed.
if (m_already_started) {
dbgln("HTMLScriptElement: Refusing to run script because it has already started.");
Expand Down Expand Up @@ -169,7 +169,7 @@ void HTMLScriptElement::prepare_script()
}

// Determine the script's type as follows:
if (is_javascript_mime_type_essence_match(script_block_type.trim(Infra::ASCII_WHITESPACE))) {
if (MimeSniff::is_javascript_mime_type_essence_match(script_block_type.trim(Infra::ASCII_WHITESPACE))) {
// - If the script block's type string with leading and trailing ASCII whitespace stripped is a JavaScript MIME type essence match, the script's type is "classic".
m_script_type = ScriptType::Classic;
} else if (script_block_type.equals_ignoring_case("module"sv)) {
Expand Down Expand Up @@ -299,32 +299,42 @@ void HTMLScriptElement::prepare_script()
auto resource = ResourceLoader::the().load_resource(Resource::Type::Generic, request);
set_resource(resource);
} else if (m_script_type == ScriptType::Module) {
// FIXME: -> "module"
// Fetch an external module script graph given url, settings object, and options.
// Fetch an external module script graph given url, settings object, options, and onComplete.
// FIXME: Pass options.
fetch_external_module_script_graph(url, document().relevant_settings_object(), [this](auto const* result) {
// 1. Mark as ready el given result.
m_script = result;
script_became_ready();
});
}
} else {
// 27. If the element does not have a src content attribute, run these substeps:

// FIXME: 1. Let base URL be the script element's node document's document base URL.
// 1. Let base URL be the script element's node document's document base URL.
auto base_url = m_document->base_url();

// 2. Switch on the script's type:
if (m_script_type == ScriptType::Classic) {
// -> "classic"
// 1. Let script be the result of creating a classic script using source text, settings object, base URL, and options.

// FIXME: Pass settings, base URL and options.
auto script = ClassicScript::create(m_document->url().to_string(), source_text, document().relevant_settings_object(), AK::URL(), m_source_line_number);
// FIXME: Pass options.
auto script = ClassicScript::create(m_document->url().to_string(), source_text, document().relevant_settings_object(), base_url, m_source_line_number);

// 2. Set the script's script to script.
m_script = script;

// 3. The script is ready.
script_became_ready();
} else if (m_script_type == ScriptType::Module) {
// FIXME: -> "module"
// 1. Fetch an inline module script graph, given source text, base URL, settings object, and options.
// When this asynchronously completes, set the script's script to the result. At that time, the script is ready.
TODO();
// FIXME: 1. Set el's delaying the load event to true.

// 2. Fetch an inline module script graph, given source text, base URL, settings object, options, and with the following steps given result:
// FIXME: Pass options
fetch_inline_module_script_graph(m_document->url().to_string(), source_text, base_url, document().relevant_settings_object(), [this](auto const* result) {
// 1. Mark as ready el given result.
m_script = result;
script_became_ready();
});
}
}

Expand Down
5 changes: 0 additions & 5 deletions Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,4 @@ void ClassicScript::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_script_record);
}

void ClassicScript::visit_host_defined_self(Cell::Visitor& visitor)
{
visitor.visit(this);
}

}
Loading