mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibWeb: Support CSS modules
This adds support for importing CSS stylesheets from CSS files in javascript.
This commit is contained in:
committed by
Shannon Booth
parent
2362a65e3b
commit
f1d3244b22
Notes:
github-actions[bot]
2026-04-03 19:22:27 +00:00
Author: https://github.com/skyz1 Commit: https://github.com/LadybirdBrowser/ladybird/commit/f1d3244b225 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6029 Reviewed-by: https://github.com/AtkinsSJ Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/konradekk Reviewed-by: https://github.com/shannonbooth ✅
@@ -49,7 +49,6 @@ public:
|
||||
virtual bool is_script() const { return false; }
|
||||
virtual bool is_classic_script() const { return false; }
|
||||
virtual bool is_module_script() const { return false; }
|
||||
virtual bool is_javascript_module_script() const { return false; }
|
||||
};
|
||||
|
||||
virtual ~Script() override;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
namespace JS {
|
||||
|
||||
// 16.2.1.8 Synthetic Module Records, https://tc39.es/ecma262/#sec-synthetic-module-records
|
||||
class SyntheticModule final : public Module {
|
||||
class JS_API SyntheticModule final : public Module {
|
||||
GC_CELL(SyntheticModule, Module);
|
||||
GC_DECLARE_ALLOCATOR(SyntheticModule);
|
||||
|
||||
|
||||
@@ -357,12 +357,9 @@ void initialize_main_thread_vm(AgentType type)
|
||||
if (is<HTML::ClassicScript>(script)) {
|
||||
script_execution_context->script_or_module = GC::Ref<JS::Script>(*as<HTML::ClassicScript>(script)->script_record());
|
||||
} else if (is<HTML::ModuleScript>(script)) {
|
||||
if (is<HTML::JavaScriptModuleScript>(script)) {
|
||||
script_execution_context->script_or_module = GC::Ref<JS::Module>(*as<HTML::JavaScriptModuleScript>(script)->record());
|
||||
} else {
|
||||
// NOTE: Handle CSS and JSON module scripts once we have those.
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
script_execution_context->script_or_module = as<HTML::ModuleScript>(script)->record().visit(
|
||||
[](Empty) -> JS::ScriptOrModule { return {}; },
|
||||
[](auto& module) -> JS::ScriptOrModule { return GC::Ref<JS::Module> { module }; });
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
@@ -620,7 +617,7 @@ void initialize_main_thread_vm(AgentType type)
|
||||
}
|
||||
// 4. Otherwise, set completion to NormalCompletion(moduleScript's record).
|
||||
else {
|
||||
module = static_cast<HTML::JavaScriptModuleScript&>(*module_script).record();
|
||||
module = static_cast<HTML::ModuleScript&>(*module_script).record().visit([](Empty) -> GC::Ptr<JS::Module> { return nullptr; }, [](GC::Ref<JS::Module> module) -> GC::Ptr<JS::Module> { return module; });
|
||||
return JS::ThrowCompletionOr<GC::Ref<JS::Module>>(*module);
|
||||
}
|
||||
}();
|
||||
|
||||
@@ -39,8 +39,8 @@ WebIDL::ExceptionOr<GC::Ref<CSSStyleSheet>> CSSStyleSheet::construct_impl(JS::Re
|
||||
// 1. Construct a new CSSStyleSheet object sheet.
|
||||
auto sheet = create(realm, CSSRuleList::create(realm), CSS::MediaList::create(realm, {}), {});
|
||||
|
||||
// 2. Set sheet’s location to the base URL of the associated Document for the current principal global object.
|
||||
auto associated_document = as<HTML::Window>(HTML::current_principal_global_object()).document();
|
||||
// 2. Set sheet’s location to the base URL of the associated Document for the current global object.
|
||||
auto associated_document = as<HTML::Window>(realm.global_object()).document();
|
||||
sheet->set_location(associated_document->base_url());
|
||||
|
||||
// 3. Set sheet’s stylesheet base URL to the baseURL attribute value from options.
|
||||
|
||||
@@ -178,7 +178,7 @@ void HTMLScriptElement::execute_script()
|
||||
VERIFY(document->current_script() == nullptr);
|
||||
|
||||
// 2. Run the module script given by el's result.
|
||||
(void)as<JavaScriptModuleScript>(*m_result.get<GC::Ref<Script>>()).run();
|
||||
(void)as<ModuleScript>(*m_result.get<GC::Ref<Script>>()).run();
|
||||
}
|
||||
// -> "importmap"
|
||||
else if (m_script_type == ScriptType::ImportMap) {
|
||||
|
||||
@@ -651,7 +651,7 @@ WebIDL::ExceptionOr<void> fetch_worklet_module_worker_script_graph(URL::URL cons
|
||||
}
|
||||
|
||||
// 2. Fetch the descendants of and link result given fetchClient, destination, and onComplete. If performFetch was given, pass it along as well.
|
||||
fetch_descendants_of_and_link_a_module_script(realm, as<JavaScriptModuleScript>(*result), fetch_client, destination, move(perform_fetch), on_complete);
|
||||
fetch_descendants_of_and_link_a_module_script(realm, as<ModuleScript>(*result), fetch_client, destination, move(perform_fetch), on_complete);
|
||||
});
|
||||
|
||||
// 2. Fetch a single module script given url, fetchClient, destination, options, settingsObject's realm, "client", true,
|
||||
@@ -768,7 +768,7 @@ void fetch_single_module_script(JS::Realm& realm,
|
||||
auto mime_type = Fetch::Infrastructure::extract_mime_type(response->header_list());
|
||||
|
||||
// 3. Let moduleScript be null.
|
||||
GC::Ptr<JavaScriptModuleScript> module_script;
|
||||
GC::Ptr<ModuleScript> module_script;
|
||||
|
||||
// FIXME: 4. Let referrerPolicy be the result of parsing the `Referrer-Policy` header given response. [REFERRERPOLICY]
|
||||
// FIXME: 5. If referrerPolicy is not the empty string, set options's referrer policy to referrerPolicy.
|
||||
@@ -805,18 +805,21 @@ void fetch_single_module_script(JS::Realm& realm,
|
||||
module_type_string = move(module_type_string),
|
||||
on_complete_root = move(on_complete_root),
|
||||
realm_root = move(realm_root)](auto* parsed, auto source_code) mutable {
|
||||
auto module_script = JavaScriptModuleScript::create_from_pre_parsed(url_string, move(source_code), *realm_root, move(response_url), parsed).release_value_but_fixme_should_propagate_errors();
|
||||
auto module_script = ModuleScript::create_from_pre_parsed(url_string, move(source_code), *realm_root, move(response_url), parsed).release_value_but_fixme_should_propagate_errors();
|
||||
auto& mm = module_map_of_realm(*realm_root);
|
||||
mm.set(url, module_type_string, { ModuleMap::EntryType::ModuleScript, module_script });
|
||||
on_complete_root->function()(module_script);
|
||||
});
|
||||
return;
|
||||
}
|
||||
module_script = JavaScriptModuleScript::create(url.to_byte_string(), source_text, module_map_realm, response->url().value_or({})).release_value_but_fixme_should_propagate_errors();
|
||||
module_script = ModuleScript::create_a_javascript_module_script(url.to_byte_string(), source_text, module_map_realm, response->url().value_or({})).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
// FIXME: 3. If the MIME type essence of mimeType is "text/css" and moduleType is "css", then set moduleScript to
|
||||
// the result of creating a CSS module script given sourceText and moduleMapRealm.
|
||||
// 3. If the MIME type essence of mimeType is "text/css" and moduleType is "css", then set moduleScript to
|
||||
// the result of creating a CSS module script given sourceText and moduleMapRealm.
|
||||
if (mime_type.has_value() && mime_type->essence() == "text/css"sv && module_type == "css")
|
||||
module_script = ModuleScript::create_a_css_module_script(url.to_byte_string(), source_text, module_map_realm).release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
// FIXME: 4. If mimeType is a JSON MIME type and moduleType is "json", then set moduleScript to the result of
|
||||
// creating a JSON module script given sourceText and moduleMapRealm.
|
||||
}
|
||||
@@ -847,7 +850,7 @@ void fetch_external_module_script_graph(JS::Realm& realm, URL::URL const& url, E
|
||||
}
|
||||
|
||||
// 2. Fetch the descendants of and link result given settingsObject, "script", and onComplete.
|
||||
auto& module_script = as<JavaScriptModuleScript>(*result);
|
||||
auto& module_script = as<ModuleScript>(*result);
|
||||
fetch_descendants_of_and_link_a_module_script(realm, module_script, settings_object, Fetch::Infrastructure::Request::Destination::Script, nullptr, on_complete);
|
||||
});
|
||||
|
||||
@@ -859,7 +862,7 @@ void fetch_external_module_script_graph(JS::Realm& realm, URL::URL const& url, E
|
||||
void fetch_inline_module_script_graph(JS::Realm& realm, ByteString const& filename, ByteString const& source_text, URL::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete)
|
||||
{
|
||||
// 1. Let script be the result of creating a JavaScript module script using sourceText, settingsObject's realm, baseURL, and options.
|
||||
auto script = JavaScriptModuleScript::create(filename, source_text.view(), settings_object.realm(), base_url).release_value_but_fixme_should_propagate_errors();
|
||||
auto script = ModuleScript::create_a_javascript_module_script(filename, source_text.view(), settings_object.realm(), base_url).release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
// 2. Fetch the descendants of and link script, given settingsObject, "script", and onComplete.
|
||||
fetch_descendants_of_and_link_a_module_script(realm, *script, settings_object, Fetch::Infrastructure::Request::Destination::Script, nullptr, on_complete);
|
||||
@@ -899,17 +902,17 @@ void fetch_single_imported_module_script(JS::Realm& realm,
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-and-link-a-module-script
|
||||
void fetch_descendants_of_and_link_a_module_script(JS::Realm& realm,
|
||||
JavaScriptModuleScript& module_script,
|
||||
ModuleScript& module_script,
|
||||
EnvironmentSettingsObject& fetch_client,
|
||||
Fetch::Infrastructure::Request::Destination destination,
|
||||
PerformTheFetchHook perform_fetch,
|
||||
OnFetchScriptComplete on_complete)
|
||||
{
|
||||
// 1. Let record be moduleScript's record.
|
||||
auto* record = module_script.record();
|
||||
auto record = module_script.record();
|
||||
|
||||
// 2. If record is null, then:
|
||||
if (!record) {
|
||||
if (record.has<Empty>()) {
|
||||
// 1. Set moduleScript's error to rethrow to moduleScript's parse error.
|
||||
module_script.set_error_to_rethrow(module_script.parse_error());
|
||||
|
||||
@@ -937,13 +940,17 @@ void fetch_descendants_of_and_link_a_module_script(JS::Realm& realm,
|
||||
prepare_to_run_callback(realm);
|
||||
|
||||
// 5. Let loadingPromise be record.LoadRequestedModules(state).
|
||||
auto& loading_promise = record->load_requested_modules(state);
|
||||
auto loading_promise = record.visit(
|
||||
[](Empty) -> GC::Ref<JS::PromiseCapability> { VERIFY_NOT_REACHED(); },
|
||||
[&](GC::Ref<JS::Module> module) { return GC::Ref { module->load_requested_modules(state) }; });
|
||||
|
||||
WebIDL::react_to_promise(loading_promise,
|
||||
// 6. Upon fulfillment of loadingPromise, run the following steps:
|
||||
GC::create_function(realm.heap(), [&realm, record, &module_script, on_complete](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
|
||||
// 1. Perform record.Link().
|
||||
auto linking_result = record->link(realm.vm());
|
||||
auto linking_result = record.visit(
|
||||
[](Empty) -> JS::ThrowCompletionOr<void> { VERIFY_NOT_REACHED(); },
|
||||
[&](GC::Ref<JS::Module> module) { return module->link(realm.vm()); });
|
||||
|
||||
// If this throws an exception, set result's error to rethrow to that exception.
|
||||
if (linking_result.is_throw_completion())
|
||||
|
||||
@@ -98,7 +98,7 @@ void fetch_external_module_script_graph(JS::Realm&, URL::URL const&, Environment
|
||||
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_and_link_a_module_script(JS::Realm&, JavaScriptModuleScript&, EnvironmentSettingsObject&, Fetch::Infrastructure::Request::Destination, PerformTheFetchHook, OnFetchScriptComplete on_complete);
|
||||
void fetch_descendants_of_and_link_a_module_script(JS::Realm&, ModuleScript&, EnvironmentSettingsObject&, Fetch::Infrastructure::Request::Destination, PerformTheFetchHook, OnFetchScriptComplete on_complete);
|
||||
|
||||
Fetch::Infrastructure::Request::Destination fetch_destination_from_module_type(Fetch::Infrastructure::Request::Destination, ByteString const&);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
|
||||
struct Entry {
|
||||
EntryType type;
|
||||
GC::Ptr<JavaScriptModuleScript> module_script;
|
||||
GC::Ptr<ModuleScript> module_script;
|
||||
};
|
||||
|
||||
using CallbackFunction = GC::Ref<GC::Function<void(Entry)>>;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/ModuleRequest.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
#include <LibWeb/CSS/CSSStyleSheet.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/Fetching.h>
|
||||
#include <LibWeb/HTML/Scripting/ModuleScript.h>
|
||||
@@ -14,25 +16,18 @@
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(JavaScriptModuleScript);
|
||||
GC_DEFINE_ALLOCATOR(ModuleScript);
|
||||
|
||||
ModuleScript::~ModuleScript() = default;
|
||||
|
||||
ModuleScript::ModuleScript(URL::URL base_url, ByteString filename, JS::Realm& realm)
|
||||
ModuleScript::ModuleScript(Optional<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<GC::Ptr<JavaScriptModuleScript>> JavaScriptModuleScript::create(ByteString const& filename, StringView source, JS::Realm& realm, URL::URL base_url)
|
||||
WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> ModuleScript::create_a_javascript_module_script(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))
|
||||
@@ -41,7 +36,7 @@ WebIDL::ExceptionOr<GC::Ptr<JavaScriptModuleScript>> JavaScriptModuleScript::cre
|
||||
// 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.create<JavaScriptModuleScript>(move(base_url), filename, realm);
|
||||
auto script = realm.create<ModuleScript>(move(base_url), filename, realm);
|
||||
|
||||
// FIXME: 5. Set script's fetch options to options.
|
||||
|
||||
@@ -71,9 +66,9 @@ WebIDL::ExceptionOr<GC::Ptr<JavaScriptModuleScript>> JavaScriptModuleScript::cre
|
||||
return script;
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<GC::Ptr<JavaScriptModuleScript>> JavaScriptModuleScript::create_from_pre_parsed(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, JS::Realm& realm, URL::URL base_url, JS::FFI::ParsedProgram* parsed)
|
||||
WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> ModuleScript::create_from_pre_parsed(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, JS::Realm& realm, URL::URL base_url, JS::FFI::ParsedProgram* parsed)
|
||||
{
|
||||
auto script = realm.create<JavaScriptModuleScript>(move(base_url), filename, realm);
|
||||
auto script = realm.create<ModuleScript>(move(base_url), filename, realm);
|
||||
|
||||
script->set_parse_error(JS::js_null());
|
||||
script->set_error_to_rethrow(JS::js_null());
|
||||
@@ -91,9 +86,41 @@ WebIDL::ExceptionOr<GC::Ptr<JavaScriptModuleScript>> JavaScriptModuleScript::cre
|
||||
return script;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-css-module-script
|
||||
// https://whatpr.org/html/9893/webappapis.html#creating-a-css-module-script
|
||||
WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> ModuleScript::create_a_css_module_script(ByteString const& filename, StringView source, JS::Realm& realm)
|
||||
{
|
||||
// 1. Let script be a new module script that this algorithm will subsequently initialize.
|
||||
// 2. Set script's realm to realm.
|
||||
// 3. Set script's base URL and fetch options to null.
|
||||
auto script = realm.create<ModuleScript>(Optional<URL::URL> {}, filename, realm);
|
||||
|
||||
// 4. 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());
|
||||
|
||||
// 5. Let sheet be the result of running the steps to create a constructed CSSStyleSheet with an empty dictionary as
|
||||
// the argument.
|
||||
auto sheet = TRY(CSS::CSSStyleSheet::construct_impl(realm));
|
||||
|
||||
// 6. Run the steps to synchronously replace the rules of a CSSStyleSheet on sheet given source.
|
||||
// If this throws an exception, catch it, and set script's parse error to that exception, and return script.
|
||||
if (auto result = sheet->replace_sync(source); result.is_error()) {
|
||||
auto throw_completion = Bindings::exception_to_throw_completion(realm.vm(), result.exception());
|
||||
script->set_parse_error(throw_completion.value());
|
||||
return script;
|
||||
}
|
||||
|
||||
// 7. Set script's record to the result of CreateDefaultExportSyntheticModule(sheet).
|
||||
script->m_record = JS::SyntheticModule::create_default_export_synthetic_module(realm, sheet, filename.view());
|
||||
|
||||
// 8. 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)
|
||||
JS::Promise* ModuleScript::run(PreventErrorReporting)
|
||||
{
|
||||
// 1. Let realm be the realm of script.
|
||||
auto& realm = this->realm();
|
||||
@@ -105,22 +132,25 @@ JS::Promise* JavaScriptModuleScript::run(PreventErrorReporting)
|
||||
return promise;
|
||||
}
|
||||
|
||||
// 3. Prepare to run script given realm.
|
||||
// FIXME: 3. Record module script execution start time given script.
|
||||
|
||||
// 4. Prepare to run script given realm.
|
||||
prepare_to_run_script(realm);
|
||||
|
||||
// 4. Let evaluationPromise be null.
|
||||
// 5. 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.
|
||||
// 6. 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:
|
||||
// 7. Otherwise:
|
||||
else {
|
||||
// 1. Let record be script's record.
|
||||
auto record = m_record;
|
||||
VERIFY(record);
|
||||
auto record = m_record.visit(
|
||||
[](Empty) -> GC::Ref<JS::Module> { VERIFY_NOT_REACHED(); },
|
||||
[](auto& module) -> GC::Ref<JS::Module> { return module; });
|
||||
|
||||
// NON-STANDARD: To ensure that LibJS can find the module on the stack, we push a new execution context.
|
||||
auto& stack = vm().interpreter_stack();
|
||||
@@ -128,7 +158,7 @@ JS::Promise* JavaScriptModuleScript::run(PreventErrorReporting)
|
||||
auto* module_execution_context = stack.allocate(0, ReadonlySpan<JS::Value> {}, 0);
|
||||
VERIFY(module_execution_context);
|
||||
module_execution_context->realm = &realm;
|
||||
module_execution_context->script_or_module = GC::Ref<JS::Module> { *record };
|
||||
module_execution_context->script_or_module = record;
|
||||
vm().push_execution_context(*module_execution_context);
|
||||
|
||||
// 2. Set evaluationPromise to record.Evaluate().
|
||||
@@ -151,19 +181,21 @@ JS::Promise* JavaScriptModuleScript::run(PreventErrorReporting)
|
||||
stack.deallocate(stack_mark);
|
||||
}
|
||||
|
||||
// FIXME: 7. If preventErrorReporting is false, then upon rejection of evaluationPromise with reason, report the exception given by reason for script.
|
||||
// FIXME: 8. 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.
|
||||
// 9. Clean up after running script with realm.
|
||||
clean_up_after_running_script(realm);
|
||||
|
||||
// 9. Return evaluationPromise.
|
||||
// 10. Return evaluationPromise.
|
||||
return evaluation_promise;
|
||||
}
|
||||
|
||||
void JavaScriptModuleScript::visit_edges(Cell::Visitor& visitor)
|
||||
void ModuleScript::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_record);
|
||||
m_record.visit(
|
||||
[&](Empty) {},
|
||||
[&](auto record) { visitor.visit(record); });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/SourceTextModule.h>
|
||||
#include <LibJS/SyntheticModule.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/HTML/Scripting/Script.h>
|
||||
|
||||
@@ -18,29 +19,21 @@ struct ParsedProgram;
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// FIXME: Support WebAssembly Module Record
|
||||
using ModuleScriptRecord = Variant<Empty, GC::Ref<JS::SourceTextModule>, GC::Ref<JS::SyntheticModule>>;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#module-script
|
||||
class ModuleScript : public Script {
|
||||
class WEB_API ModuleScript : public Script {
|
||||
GC_CELL(ModuleScript, Script);
|
||||
GC_DECLARE_ALLOCATOR(ModuleScript);
|
||||
|
||||
public:
|
||||
virtual ~ModuleScript() override;
|
||||
|
||||
protected:
|
||||
ModuleScript(URL::URL base_url, ByteString filename, JS::Realm&);
|
||||
|
||||
private:
|
||||
virtual bool is_module_script() const final { return true; }
|
||||
};
|
||||
|
||||
class WEB_API JavaScriptModuleScript final : public ModuleScript {
|
||||
GC_CELL(JavaScriptModuleScript, ModuleScript);
|
||||
GC_DECLARE_ALLOCATOR(JavaScriptModuleScript);
|
||||
|
||||
public:
|
||||
virtual ~JavaScriptModuleScript() override;
|
||||
|
||||
static WebIDL::ExceptionOr<GC::Ptr<JavaScriptModuleScript>> create(ByteString const& filename, StringView source, JS::Realm&, URL::URL base_url);
|
||||
static WebIDL::ExceptionOr<GC::Ptr<JavaScriptModuleScript>> create_from_pre_parsed(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, JS::Realm&, URL::URL base_url, JS::FFI::ParsedProgram* parsed);
|
||||
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create(ByteString const& filename, StringView source, JS::Realm&, URL::URL base_url);
|
||||
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_from_pre_parsed(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, JS::Realm&, URL::URL base_url, JS::FFI::ParsedProgram* parsed);
|
||||
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_a_javascript_module_script(ByteString const& filename, StringView source, JS::Realm&, URL::URL base_url);
|
||||
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_a_css_module_script(ByteString const& filename, StringView source, JS::Realm&);
|
||||
|
||||
enum class PreventErrorReporting {
|
||||
Yes,
|
||||
@@ -49,28 +42,24 @@ public:
|
||||
|
||||
JS::Promise* run(PreventErrorReporting = PreventErrorReporting::No);
|
||||
|
||||
JS::SourceTextModule const* record() const { return m_record.ptr(); }
|
||||
JS::SourceTextModule* record() { return m_record.ptr(); }
|
||||
ModuleScriptRecord record() const { return m_record; }
|
||||
|
||||
protected:
|
||||
JavaScriptModuleScript(URL::URL base_url, ByteString filename, JS::Realm&);
|
||||
ModuleScript(Optional<URL::URL> base_url, ByteString filename, JS::Realm&);
|
||||
|
||||
private:
|
||||
virtual bool is_javascript_module_script() const final { return true; }
|
||||
virtual bool is_module_script() const final { return true; }
|
||||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||||
|
||||
GC::Ptr<JS::SourceTextModule> m_record;
|
||||
ModuleScriptRecord 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;
|
||||
Function<void(ModuleScript const*)> m_completed_fetch_internal_callback;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool JS::Script::HostDefined::fast_is<Web::HTML::ModuleScript>() const { return is_module_script(); }
|
||||
|
||||
template<>
|
||||
inline bool JS::Script::HostDefined::fast_is<Web::HTML::JavaScriptModuleScript>() const { return is_javascript_module_script(); }
|
||||
|
||||
@@ -254,7 +254,7 @@ void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder mes
|
||||
if (auto* classic_script = as_if<Web::HTML::ClassicScript>(*script))
|
||||
(void)classic_script->run();
|
||||
else
|
||||
(void)as<Web::HTML::JavaScriptModuleScript>(*script).run();
|
||||
(void)as<Web::HTML::ModuleScript>(*script).run();
|
||||
|
||||
// FIXME: 11. Enable outside port's port message queue.
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ Text/input/wpt-import/css/cssom/getComputedStyle-insets-absolute.html
|
||||
Text/input/wpt-import/css/cssom/getComputedStyle-insets-relative.html
|
||||
Text/input/wpt-import/html/semantics/forms/the-input-element/cloning-steps.html
|
||||
Text/input/wpt-import/html/semantics/forms/the-input-element/show-picker-disabled-readonly.html
|
||||
Text/input/wpt-import/html/semantics/scripting-1/the-script-element/css-module/import-css-module-dynamic.html
|
||||
Text/input/wpt-import/html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html
|
||||
|
||||
; Unable to fetch the test resources JSON with CORS error.
|
||||
; Fails in other browsers too when loaded from file://.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Harness status: OK
|
||||
|
||||
Found 5 tests
|
||||
|
||||
5 Pass
|
||||
Pass A CSS Module should load
|
||||
Pass A large CSS Module should load
|
||||
Pass An @import CSS Module should not load, but should not throw an exception
|
||||
Pass A parse error should not prevent subsequent rules from being included in a CSS module
|
||||
Pass CSS module without type attribute should result in a fetch error
|
||||
@@ -0,0 +1,7 @@
|
||||
Harness status: OK
|
||||
|
||||
Found 2 tests
|
||||
|
||||
2 Pass
|
||||
Pass Load a CSS module with dynamic import()
|
||||
Pass Ensure that loading a CSS module with dymnamic import() fails without a type attribute
|
||||
@@ -0,0 +1,83 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<script src="../../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="test">I am a test div.</div>
|
||||
<div id="test2">I am a test div.</div>
|
||||
<div id="test3">I am a test div.</div>
|
||||
<div id="test3b">I am a test div.</div>
|
||||
<div id="test4">I am a test div.</div>
|
||||
<div id="test4b">I am a test div.</div>
|
||||
<script>
|
||||
window.errorCount = 0;
|
||||
window.onerror = (errorMsg, url, lineNumber, column, errorObj) => {
|
||||
window.errorCount++;
|
||||
};
|
||||
</script>
|
||||
<script type="module" onerror="unreachable()">
|
||||
import sheet from "./resources/basic.css" with { type: "css" };
|
||||
test(() => {
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||
assert_equals(getComputedStyle(document.querySelector('#test'))
|
||||
.backgroundColor, "rgb(255, 0, 0)", "CSS module import should succeed");
|
||||
}, "A CSS Module should load");
|
||||
</script>
|
||||
<script type="module" onerror="unreachable()">
|
||||
import sheet from "./resources/basic-large.css" with { type: "css" };
|
||||
test(() => {
|
||||
// This tests potential streaming compilation of modules in
|
||||
// Chromium that is triggered only for large (32>KiB) files in older
|
||||
// versions.
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||
assert_equals(getComputedStyle(document.querySelector('#test2'))
|
||||
.backgroundColor, "rgb(255, 0, 0)",
|
||||
"CSS module import should succeed");
|
||||
}, "A large CSS Module should load");
|
||||
</script>
|
||||
<script type="module" onerror="unreachable()">
|
||||
import sheet from "./resources/bad-import.css" with { type: "css" };
|
||||
test(() => {
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||
assert_equals(window.errorCount, 0);
|
||||
assert_equals(sheet.cssRules.length, 1, "Parser should skip @import rule");
|
||||
assert_equals(getComputedStyle(document.querySelector('#test3b'))
|
||||
.backgroundColor, "rgba(0, 0, 0, 0)",
|
||||
"CSS module @import should not succeed");
|
||||
assert_equals(getComputedStyle(document.querySelector('#test3'))
|
||||
.backgroundColor, "rgb(0, 255, 0)",
|
||||
"Rule after @import should still be applied");
|
||||
}, "An @import CSS Module should not load, but should not throw an exception");
|
||||
</script>
|
||||
<script type="module" onerror="unreachable()">
|
||||
import sheet from "./resources/malformed.css" with { type: "css" };
|
||||
test(() => {
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||
assert_equals(window.errorCount, 0);
|
||||
assert_equals(sheet.cssRules.length, 1, "Import of malformed CSS should succeed and rules after the parse error should still be parsed");
|
||||
assert_equals(getComputedStyle(document.querySelector('#test4'))
|
||||
.backgroundColor, "rgba(0, 0, 0, 0)",
|
||||
"Malformed CSS rule should not be applied");
|
||||
assert_equals(getComputedStyle(document.querySelector('#test4b'))
|
||||
.backgroundColor, "rgb(0, 255, 0)",
|
||||
"Parsing should recover and rules after malformed rules should be applied");
|
||||
}, "A parse error should not prevent subsequent rules from being included in a CSS module");
|
||||
</script>
|
||||
<script type="module">
|
||||
promise_test(function (test) {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = "resources/css-module-without-attribute-iframe.html";
|
||||
return new Promise(resolve => {
|
||||
iframe.onload = resolve;
|
||||
document.body.appendChild(iframe);
|
||||
}).then(event => {
|
||||
assert_equals(iframe.contentDocument.window_onerror, undefined);
|
||||
assert_equals(iframe.contentDocument.script_onerror.type, "error");
|
||||
assert_equals(getComputedStyle(iframe.contentDocument.querySelector('#test'))
|
||||
.backgroundColor, "rgba(0, 0, 0, 0)",
|
||||
"CSS module without type attribute should result in a fetch error");
|
||||
});
|
||||
}, "CSS module without type attribute should result in a fetch error");
|
||||
</script>
|
||||
</body>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
|
||||
<head>
|
||||
<script src="../../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
promise_test(async function (test) {
|
||||
const css_module = await import("./resources/basic.css", { with: { type: "css" }});
|
||||
assert_true(css_module.default instanceof CSSStyleSheet);
|
||||
assert_equals(css_module.default.cssRules[0].cssText,
|
||||
"#test { background-color: rgb(255, 0, 0); }");
|
||||
}, "Load a CSS module with dynamic import()");
|
||||
|
||||
promise_test(function (test) {
|
||||
return promise_rejects_js(test, TypeError,
|
||||
import("./resources/basic.css"),
|
||||
"Attempting to import() a CSS module without a type attribute should fail");
|
||||
}, "Ensure that loading a CSS module with dymnamic import() fails without a type attribute");
|
||||
</script>
|
||||
</body>
|
||||
@@ -0,0 +1,4 @@
|
||||
@import "atImported.css";
|
||||
#test3 {
|
||||
background-color:#00FF00;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
#test {
|
||||
background-color: #FF0000;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<body>
|
||||
<script>
|
||||
window.onerror = function (errorMsg, url, lineNumber, column, errorObj)
|
||||
{
|
||||
document.window_onerror = errorObj.name;
|
||||
return true;
|
||||
};
|
||||
|
||||
function scriptErrorHandler(e) {
|
||||
document.script_onerror = e;
|
||||
}
|
||||
</script>
|
||||
<script type="module" onerror="scriptErrorHandler(event)">
|
||||
import v from "./basic.css";
|
||||
document.adoptedStyleSheets = [v];
|
||||
</script>
|
||||
|
||||
<div id="test">
|
||||
I am a test div.
|
||||
</div>
|
||||
</body>
|
||||
@@ -0,0 +1,7 @@
|
||||
#test4 } {
|
||||
background-color: #FF0000;
|
||||
}
|
||||
|
||||
#test4b {
|
||||
background-color: #00FF00;
|
||||
}
|
||||
Reference in New Issue
Block a user