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;