mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-24 18:02:20 +00:00
LibWeb: Give Element a CustomStateSet, exposed by ElementInternals
This commit is contained in:
parent
e63d81b36e
commit
b6ffea8990
Notes:
github-actions[bot]
2025-07-04 17:11:47 +00:00
Author: https://github.com/AtkinsSJ
Commit: b6ffea8990
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5302
Reviewed-by: https://github.com/tcl3 ✅
10 changed files with 209 additions and 1 deletions
|
@ -46,6 +46,7 @@
|
||||||
#include <LibWeb/HTML/CustomElements/CustomElementName.h>
|
#include <LibWeb/HTML/CustomElements/CustomElementName.h>
|
||||||
#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
|
#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
|
||||||
#include <LibWeb/HTML/CustomElements/CustomElementRegistry.h>
|
#include <LibWeb/HTML/CustomElements/CustomElementRegistry.h>
|
||||||
|
#include <LibWeb/HTML/CustomElements/CustomStateSet.h>
|
||||||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||||||
#include <LibWeb/HTML/HTMLAnchorElement.h>
|
#include <LibWeb/HTML/HTMLAnchorElement.h>
|
||||||
#include <LibWeb/HTML/HTMLAreaElement.h>
|
#include <LibWeb/HTML/HTMLAreaElement.h>
|
||||||
|
@ -118,6 +119,7 @@ void Element::visit_edges(Cell::Visitor& visitor)
|
||||||
visitor.visit(m_class_list);
|
visitor.visit(m_class_list);
|
||||||
visitor.visit(m_shadow_root);
|
visitor.visit(m_shadow_root);
|
||||||
visitor.visit(m_custom_element_definition);
|
visitor.visit(m_custom_element_definition);
|
||||||
|
visitor.visit(m_custom_state_set);
|
||||||
visitor.visit(m_cascaded_properties);
|
visitor.visit(m_cascaded_properties);
|
||||||
visitor.visit(m_computed_properties);
|
visitor.visit(m_computed_properties);
|
||||||
if (m_pseudo_element_data) {
|
if (m_pseudo_element_data) {
|
||||||
|
@ -3824,6 +3826,13 @@ auto Element::ensure_custom_element_reaction_queue() -> CustomElementReactionQue
|
||||||
return *m_custom_element_reaction_queue;
|
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()
|
CSS::StyleSheetList& Element::document_or_shadow_root_style_sheets()
|
||||||
{
|
{
|
||||||
auto& root_node = root();
|
auto& root_node = root();
|
||||||
|
|
|
@ -351,6 +351,9 @@ public:
|
||||||
CustomElementReactionQueue const* custom_element_reaction_queue() const { return m_custom_element_reaction_queue; }
|
CustomElementReactionQueue const* custom_element_reaction_queue() const { return m_custom_element_reaction_queue; }
|
||||||
CustomElementReactionQueue& ensure_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<void> upgrade_element(GC::Ref<HTML::CustomElementDefinition> custom_element_definition);
|
JS::ThrowCompletionOr<void> upgrade_element(GC::Ref<HTML::CustomElementDefinition> custom_element_definition);
|
||||||
void try_to_upgrade();
|
void try_to_upgrade();
|
||||||
|
|
||||||
|
@ -587,6 +590,9 @@ private:
|
||||||
// https://dom.spec.whatwg.org/#concept-element-is-value
|
// https://dom.spec.whatwg.org/#concept-element-is-value
|
||||||
Optional<String> m_is_value;
|
Optional<String> m_is_value;
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/custom-elements.html#states-set
|
||||||
|
GC::Ptr<HTML::CustomStateSet> m_custom_state_set;
|
||||||
|
|
||||||
// https://www.w3.org/TR/intersection-observer/#dom-element-registeredintersectionobservers-slot
|
// 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.
|
// Element objects have an internal [[RegisteredIntersectionObservers]] slot, which is initialized to an empty list.
|
||||||
OwnPtr<Vector<IntersectionObserver::IntersectionObserverRegistration>> m_registered_intersection_observers;
|
OwnPtr<Vector<IntersectionObserver::IntersectionObserverRegistration>> m_registered_intersection_observers;
|
||||||
|
|
|
@ -208,6 +208,13 @@ WebIDL::ExceptionOr<GC::Ptr<DOM::NodeList>> ElementInternals::labels()
|
||||||
return WebIDL::NotSupportedError::create(realm(), "FIXME: ElementInternals::labels()"_string);
|
return WebIDL::NotSupportedError::create(realm(), "FIXME: ElementInternals::labels()"_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-states
|
||||||
|
GC::Ptr<CustomStateSet> 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)
|
void ElementInternals::initialize(JS::Realm& realm)
|
||||||
{
|
{
|
||||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(ElementInternals);
|
WEB_SET_PROTOTYPE_FOR_INTERFACE(ElementInternals);
|
||||||
|
|
|
@ -57,6 +57,7 @@ public:
|
||||||
WebIDL::ExceptionOr<bool> report_validity() const;
|
WebIDL::ExceptionOr<bool> report_validity() const;
|
||||||
|
|
||||||
WebIDL::ExceptionOr<GC::Ptr<DOM::NodeList>> labels();
|
WebIDL::ExceptionOr<GC::Ptr<DOM::NodeList>> labels();
|
||||||
|
GC::Ptr<CustomStateSet> states();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ElementInternals(JS::Realm&, HTMLElement& target_element);
|
explicit ElementInternals(JS::Realm&, HTMLElement& target_element);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#import <DOM/ShadowRoot.idl>
|
#import <DOM/ShadowRoot.idl>
|
||||||
|
#import <HTML/CustomElements/CustomStateSet.idl>
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals
|
// https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals
|
||||||
[Exposed=Window]
|
[Exposed=Window]
|
||||||
|
@ -24,7 +25,7 @@ interface ElementInternals {
|
||||||
readonly attribute NodeList labels;
|
readonly attribute NodeList labels;
|
||||||
|
|
||||||
// Custom state pseudo-class
|
// Custom state pseudo-class
|
||||||
[FIXME, SameObject] readonly attribute CustomStateSet states;
|
[SameObject] readonly attribute CustomStateSet states;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Accessibility semantics
|
// Accessibility semantics
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 1 tests
|
||||||
|
|
||||||
|
1 Pass
|
||||||
|
Pass customstateset doesn't crash after GC on detached node
|
52
Tests/LibWeb/Text/input/wpt-import/common/gc.js
Normal file
52
Tests/LibWeb/Text/input/wpt-import/common/gc.js
Normal file
|
@ -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<undefined>} 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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,83 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel=help href="https://html.spec.whatwg.org/multipage/custom-elements.html#custom-state-pseudo-class">
|
||||||
|
<script src="../../resources/testharness.js"></script>
|
||||||
|
<script src="../../resources/testharnessreport.js"></script>
|
||||||
|
<script>
|
||||||
|
class TestElement extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._internals = this.attachInternals();
|
||||||
|
}
|
||||||
|
|
||||||
|
get internals() {
|
||||||
|
return this._internals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define("test-element", TestElement);
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
let i = (new TestElement()).internals;
|
||||||
|
|
||||||
|
assert_true(i.states instanceof CustomStateSet);
|
||||||
|
assert_equals(i.states.size, 0);
|
||||||
|
assert_false(i.states.has('foo'));
|
||||||
|
assert_false(i.states.has('--foo'));
|
||||||
|
assert_equals(i.states.toString(), '[object CustomStateSet]');
|
||||||
|
}, 'CustomStateSet behavior of ElementInternals.states: Initial state');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
let i = (new TestElement()).internals;
|
||||||
|
assert_throws_js(TypeError, () => { i.states.supports('foo'); });
|
||||||
|
i.states.add(''); // should not throw.
|
||||||
|
i.states.add('--a\tb'); // should not throw.
|
||||||
|
}, 'CustomStateSet behavior of ElementInternals.states: Exceptions');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
let i = (new TestElement()).internals;
|
||||||
|
i.states.add('--foo');
|
||||||
|
i.states.add('--bar');
|
||||||
|
i.states.add('--foo');
|
||||||
|
assert_equals(i.states.size, 2);
|
||||||
|
assert_true(i.states.has('--foo'));
|
||||||
|
assert_true(i.states.has('--bar'));
|
||||||
|
assert_array_equals([...i.states], ['--foo', '--bar']);
|
||||||
|
i.states.delete('--foo');
|
||||||
|
assert_array_equals([...i.states], ['--bar']);
|
||||||
|
i.states.add('--foo');
|
||||||
|
assert_array_equals([...i.states], ['--bar', '--foo']);
|
||||||
|
i.states.delete('--bar');
|
||||||
|
i.states.add('--baz');
|
||||||
|
assert_array_equals([...i.states], ['--foo', '--baz']);
|
||||||
|
}, 'CustomStateSet behavior of ElementInternals.states: Modifications');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
let i = (new TestElement()).internals;
|
||||||
|
i.states.add('--one');
|
||||||
|
i.states.add('--two');
|
||||||
|
i.states.add('--three');
|
||||||
|
let iter = i.states.values();
|
||||||
|
|
||||||
|
// Delete the next item.
|
||||||
|
i.states.delete('--one');
|
||||||
|
let item = iter.next();
|
||||||
|
assert_false(item.done);
|
||||||
|
assert_equals(item.value, '--two');
|
||||||
|
|
||||||
|
// Clear the set.
|
||||||
|
i.states.clear();
|
||||||
|
item = iter.next();
|
||||||
|
assert_true(item.done);
|
||||||
|
|
||||||
|
// Delete the previous item.
|
||||||
|
i.states.add('--one');
|
||||||
|
i.states.add('--two');
|
||||||
|
i.states.add('--three');
|
||||||
|
iter = i.states.values();
|
||||||
|
item = iter.next();
|
||||||
|
assert_equals(item.value, '--one');
|
||||||
|
i.states.delete('--one');
|
||||||
|
item = iter.next();
|
||||||
|
assert_equals(item.value, '--two');
|
||||||
|
}, 'Updating a CustomStateSet while iterating it should work');
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="timeout" content="long">
|
||||||
|
<meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" />
|
||||||
|
<link rel="help" href="https://html.spec.whatwg.org/multipage/custom-elements.html#custom-state-pseudo-class" />
|
||||||
|
<title>CustomStateSet doesn't crash after GC on detached node</title>
|
||||||
|
|
||||||
|
<script src="../../resources/testharness.js"></script>
|
||||||
|
<script src="../../resources/testharnessreport.js"></script>
|
||||||
|
<script src="../../common/gc.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<custom-state id="myCE"></custom-state>
|
||||||
|
<script>
|
||||||
|
customElements.define('custom-state', class extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
this.elementInternals = this.attachInternals();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
promise_test(async function() {
|
||||||
|
const states = myCE.elementInternals.states;
|
||||||
|
myCE.remove();
|
||||||
|
await garbageCollect();
|
||||||
|
states.add('still-works');
|
||||||
|
assert_equals(states.size, 1);
|
||||||
|
assert_true(states.delete('still-works'));
|
||||||
|
assert_equals(states.size, 0);
|
||||||
|
}, "customstateset doesn't crash after GC on detached node");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue