Commit 91e0fbdb authored by Kent Tamura's avatar Kent Tamura Committed by Chromium LUCI CQ

Custom state: Update the IDL interface for 'states' attribute

In order to conform to the latest specification [1], this CL changes the
type of 'states' attribute from 'DOMTokenList' to 'CustomStateSet',
which is a 'setlike<DOMString>.'

* dom_token_list.{cc,h}
  Remove 'virtual' from 'value()' and 'setValue()'. No one overrides
  them.

* custom_state_set.{cc,h,idl}
  Added.

* element_internals.{cc,h,idl}
  Switch 'states' implementation.
  Remove 'CustomStatesTokenList' class

* wpt/custom-elements/state/tentative/
  Update tests for the new interface

This feature is not shipped yet.

[1] https://wicg.github.io/custom-state-pseudo-class/

Bug: 1012098
Change-Id: I642d16f96449db93cc9c4dc1211c7f242e608192
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2639345
Commit-Queue: Kent Tamura <tkent@chromium.org>
Reviewed-by: default avatarMason Freed <masonfreed@chromium.org>
Cr-Commit-Position: refs/heads/master@{#845986}
parent cd36843f
......@@ -645,6 +645,8 @@ generated_interface_sources_in_core = [
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_custom_element_registry.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_custom_event.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_custom_event.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_custom_state_set.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_custom_state_set.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_data_transfer.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_data_transfer.h",
"$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_data_transfer_item.cc",
......
......@@ -322,6 +322,7 @@ static_idl_files_in_core = get_path_info(
"//third_party/blink/renderer/core/html/canvas/image_encode_options.idl",
"//third_party/blink/renderer/core/html/canvas/text_metrics.idl",
"//third_party/blink/renderer/core/html/custom/custom_element_registry.idl",
"//third_party/blink/renderer/core/html/custom/custom_state_set.idl",
"//third_party/blink/renderer/core/html/custom/element_internals.idl",
"//third_party/blink/renderer/core/html/custom/validity_state_flags.idl",
"//third_party/blink/renderer/core/html/event_handler.idl",
......
......@@ -216,6 +216,7 @@ core_interface_idl_files_core_only =
"html/canvas/image_data.idl",
"html/canvas/text_metrics.idl",
"html/custom/custom_element_registry.idl",
"html/custom/custom_state_set.idl",
"html/custom/element_internals.idl",
"html/forms/form_data.idl",
"html/forms/form_data_event.idl",
......
......@@ -255,14 +255,10 @@ void DOMTokenList::UpdateWithTokenSet(const SpaceSplitString& token_set) {
}
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().
}
......
......@@ -60,8 +60,8 @@ class CORE_EXPORT DOMTokenList : public ScriptWrappable {
const AtomicString& new_token,
ExceptionState&);
bool supports(const AtomicString&, ExceptionState&);
virtual AtomicString value() const;
virtual void setValue(const AtomicString&);
AtomicString value() const;
void setValue(const AtomicString&);
AtomicString toString() const { return value(); }
// This function should be called when the associated attribute value was
......
......@@ -58,6 +58,8 @@ blink_core_sources_html = [
"custom/custom_element_registry.h",
"custom/custom_element_upgrade_sorter.cc",
"custom/custom_element_upgrade_sorter.h",
"custom/custom_state_set.cc",
"custom/custom_state_set.h",
"custom/element_internals.cc",
"custom/element_internals.h",
"document_all_name_collection.cc",
......
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/html/custom/custom_state_set.h"
#include "third_party/blink/renderer/core/css/css_selector.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_idioms.h"
#include "third_party/blink/renderer/core/dom/element.h"
namespace blink {
CustomStateSet::CustomStateSet(Element& element) : element_(element) {}
void CustomStateSet::Trace(Visitor* visitor) const {
visitor->Trace(element_);
ScriptWrappable::Trace(visitor);
}
void CustomStateSet::add(const String& value, ExceptionState& exception_state) {
// https://wicg.github.io/custom-state-pseudo-class/#dom-customstateset-add
// 1. If value does not match to <dashed-ident>, then throw a "SyntaxError"
// DOMException.
if (!value.StartsWith("--")) {
exception_state.ThrowDOMException(
DOMExceptionCode::kSyntaxError,
"The specified value '" + value + "' must start with '--'.");
return;
}
for (wtf_size_t i = 2; i < value.length(); ++i) {
if (IsNameCodePoint(value[i]))
continue;
exception_state.ThrowDOMException(
DOMExceptionCode::kSyntaxError,
"The specified value '" + value +
"' must match to <dashed-ident> production. '" + value[i] +
"' is invalid.");
return;
}
// 2. Invoke the default add operation, which the setlike<DOMString> would
// have if CustomStateSet interface had no add(value) operation, with value
// argument.
set_.insert(value);
InvalidateStyle();
}
uint32_t CustomStateSet::size() const {
return set_.size();
}
void CustomStateSet::clearForBinding(ScriptState*, ExceptionState&) {
set_.clear();
InvalidateStyle();
}
bool CustomStateSet::deleteForBinding(ScriptState*,
const String& value,
ExceptionState&) {
auto iter = set_.find(value);
if (iter == set_.cend())
return false;
set_.erase(iter);
InvalidateStyle();
return true;
}
bool CustomStateSet::hasForBinding(ScriptState*,
const String& value,
ExceptionState&) const {
return Has(value);
}
bool CustomStateSet::Has(const String& value) const {
return set_.Contains(value);
}
class CustomStateIterationSource : public CustomStateSet::IterationSource {
public:
explicit CustomStateIterationSource(CustomStateSet& states)
: states_(states), iterator_(states.set_.begin()) {}
void Trace(Visitor* visitor) const override {
visitor->Trace(states_);
CustomStateSet::IterationSource::Trace(visitor);
}
bool Next(ScriptState*,
String& out_key,
String& out_value,
ExceptionState&) override {
if (iterator_ == states_->set_.end())
return false;
String value = *iterator_;
++iterator_;
out_key = value;
out_value = value;
return true;
}
private:
Member<CustomStateSet> states_;
LinkedHashSet<String>::const_iterator iterator_;
};
CustomStateSet::IterationSource* CustomStateSet::StartIteration(
ScriptState*,
ExceptionState&) {
return MakeGarbageCollected<CustomStateIterationSource>(*this);
}
void CustomStateSet::InvalidateStyle() const {
// TOOD(tkent): The following line invalidates all of rulesets with any
// custom state pseudo classes though we should invalidate only rulesets
// with the updated state ideally. We can improve style resolution
// performance in documents with various custom state pseudo classes by
// having blink::InvalidationSet for each of states.
element_->PseudoStateChanged(CSSSelector::kPseudoState);
}
} // namespace blink
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_CUSTOM_CUSTOM_STATE_SET_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_CUSTOM_CUSTOM_STATE_SET_H_
#include "third_party/blink/renderer/bindings/core/v8/iterable.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
namespace blink {
class Element;
// This class is an implementation of 'CustomStateSet' IDL interface.
class CustomStateSet final : public ScriptWrappable,
public SetlikeIterable<String> {
DEFINE_WRAPPERTYPEINFO();
public:
explicit CustomStateSet(Element& element);
void Trace(Visitor* visitor) const override;
// IDL bindings:
void add(const String& value, ExceptionState& exception_state);
uint32_t size() const;
void clearForBinding(ScriptState*, ExceptionState&);
bool deleteForBinding(ScriptState*, const String& value, ExceptionState&);
bool hasForBinding(ScriptState*, const String& value, ExceptionState&) const;
bool Has(const String& value) const;
private:
// blink::Iterable override:
IterationSource* StartIteration(ScriptState* state, ExceptionState&) override;
void InvalidateStyle() const;
Member<Element> element_;
LinkedHashSet<String> set_;
friend class CustomStateIterationSource;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_CUSTOM_CUSTOM_STATE_SET_H_
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://wicg.github.io/custom-state-pseudo-class/#customstateset
[
Exposed=Window,
RuntimeEnabled=CustomStatePseudoClass
]
interface CustomStateSet {
setlike<DOMString>;
[RaisesException] void add(DOMString key);
};
......@@ -6,11 +6,11 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_validity_state_flags.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"
#include "third_party/blink/renderer/core/html/custom/custom_element_registry.h"
#include "third_party/blink/renderer/core/html/custom/custom_state_set.h"
#include "third_party/blink/renderer/core/html/forms/form_controller.h"
#include "third_party/blink/renderer/core/html/forms/form_data.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
......@@ -32,20 +32,6 @@ 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);
// Should we have invalidation set for each of state tokens?
GetElement().PseudoStateChanged(CSSSelector::kPseudoState);
}
};
ElementInternals::ElementInternals(HTMLElement& target) : target_(target) {
}
......@@ -228,14 +214,14 @@ LabelsNodeList* ElementInternals::labels(ExceptionState& exception_state) {
return Target().labels();
}
DOMTokenList* ElementInternals::states() {
CustomStateSet* ElementInternals::states() {
if (!custom_states_)
custom_states_ = MakeGarbageCollected<CustomStatesTokenList>(Target());
custom_states_ = MakeGarbageCollected<CustomStateSet>(Target());
return custom_states_;
}
bool ElementInternals::HasState(const AtomicString& state) const {
return custom_states_ && custom_states_->contains(state);
return custom_states_ && custom_states_->Has(state);
}
ShadowRoot* ElementInternals::shadowRoot() const {
......
......@@ -14,7 +14,7 @@
namespace blink {
class DOMTokenList;
class CustomStateSet;
class HTMLElement;
class ValidityStateFlags;
......@@ -50,7 +50,7 @@ class CORE_EXPORT ElementInternals : public ScriptWrappable,
bool checkValidity(ExceptionState& exception_state);
bool reportValidity(ExceptionState& exception_state);
LabelsNodeList* labels(ExceptionState& exception_state);
DOMTokenList* states();
CustomStateSet* states();
bool HasState(const AtomicString& state) const;
......@@ -107,7 +107,7 @@ class CORE_EXPORT ElementInternals : public ScriptWrappable,
Member<ValidityStateFlags> validity_flags_;
Member<Element> validation_anchor_;
Member<DOMTokenList> custom_states_;
Member<CustomStateSet> custom_states_;
HashMap<QualifiedName, AtomicString> accessibility_semantics_map_;
......
......@@ -25,8 +25,8 @@ interface ElementInternals {
[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;
// https://wicg.github.io/custom-state-pseudo-class/#dom-elementinternals-states
[RuntimeEnabled=CustomStatePseudoClass] readonly attribute CustomStateSet states;
// Access to shadowRoot from custom elements. See crbug.com/1042130 and
// https://github.com/w3c/webcomponents/issues/871#issuecomment-672082936
......
......@@ -17,49 +17,36 @@ 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');
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'); });
assert_throws_dom('SyntaxError', () => { i.states.add(''); });
assert_throws_dom('InvalidCharacterError', () => { i.states.add('a\tb'); });
}, 'DOMTokenList behavior of ElementInternals.states: Exceptions');
assert_throws_dom('SyntaxError', () => { i.states.add('--a\tb'); });
}, 'CustomStateSet 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');
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');
</script>
......@@ -94,7 +94,7 @@ test(() => {
element.focus();
let states = element.i.states;
states.value = '--foo';
states.add('--foo');
assert_true(element.matches(':focus:--foo'));
assert_true(element.matches(':--foo:focus'));
}, ':--foo and other pseudo classes');
......@@ -109,10 +109,11 @@ test(() => {
innerStates.add('--innerFoo');
assert_equals(getComputedStyle(inner).opacity, '0.5',
'::part() followed by :--foo');
innerStates.replace('--innerFoo', '--innerfoo');
innerStates.delete('--innerFoo');
innerStates.add('--innerfoo');
assert_equals(getComputedStyle(inner).opacity, '0',
':--foo matching should be case-sensitive');
innerStates.remove('--innerfoo');
innerStates.delete('--innerfoo');
outer.i.states.add('--outerFoo');
assert_equals(getComputedStyle(inner).opacity, '0.25',
......@@ -124,7 +125,7 @@ test(() => {
document.body.appendChild(outer);
assert_equals(getComputedStyle(outer).borderStyle, 'solid');
outer.i.states.toggle('--dotted');
outer.i.states.add('--dotted');
assert_equals(getComputedStyle(outer).borderStyle, 'dotted');
}, ':--foo and :host()');
</script>
......
This is a testharness.js-based test.
PASS idl_test setup
PASS idl_test validation
PASS Partial interface ElementInternals: original interface defined
PASS Partial interface ElementInternals: member names are unique
PASS ElementInternals includes ARIAMixin: member names are unique
FAIL CustomStateSet interface: existence and properties of interface object assert_own_property: self does not have own property "CustomStateSet" expected property "CustomStateSet" missing
FAIL CustomStateSet interface object length assert_own_property: self does not have own property "CustomStateSet" expected property "CustomStateSet" missing
FAIL CustomStateSet interface object name assert_own_property: self does not have own property "CustomStateSet" expected property "CustomStateSet" missing
FAIL CustomStateSet interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CustomStateSet" expected property "CustomStateSet" missing
FAIL CustomStateSet interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CustomStateSet" expected property "CustomStateSet" missing
FAIL CustomStateSet interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CustomStateSet" expected property "CustomStateSet" missing
FAIL CustomStateSet interface: operation add(DOMString) assert_own_property: self does not have own property "CustomStateSet" expected property "CustomStateSet" missing
FAIL CustomStateSet must be primary interface of customStateSet assert_own_property: self does not have own property "CustomStateSet" expected property "CustomStateSet" missing
FAIL Stringification of customStateSet assert_class_string: class string of customStateSet expected "[object CustomStateSet]" but got "[object DOMTokenList]"
PASS CustomStateSet interface: customStateSet must inherit property "add(DOMString)" with the proper type
FAIL CustomStateSet interface: calling add(DOMString) on customStateSet with too few arguments must throw TypeError assert_throws_js: Called with 0 arguments function "function() {
fn.apply(obj, args);
}" did not throw
PASS ElementInternals interface: attribute states
Harness: the test ran to completion.
......@@ -1279,6 +1279,19 @@ interface CustomEvent : Event
getter detail
method constructor
method initCustomEvent
interface CustomStateSet
attribute @@toStringTag
getter size
method @@iterator
method add
method clear
method constructor
method delete
method entries
method forEach
method has
method keys
method values
interface DOMError
attribute @@toStringTag
getter message
......
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