Commit 02f429d4 authored by Kent Tamura's avatar Kent Tamura Committed by Commit Bot

Custom state: Implement 'states' IDL attribute of ElementInternals

'states' is represented by blink::CustomStatesTokenList inherited from
DOMTokenList. It passes a null QualifiedName to DOMTokenList as the
associated attribute name. CustomStatesTokenList doesn't keep a
serialized string, and 'value' getter serializes tokens on the fly.

This CL doesn't contains changes for ':state()' pseudo class.

The 'states' IDL attribute is behind a new runtime flag,
CustomStatePseudoClass.

Bug: 1012098
Change-Id: Icee5f1703f5930603117c1ba73994f3bbba4e2cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1847606Reviewed-by: default avatarRakina Zata Amni <rakina@chromium.org>
Commit-Queue: Kent Tamura <tkent@chromium.org>
Cr-Commit-Position: refs/heads/master@{#704118}
parent d97b108b
...@@ -254,11 +254,15 @@ void DOMTokenList::UpdateWithTokenSet(const SpaceSplitString& token_set) { ...@@ -254,11 +254,15 @@ void DOMTokenList::UpdateWithTokenSet(const SpaceSplitString& token_set) {
setValue(token_set.SerializeToString()); setValue(token_set.SerializeToString());
} }
const AtomicString& DOMTokenList::value() const { AtomicString DOMTokenList::value() const {
DCHECK_NE(attribute_name_, g_null_name)
<< "The subclass of DOMTokenList should override value().";
return element_->getAttribute(attribute_name_); return element_->getAttribute(attribute_name_);
} }
void DOMTokenList::setValue(const AtomicString& value) { void DOMTokenList::setValue(const AtomicString& value) {
DCHECK_NE(attribute_name_, g_null_name)
<< "The subclass of DOMTokenList should override setValue().";
element_->setAttribute(attribute_name_, value); element_->setAttribute(attribute_name_, value);
// setAttribute() will call DidUpdateAttributeValue(). // setAttribute() will call DidUpdateAttributeValue().
} }
......
...@@ -59,9 +59,9 @@ class CORE_EXPORT DOMTokenList : public ScriptWrappable { ...@@ -59,9 +59,9 @@ class CORE_EXPORT DOMTokenList : public ScriptWrappable {
const AtomicString& new_token, const AtomicString& new_token,
ExceptionState&); ExceptionState&);
bool supports(const AtomicString&, ExceptionState&); bool supports(const AtomicString&, ExceptionState&);
const AtomicString& value() const; virtual AtomicString value() const;
void setValue(const AtomicString&); virtual void setValue(const AtomicString&);
const AtomicString& toString() const { return value(); } AtomicString toString() const { return value(); }
// This function should be called when the associated attribute value was // This function should be called when the associated attribute value was
// updated. // updated.
...@@ -85,6 +85,10 @@ class CORE_EXPORT DOMTokenList : public ScriptWrappable { ...@@ -85,6 +85,10 @@ class CORE_EXPORT DOMTokenList : public ScriptWrappable {
SpaceSplitString token_set_; SpaceSplitString token_set_;
const Member<Element> element_; const Member<Element> element_;
// Normal DOMTokenList instances is associated to an attribute name.
// So |attribute_name_| is typically an html_names::kFooAttr.
// CustomStateTokenList is associated to no attribute name.
// |attribute_name_| is |g_null_name| in that case.
const QualifiedName attribute_name_; const QualifiedName attribute_name_;
bool is_in_update_step_ = false; bool is_in_update_step_ = false;
DISALLOW_COPY_AND_ASSIGN(DOMTokenList); DISALLOW_COPY_AND_ASSIGN(DOMTokenList);
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "third_party/blink/renderer/core/html/custom/element_internals.h" #include "third_party/blink/renderer/core/html/custom/element_internals.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/dom/dom_token_list.h"
#include "third_party/blink/renderer/core/dom/node_lists_node_data.h" #include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
#include "third_party/blink/renderer/core/fileapi/file.h" #include "third_party/blink/renderer/core/fileapi/file.h"
#include "third_party/blink/renderer/core/html/custom/custom_element.h" #include "third_party/blink/renderer/core/html/custom/custom_element.h"
...@@ -31,6 +32,20 @@ bool IsValidityStateFlagsValid(const ValidityStateFlags* flags) { ...@@ -31,6 +32,20 @@ bool IsValidityStateFlagsValid(const ValidityStateFlags* flags) {
} }
} // anonymous namespace } // anonymous namespace
class CustomStatesTokenList : public DOMTokenList {
public:
CustomStatesTokenList(Element& element)
: DOMTokenList(element, g_null_name) {}
AtomicString value() const override { return TokenSet().SerializeToString(); }
void setValue(const AtomicString& new_value) override {
DidUpdateAttributeValue(value(), new_value);
// TODO(crbug.com/1012098): Calls GetElement().PseudoStateChanged() for
// ':state()'
}
};
ElementInternals::ElementInternals(HTMLElement& target) : target_(target) { ElementInternals::ElementInternals(HTMLElement& target) : target_(target) {
value_.SetUSVString(String()); value_.SetUSVString(String());
} }
...@@ -41,6 +56,7 @@ void ElementInternals::Trace(Visitor* visitor) { ...@@ -41,6 +56,7 @@ void ElementInternals::Trace(Visitor* visitor) {
visitor->Trace(state_); visitor->Trace(state_);
visitor->Trace(validity_flags_); visitor->Trace(validity_flags_);
visitor->Trace(validation_anchor_); visitor->Trace(validation_anchor_);
visitor->Trace(custom_states_);
visitor->Trace(explicitly_set_attr_elements_map_); visitor->Trace(explicitly_set_attr_elements_map_);
ListedElement::Trace(visitor); ListedElement::Trace(visitor);
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
...@@ -213,6 +229,12 @@ LabelsNodeList* ElementInternals::labels(ExceptionState& exception_state) { ...@@ -213,6 +229,12 @@ LabelsNodeList* ElementInternals::labels(ExceptionState& exception_state) {
return Target().labels(); return Target().labels();
} }
DOMTokenList* ElementInternals::states() {
if (!custom_states_)
custom_states_ = MakeGarbageCollected<CustomStatesTokenList>(Target());
return custom_states_;
}
const AtomicString& ElementInternals::FastGetAttribute( const AtomicString& ElementInternals::FastGetAttribute(
const QualifiedName& attribute) const { const QualifiedName& attribute) const {
return accessibility_semantics_map_.at(attribute); return accessibility_semantics_map_.at(attribute);
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
namespace blink { namespace blink {
class DOMTokenList;
class HTMLElement; class HTMLElement;
class LabelsNodeList; class LabelsNodeList;
class ValidityStateFlags; class ValidityStateFlags;
...@@ -50,6 +51,7 @@ class CORE_EXPORT ElementInternals : public ScriptWrappable, ...@@ -50,6 +51,7 @@ class CORE_EXPORT ElementInternals : public ScriptWrappable,
bool checkValidity(ExceptionState& exception_state); bool checkValidity(ExceptionState& exception_state);
bool reportValidity(ExceptionState& exception_state); bool reportValidity(ExceptionState& exception_state);
LabelsNodeList* labels(ExceptionState& exception_state); LabelsNodeList* labels(ExceptionState& exception_state);
DOMTokenList* states();
// We need these functions because we are reflecting ARIA attributes. // We need these functions because we are reflecting ARIA attributes.
// See dom/aria_attributes.idl. // See dom/aria_attributes.idl.
...@@ -102,6 +104,9 @@ class CORE_EXPORT ElementInternals : public ScriptWrappable, ...@@ -102,6 +104,9 @@ class CORE_EXPORT ElementInternals : public ScriptWrappable,
bool is_disabled_ = false; bool is_disabled_ = false;
Member<ValidityStateFlags> validity_flags_; Member<ValidityStateFlags> validity_flags_;
Member<Element> validation_anchor_; Member<Element> validation_anchor_;
Member<DOMTokenList> custom_states_;
HashMap<QualifiedName, AtomicString> accessibility_semantics_map_; HashMap<QualifiedName, AtomicString> accessibility_semantics_map_;
// See // See
......
...@@ -24,5 +24,9 @@ interface ElementInternals { ...@@ -24,5 +24,9 @@ interface ElementInternals {
[RaisesException] boolean reportValidity(); [RaisesException] boolean reportValidity();
[RaisesException] readonly attribute NodeList labels; [RaisesException] readonly attribute NodeList labels;
// Custom state
// https://github.com/w3c/webcomponents/blob/gh-pages/proposals/custom-states-and-state-pseudo-class.md
[RuntimeEnabled=CustomStatePseudoClass] readonly attribute DOMTokenList states;
}; };
...@@ -508,6 +508,10 @@ ...@@ -508,6 +508,10 @@
origin_trial_feature_name: "WebComponentsV0", origin_trial_feature_name: "WebComponentsV0",
status: "stable", status: "stable",
}, },
{
name: "CustomStatePseudoClass",
status: "test",
},
{ {
name: "Database", name: "Database",
status: "stable", status: "stable",
......
<!DOCTYPE html>
<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 DOMTokenList);
assert_equals(i.states.length, 0);
assert_false(i.states.contains('foo'));
assert_equals(i.states.value, '');
assert_equals(i.states.toString(), '');
}, 'DOMTokenList behavior of ElementInternals.states: Initial state');
test(() => {
let i = (new TestElement()).internals;
assert_throws(new TypeError(), () => { i.states.supports('foo'); });
assert_throws(new SyntaxError(), () => { i.states.add(''); });
assert_throws('InvalidCharacterError', () => { i.states.add('a\tb'); });
}, 'DOMTokenList behavior of ElementInternals.states: Exceptions');
test(() => {
let i = (new TestElement()).internals;
i.states.add('foo', 'bar', 'foo');
assert_equals(i.states.length, 2);
assert_true(i.states.contains('foo'));
assert_true(i.states.contains('bar'));
assert_equals(i.states.item(0), 'foo');
assert_equals(i.states.item(1), 'bar');
assert_equals(i.states[0], 'foo');
assert_equals(i.states[1], 'bar');
assert_equals(i.states.value, 'foo bar');
i.states.remove('foo');
assert_array_equals(i.states, ['bar']);
assert_equals(i.states.value, 'bar');
i.states.toggle('foo');
assert_array_equals(i.states, ['bar', 'foo']);
assert_equals(i.states.value, 'bar foo');
i.states.replace('bar', 'baz');
assert_array_equals(i.states, ['baz', 'foo']);
assert_equals(i.states.value, 'baz foo');
i.states.value = ' c u s t o o o o m';
assert_equals(i.states.length, 6);
assert_array_equals(i.states, ['c', 'u', 's', 't', 'o', 'm']);
i.states.value = '';
// No throw for a token which can't be a CSS <ident>.
i.states.add('foo)');
assert_array_equals(i.states, ['foo)']);
}, 'DOMTokenList behavior of ElementInternals.states: Modifications');
</script>
...@@ -2234,6 +2234,7 @@ interface ElementInternals ...@@ -2234,6 +2234,7 @@ interface ElementInternals
getter form getter form
getter labels getter labels
getter role getter role
getter states
getter validationMessage getter validationMessage
getter validity getter validity
getter willValidate getter willValidate
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment