From 7c322ec710edad2b546e14f372187c97daeb30b0 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 7 Mar 2024 23:36:52 +0100 Subject: [PATCH] LibWeb: Implement adoptedStyleSheets attribute for Document https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets The attribute implementation for ShadowRoot is currently missing because we do not yet distinguish between the style sheets of ShadowRoot and Document, and we need to address the issue first. --- .../document-adopted-style-sheets.txt | 11 +++ .../input/document-adopted-style-sheets.html | 72 ++++++++++++++++++ .../Libraries/LibWeb/CSS/StyleComputer.cpp | 5 +- Userland/Libraries/LibWeb/DOM/Document.cpp | 76 +++++++++++++++++++ Userland/Libraries/LibWeb/DOM/Document.h | 8 ++ Userland/Libraries/LibWeb/DOM/Document.idl | 1 + 6 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/document-adopted-style-sheets.txt create mode 100644 Tests/LibWeb/Text/input/document-adopted-style-sheets.html diff --git a/Tests/LibWeb/Text/expected/document-adopted-style-sheets.txt b/Tests/LibWeb/Text/expected/document-adopted-style-sheets.txt new file mode 100644 index 0000000000..493750a428 --- /dev/null +++ b/Tests/LibWeb/Text/expected/document-adopted-style-sheets.txt @@ -0,0 +1,11 @@ + color with no adopted style sheets: rgb(0, 0, 0) +document.adoptedStyleSheets.length=(1) +add style sheet using Array.prototype.push(): rgb(255, 0, 0) +document.adoptedStyleSheets.length=(0) +delete added style sheet using Array.prototype.pop(): rgb(0, 0, 0) +document.adoptedStyleSheets.length=(1) +add style by assigning array to document.adoptedStyleSheets: rgb(255, 0, 0) +document.adoptedStyleSheets.length=(1) +add style by assigning Set to document.adoptedStyleSheets: rgb(0, 128, 0) +assignment of non-iterable value to document.adoptedStyleSheets throws "1 is not iterable" +assignment of value that is not CSSStyleSheet throws "Not an object of type CSSStyleSheet" diff --git a/Tests/LibWeb/Text/input/document-adopted-style-sheets.html b/Tests/LibWeb/Text/input/document-adopted-style-sheets.html new file mode 100644 index 0000000000..f8bc245bd9 --- /dev/null +++ b/Tests/LibWeb/Text/input/document-adopted-style-sheets.html @@ -0,0 +1,72 @@ + +
+ diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index 4942f76400..b959e5f17f 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -266,8 +266,9 @@ void StyleComputer::for_each_stylesheet(CascadeOrigin cascade_origin, Callback c callback(*m_user_style_sheet); } if (cascade_origin == CascadeOrigin::Author) { - for (auto const& sheet : document().style_sheets().sheets()) - callback(*sheet); + document().for_each_css_style_sheet([&](CSSStyleSheet& sheet) { + callback(sheet); + }); } } diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 2eebc56413..89a7e11d1c 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -483,6 +483,8 @@ void Document::visit_edges(Cell::Visitor& visitor) visitor.visit(event.event); visitor.visit(event.target); } + + visitor.visit(m_adopted_style_sheets); } // https://w3c.github.io/selection-api/#dom-document-getselection @@ -4562,4 +4564,78 @@ bool Document::has_skipped_resize_observations() return false; } +static JS::NonnullGCPtr create_adopted_style_sheets_list(Document& document) +{ + auto adopted_style_sheets = WebIDL::ObservableArray::create(document.realm()); + adopted_style_sheets->set_on_set_an_indexed_value_callback([&document](JS::Value& value) -> WebIDL::ExceptionOr { + auto& vm = document.vm(); + if (!value.is_object()) + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "CSSStyleSheet"); + auto& object = value.as_object(); + if (!is(object)) + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "CSSStyleSheet"); + auto& style_sheet = static_cast(object); + + // The set an indexed value algorithm for adoptedStyleSheets, given value and index, is the following: + // 1. If value’s constructed flag is not set, or its constructor document is not equal to this + // DocumentOrShadowRoot's node document, throw a "NotAllowedError" DOMException. + if (!style_sheet.constructed()) + return WebIDL::NotAllowedError::create(document.realm(), "StyleSheet's constructed flag is not set."_fly_string); + if (!style_sheet.constructed() || style_sheet.constructor_document().ptr() != &document) + return WebIDL::NotAllowedError::create(document.realm(), "Sharing a StyleSheet between documents is not allowed."_fly_string); + + document.style_computer().load_fonts_from_sheet(style_sheet); + document.style_computer().invalidate_rule_cache(); + document.invalidate_style(); + return {}; + }); + adopted_style_sheets->set_on_delete_an_indexed_value_callback([&document]() -> WebIDL::ExceptionOr { + document.style_computer().invalidate_rule_cache(); + document.invalidate_style(); + return {}; + }); + + return adopted_style_sheets; +} + +JS::NonnullGCPtr Document::adopted_style_sheets() const +{ + if (!m_adopted_style_sheets) + m_adopted_style_sheets = create_adopted_style_sheets_list(const_cast(*this)); + return *m_adopted_style_sheets; +} + +WebIDL::ExceptionOr Document::set_adopted_style_sheets(JS::Value new_value) +{ + if (!m_adopted_style_sheets) + m_adopted_style_sheets = create_adopted_style_sheets_list(const_cast(*this)); + + m_adopted_style_sheets->clear(); + auto iterator_record = TRY(get_iterator(vm(), new_value, JS::IteratorHint::Sync)); + while (true) { + auto next = TRY(iterator_step_value(vm(), iterator_record)); + if (!next.has_value()) + break; + TRY(m_adopted_style_sheets->append(*next)); + } + + return {}; +} + +void Document::for_each_css_style_sheet(Function&& callback) const +{ + for (auto& style_sheet : m_style_sheets->sheets()) + callback(*style_sheet); + + if (m_adopted_style_sheets) { + for (auto& entry : m_adopted_style_sheets->indexed_properties()) { + auto value_and_attributes = m_adopted_style_sheets->indexed_properties().storage()->get(entry.index()); + if (value_and_attributes.has_value()) { + auto& style_sheet = verify_cast(value_and_attributes->value.as_object()); + callback(style_sheet); + } + } + } +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 530ca53522..b5f9c14041 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -34,6 +34,7 @@ #include #include #include +#include namespace Web::DOM { @@ -131,6 +132,8 @@ public: CSS::StyleSheetList& style_sheets(); CSS::StyleSheetList const& style_sheets() const; + void for_each_css_style_sheet(Function&& callback) const; + CSS::StyleSheetList* style_sheets_for_bindings() { return &style_sheets(); } virtual FlyString node_name() const override { return "#document"_fly_string; } @@ -598,6 +601,9 @@ public: [[nodiscard]] bool has_active_resize_observations(); [[nodiscard]] bool has_skipped_resize_observations(); + JS::NonnullGCPtr adopted_style_sheets() const; + WebIDL::ExceptionOr set_adopted_style_sheets(JS::Value); + protected: virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; @@ -832,6 +838,8 @@ private: bool m_design_mode_enabled { false }; bool m_needs_to_resolve_paint_only_properties { true }; + + mutable JS::GCPtr m_adopted_style_sheets; }; template<> diff --git a/Userland/Libraries/LibWeb/DOM/Document.idl b/Userland/Libraries/LibWeb/DOM/Document.idl index 5054da003d..c00419ea27 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.idl +++ b/Userland/Libraries/LibWeb/DOM/Document.idl @@ -95,6 +95,7 @@ interface Document : Node { [CEReactions, ImplementedAs=adopt_node_binding] Node adoptNode(Node node); [ImplementedAs=style_sheets_for_bindings] readonly attribute StyleSheetList styleSheets; + attribute any adoptedStyleSheets; readonly attribute DOMString compatMode; readonly attribute DocumentType? doctype;