diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index e8fc97f6ac3..1b023d32555 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +119,7 @@ void Element::visit_edges(Cell::Visitor& visitor) visitor.visit(m_class_list); visitor.visit(m_shadow_root); visitor.visit(m_custom_element_definition); + visitor.visit(m_custom_state_set); visitor.visit(m_cascaded_properties); visitor.visit(m_computed_properties); if (m_pseudo_element_data) { @@ -3824,6 +3826,13 @@ auto Element::ensure_custom_element_reaction_queue() -> CustomElementReactionQue return *m_custom_element_reaction_queue; } +HTML::CustomStateSet& Element::ensure_custom_state_set() +{ + if (!m_custom_state_set) + m_custom_state_set = HTML::CustomStateSet::create(realm(), *this); + return *m_custom_state_set; +} + CSS::StyleSheetList& Element::document_or_shadow_root_style_sheets() { auto& root_node = root(); diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index ebe0e4e61b8..6941e7c9283 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -351,6 +351,9 @@ public: CustomElementReactionQueue const* custom_element_reaction_queue() const { return m_custom_element_reaction_queue; } CustomElementReactionQueue& ensure_custom_element_reaction_queue(); + HTML::CustomStateSet const* custom_state_set() const { return m_custom_state_set; } + HTML::CustomStateSet& ensure_custom_state_set(); + JS::ThrowCompletionOr upgrade_element(GC::Ref custom_element_definition); void try_to_upgrade(); @@ -587,6 +590,9 @@ private: // https://dom.spec.whatwg.org/#concept-element-is-value Optional m_is_value; + // https://html.spec.whatwg.org/multipage/custom-elements.html#states-set + GC::Ptr m_custom_state_set; + // https://www.w3.org/TR/intersection-observer/#dom-element-registeredintersectionobservers-slot // Element objects have an internal [[RegisteredIntersectionObservers]] slot, which is initialized to an empty list. OwnPtr> m_registered_intersection_observers; diff --git a/Libraries/LibWeb/HTML/ElementInternals.cpp b/Libraries/LibWeb/HTML/ElementInternals.cpp index 988e7bfe01d..53479232e50 100644 --- a/Libraries/LibWeb/HTML/ElementInternals.cpp +++ b/Libraries/LibWeb/HTML/ElementInternals.cpp @@ -208,6 +208,13 @@ WebIDL::ExceptionOr> ElementInternals::labels() return WebIDL::NotSupportedError::create(realm(), "FIXME: ElementInternals::labels()"_string); } +// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-states +GC::Ptr ElementInternals::states() +{ + // The states getter steps are to return this's target element's states set. + return m_target_element->ensure_custom_state_set(); +} + void ElementInternals::initialize(JS::Realm& realm) { WEB_SET_PROTOTYPE_FOR_INTERFACE(ElementInternals); diff --git a/Libraries/LibWeb/HTML/ElementInternals.h b/Libraries/LibWeb/HTML/ElementInternals.h index e57e086462c..150a423fed9 100644 --- a/Libraries/LibWeb/HTML/ElementInternals.h +++ b/Libraries/LibWeb/HTML/ElementInternals.h @@ -57,6 +57,7 @@ public: WebIDL::ExceptionOr report_validity() const; WebIDL::ExceptionOr> labels(); + GC::Ptr states(); private: explicit ElementInternals(JS::Realm&, HTMLElement& target_element); diff --git a/Libraries/LibWeb/HTML/ElementInternals.idl b/Libraries/LibWeb/HTML/ElementInternals.idl index 34912a2e2f0..be95b5ebd80 100644 --- a/Libraries/LibWeb/HTML/ElementInternals.idl +++ b/Libraries/LibWeb/HTML/ElementInternals.idl @@ -1,4 +1,5 @@ #import +#import // https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals [Exposed=Window] @@ -24,7 +25,7 @@ interface ElementInternals { readonly attribute NodeList labels; // Custom state pseudo-class - [FIXME, SameObject] readonly attribute CustomStateSet states; + [SameObject] readonly attribute CustomStateSet states; }; // Accessibility semantics diff --git a/Tests/LibWeb/Text/expected/wpt-import/custom-elements/state/ElementInternals-states.txt b/Tests/LibWeb/Text/expected/wpt-import/custom-elements/state/ElementInternals-states.txt new file mode 100644 index 00000000000..bb8a4e0ba5d --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/custom-elements/state/ElementInternals-states.txt @@ -0,0 +1,9 @@ +Harness status: OK + +Found 4 tests + +4 Pass +Pass CustomStateSet behavior of ElementInternals.states: Initial state +Pass CustomStateSet behavior of ElementInternals.states: Exceptions +Pass CustomStateSet behavior of ElementInternals.states: Modifications +Pass Updating a CustomStateSet while iterating it should work \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/custom-elements/state/custom-state-set-strong-ref.txt b/Tests/LibWeb/Text/expected/wpt-import/custom-elements/state/custom-state-set-strong-ref.txt new file mode 100644 index 00000000000..c6818539218 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/custom-elements/state/custom-state-set-strong-ref.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass customstateset doesn't crash after GC on detached node \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/common/gc.js b/Tests/LibWeb/Text/input/wpt-import/common/gc.js new file mode 100644 index 00000000000..ac43a4cfaf7 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/common/gc.js @@ -0,0 +1,52 @@ +/** + * Does a best-effort attempt at invoking garbage collection. Attempts to use + * the standardized `TestUtils.gc()` function, but falls back to other + * environment-specific nonstandard functions, with a final result of just + * creating a lot of garbage (in which case you will get a console warning). + * + * This should generally only be used to attempt to trigger bugs and crashes + * inside tests, i.e. cases where if garbage collection happened, then this + * should not trigger some misbehavior. You cannot rely on garbage collection + * successfully trigger, or that any particular unreachable object will be + * collected. + * + * @returns {Promise} A promise you should await to ensure garbage + * collection has had a chance to complete. + */ +self.garbageCollect = async () => { + // https://testutils.spec.whatwg.org/#the-testutils-namespace + if (self.TestUtils?.gc) { + return TestUtils.gc(); + } + + // Use --expose_gc for V8 (and Node.js) + // to pass this flag at chrome launch use: --js-flags="--expose-gc" + // Exposed in SpiderMonkey shell as well + if (self.gc) { + return self.gc(); + } + + // Present in some WebKit development environments + if (self.GCController) { + return GCController.collect(); + } + + console.warn( + 'Tests are running without the ability to do manual garbage collection. ' + + 'They will still work, but coverage will be suboptimal.'); + + for (var i = 0; i < 1000; i++) { + gcRec(10); + } + + function gcRec(n) { + if (n < 1) { + return {}; + } + + let temp = { i: "ab" + i + i / 100000 }; + temp += "foo"; + + gcRec(n - 1); + } +}; diff --git a/Tests/LibWeb/Text/input/wpt-import/custom-elements/state/ElementInternals-states.html b/Tests/LibWeb/Text/input/wpt-import/custom-elements/state/ElementInternals-states.html new file mode 100644 index 00000000000..f73260cd546 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/custom-elements/state/ElementInternals-states.html @@ -0,0 +1,83 @@ + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/custom-elements/state/custom-state-set-strong-ref.html b/Tests/LibWeb/Text/input/wpt-import/custom-elements/state/custom-state-set-strong-ref.html new file mode 100644 index 00000000000..d555da62e60 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/custom-elements/state/custom-state-set-strong-ref.html @@ -0,0 +1,34 @@ + + + + + + + + CustomStateSet doesn't crash after GC on detached node + + + + + + + + + +