LibWeb: Support CSS modules

This adds support for importing CSS stylesheets from CSS files in
javascript.
This commit is contained in:
Glenn Skrzypczak
2025-10-26 18:06:19 +01:00
committed by Shannon Booth
parent 2362a65e3b
commit f1d3244b22
Notes: github-actions[bot] 2026-04-03 19:22:27 +00:00
21 changed files with 272 additions and 80 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}();

View File

@@ -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 sheets 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 sheets 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 sheets stylesheet base URL to the baseURL attribute value from options.

View File

@@ -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) {

View File

@@ -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())

View File

@@ -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&);

View File

@@ -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)>>;

View File

@@ -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); });
}
}

View File

@@ -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(); }

View File

@@ -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.

View File

@@ -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://.

View 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

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,4 @@
@import "atImported.css";
#test3 {
background-color:#00FF00;
}

View File

@@ -0,0 +1,3 @@
#test {
background-color: #FF0000;
}

View File

@@ -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>

View File

@@ -0,0 +1,7 @@
#test4 } {
background-color: #FF0000;
}
#test4b {
background-color: #00FF00;
}