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) {
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_);
}
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);
// setAttribute() will call DidUpdateAttributeValue().
}
......
......@@ -59,9 +59,9 @@ class CORE_EXPORT DOMTokenList : public ScriptWrappable {
const AtomicString& new_token,
ExceptionState&);
bool supports(const AtomicString&, ExceptionState&);
const AtomicString& value() const;
void setValue(const AtomicString&);
const AtomicString& toString() const { return value(); }
virtual AtomicString value() const;
virtual void setValue(const AtomicString&);
AtomicString toString() const { return value(); }
// This function should be called when the associated attribute value was
// updated.
......@@ -85,6 +85,10 @@ class CORE_EXPORT DOMTokenList : public ScriptWrappable {
SpaceSplitString token_set_;
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_;
bool is_in_update_step_ = false;
DISALLOW_COPY_AND_ASSIGN(DOMTokenList);
......
......@@ -5,6 +5,7 @@
#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/dom/dom_token_list.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/html/custom/custom_element.h"
......@@ -31,6 +32,20 @@ bool IsValidityStateFlagsValid(const ValidityStateFlags* flags) {
}
} // 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) {
value_.SetUSVString(String());
}
......@@ -41,6 +56,7 @@ void ElementInternals::Trace(Visitor* visitor) {
visitor->Trace(state_);
visitor->Trace(validity_flags_);
visitor->Trace(validation_anchor_);
visitor->Trace(custom_states_);
visitor->Trace(explicitly_set_attr_elements_map_);
ListedElement::Trace(visitor);
ScriptWrappable::Trace(visitor);
......@@ -213,6 +229,12 @@ LabelsNodeList* ElementInternals::labels(ExceptionState& exception_state) {
return Target().labels();
}
DOMTokenList* ElementInternals::states() {
if (!custom_states_)
custom_states_ = MakeGarbageCollected<CustomStatesTokenList>(Target());
return custom_states_;
}
const AtomicString& ElementInternals::FastGetAttribute(
const QualifiedName& attribute) const {
return accessibility_semantics_map_.at(attribute);
......
......@@ -13,6 +13,7 @@
namespace blink {
class DOMTokenList;
class HTMLElement;
class LabelsNodeList;
class ValidityStateFlags;
......@@ -50,6 +51,7 @@ class CORE_EXPORT ElementInternals : public ScriptWrappable,
bool checkValidity(ExceptionState& exception_state);
bool reportValidity(ExceptionState& exception_state);
LabelsNodeList* labels(ExceptionState& exception_state);
DOMTokenList* states();
// We need these functions because we are reflecting ARIA attributes.
// See dom/aria_attributes.idl.
......@@ -102,6 +104,9 @@ class CORE_EXPORT ElementInternals : public ScriptWrappable,
bool is_disabled_ = false;
Member<ValidityStateFlags> validity_flags_;
Member<Element> validation_anchor_;
Member<DOMTokenList> custom_states_;
HashMap<QualifiedName, AtomicString> accessibility_semantics_map_;
// See
......
......@@ -24,5 +24,9 @@ interface ElementInternals {
[RaisesException] boolean reportValidity();
[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 @@
origin_trial_feature_name: "WebComponentsV0",
status: "stable",
},
{
name: "CustomStatePseudoClass",
status: "test",
},
{
name: "Database",
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
getter form
getter labels
getter role
getter states
getter validationMessage
getter validity
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