Commit 165c8900 authored by Kent Tamura's avatar Kent Tamura Committed by Commit Bot

Form-associated custom elements: Implement HTMLElement.prototype.attachInternals().

- Introduce empty ElementInternals interface
- Add HTMLElement.prototype.attachInternals()
- Add a flag to ElementRareData about ElementInternals attachment
- Promote "ElementInternals" runtime flag to "test"

All the change is behind the runtime flag.
Some test cases in external/wpt/custom-elements/CustomElementRegistry.html
fail by this CL because this CL enables GET() operation for "disabledFeatures",
and these test cases doesn't expect it.  We should update the testcases later.


Bug: 905922
Bug: https://github.com/w3c/webcomponents/issues/758
Change-Id: I7b3dbfd1d422c7eae5c7a6e01ddea50a00134a6e
Reviewed-on: https://chromium-review.googlesource.com/c/1341734Reviewed-by: default avatarHayato Ito <hayato@chromium.org>
Commit-Queue: Kent Tamura <tkent@chromium.org>
Cr-Commit-Position: refs/heads/master@{#609231}
parent a562f181
<!DOCTYPE html>
<body>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<div id="container"></div>
<script>
test(() => {
class MyElement1 extends HTMLElement {
}
customElements.define('my-element1', MyElement1);
const container = document.querySelector('#container');
let element = new MyElement1();
assert_true(element.attachInternals() instanceof ElementInternals,
'New - 1st call');
assert_throws('InvalidStateError', () => { element.attachInternals(); },
'New - 2nd call');
element = document.createElement('my-element1');
assert_true(element.attachInternals() instanceof ElementInternals,
'createElement - 1st call');
assert_throws('InvalidStateError', () => { element.attachInternals(); },
'createElement - 2nd call');
container.innerHTML = '<my-element1></my-element1>';
assert_true(container.firstChild.attachInternals() instanceof ElementInternals,
'Parser - 1st call');
assert_throws('InvalidStateError', () => {
container.firstChild.attachInternals();
}, 'Parser - 2nd call');
}, 'Successful attachInternals() and the second call.');
test(() => {
const builtin = document.createElement('div');
assert_throws('InvalidStateError', () => { builtin.attachInternals() });
const undefinedCustom = document.createElement('my-element');
assert_throws('InvalidStateError', () => { undefinedCustom.attachInternals() });
}, 'If a custom element definition for the local name of the element doesn\'t' +
' exist, throw an InvalidStateError');
test(() => {
class MyElement2 extends HTMLElement {
static get disabledFeatures() { return ['internals']; }
}
customElements.define('my-element2', MyElement2);
const container = document.querySelector('#container');
assert_throws('InvalidStateError', () => {
(new MyElement2).attachInternals();
});
assert_throws('InvalidStateError', () => {
document.createElement('my-element2').attachInternals();
});
assert_throws('InvalidStateError', () => {
container.innerHTML = '<my-element2></my-element2>';
container.firstChild.attachInternals();
});
}, 'If a custom element definition for the local name of the element has ' +
'disable internals flag, throw an InvalidStateError');
</script>
</body>
This is a testharness.js-based test.
PASS CustomElementRegistry interface must have define as a method
PASS customElements.define must throw when the element interface is not a constructor
PASS customElements.define must not throw the constructor is HTMLElement
PASS customElements.define must throw with an invalid name
PASS customElements.define must throw when there is already a custom element of the same name
PASS customElements.define must throw a NotSupportedError when there is already a custom element with the same class
PASS customElements.define must throw a NotSupportedError when element definition is running flag is set
PASS customElements.define must check IsConstructor on the constructor before checking the element definition is running flag
PASS customElements.define must validate the custom element name before checking the element definition is running flag
PASS customElements.define unset the element definition is running flag before upgrading custom elements
FAIL customElements.define must not throw when defining another custom element in a different global object during Get(constructor, "prototype") Failed to execute 'define' on 'CustomElementRegistry': this name has already been used with this registry
PASS Custom Elements: CustomElementRegistry interface
FAIL customElements.define must get "prototype" property of the constructor assert_array_equals: lengths differ, expected 1 got 2
PASS customElements.define must rethrow an exception thrown while getting "prototype" property of the constructor
PASS customElements.define must throw when "prototype" property of the constructor is not an object
PASS customElements.define must get callbacks of the constructor prototype
PASS customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype
PASS customElements.define must rethrow an exception thrown while converting a callback value to Function callback type
FAIL customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present assert_array_equals: lengths differ, expected 4 got 6
PASS customElements.define must rethrow an exception thrown while getting observedAttributes on the constructor prototype
PASS customElements.define must rethrow an exception thrown while converting the value of observedAttributes to sequence<DOMString>
PASS customElements.define must rethrow an exception thrown while iterating over observedAttributes to sequence<DOMString>
PASS customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on observedAttributes
PASS customElements.define must not throw even if "observedAttributes" fails to convert if "attributeChangedCallback" is not defined
PASS customElements.define must define an instantiatable custom element
PASS customElements.define must upgrade elements in the shadow-including tree order
PASS CustomElementRegistry interface must have get as a method
PASS customElements.get must return undefined when the registry does not contain an entry with the given name
PASS customElements.get must return undefined when the registry does not contain an entry with the given name even if the name was not a valid custom element name
PASS customElements.get return the constructor of the entry with the given name when there is a matching entry.
PASS customElements.whenDefined must return a promise for a valid custom element name
PASS customElements.whenDefined must return the same promise each time invoked for a valid custom element name which has not been defined
PASS customElements.whenDefined must return an unresolved promise when the registry does not contain the entry with the given name
PASS customElements.whenDefined must return a rejected promise when the given name is not a valid custom element name
PASS customElements.whenDefined must return a resolved promise when the registry contains the entry with the given name
PASS customElements.whenDefined must return a new resolved promise each time invoked when the registry contains the entry with the given name
PASS A promise returned by customElements.whenDefined must be resolved by "define"
Harness: the test ran to completion.
...@@ -73,6 +73,7 @@ namespace http://www.w3.org/1999/xhtml ...@@ -73,6 +73,7 @@ namespace http://www.w3.org/1999/xhtml
property ariaValueNow property ariaValueNow
property ariaValueText property ariaValueText
property assignedSlot property assignedSlot
property attachInternals
property attachShadow property attachShadow
property attributeStyleMap property attributeStyleMap
property attributes property attributes
......
...@@ -2035,6 +2035,9 @@ interface Element : Node ...@@ -2035,6 +2035,9 @@ interface Element : Node
setter scrollLeft setter scrollLeft
setter scrollTop setter scrollTop
setter slot setter slot
interface ElementInternals
attribute @@toStringTag
method constructor
interface EnterPictureInPictureEvent : Event interface EnterPictureInPictureEvent : Event
attribute @@toStringTag attribute @@toStringTag
getter pictureInPictureWindow getter pictureInPictureWindow
...@@ -2656,6 +2659,7 @@ interface HTMLElement : Element ...@@ -2656,6 +2659,7 @@ interface HTMLElement : Element
getter tabIndex getter tabIndex
getter title getter title
getter translate getter translate
method attachInternals
method blur method blur
method click method click
method constructor method constructor
......
...@@ -266,6 +266,7 @@ core_idl_files = ...@@ -266,6 +266,7 @@ core_idl_files =
"html/canvas/image_data.idl", "html/canvas/image_data.idl",
"html/canvas/text_metrics.idl", "html/canvas/text_metrics.idl",
"html/custom/custom_element_registry.idl", "html/custom/custom_element_registry.idl",
"html/custom/element_internals.idl",
"html/forms/form_data.idl", "html/forms/form_data.idl",
"html/forms/form_data_event.idl", "html/forms/form_data_event.idl",
"html/forms/html_button_element.idl", "html/forms/html_button_element.idl",
......
...@@ -2666,6 +2666,14 @@ const AtomicString& Element::IsValue() const { ...@@ -2666,6 +2666,14 @@ const AtomicString& Element::IsValue() const {
return g_null_atom; return g_null_atom;
} }
void Element::SetDidAttachInternals() {
EnsureElementRareData().SetDidAttachInternals();
}
bool Element::DidAttachInternals() const {
return HasRareData() && GetElementRareData()->DidAttachInternals();
}
ShadowRoot* Element::createShadowRoot(ExceptionState& exception_state) { ShadowRoot* Element::createShadowRoot(ExceptionState& exception_state) {
if (ShadowRoot* root = GetShadowRoot()) { if (ShadowRoot* root = GetShadowRoot()) {
if (root->IsUserAgent()) { if (root->IsUserAgent()) {
......
...@@ -808,6 +808,8 @@ class CORE_EXPORT Element : public ContainerNode { ...@@ -808,6 +808,8 @@ class CORE_EXPORT Element : public ContainerNode {
// https://dom.spec.whatwg.org/#concept-element-is-value // https://dom.spec.whatwg.org/#concept-element-is-value
void SetIsValue(const AtomicString&); void SetIsValue(const AtomicString&);
const AtomicString& IsValue() const; const AtomicString& IsValue() const;
void SetDidAttachInternals();
bool DidAttachInternals() const;
bool ContainsFullScreenElement() const { bool ContainsFullScreenElement() const {
return HasElementFlag(ElementFlags::kContainsFullScreenElement); return HasElementFlag(ElementFlags::kContainsFullScreenElement);
......
...@@ -42,6 +42,7 @@ struct SameSizeAsElementRareData : NodeRareData { ...@@ -42,6 +42,7 @@ struct SameSizeAsElementRareData : NodeRareData {
IntSize scroll_offset; IntSize scroll_offset;
void* pointers_or_strings[5]; void* pointers_or_strings[5];
Member<void*> members[15]; Member<void*> members[15];
bool flags[1];
}; };
ElementRareData::ElementRareData(NodeRenderingData* node_layout_data) ElementRareData::ElementRareData(NodeRenderingData* node_layout_data)
......
...@@ -155,6 +155,8 @@ class ElementRareData : public NodeRareData { ...@@ -155,6 +155,8 @@ class ElementRareData : public NodeRareData {
} }
void SetIsValue(const AtomicString& is_value) { is_value_ = is_value; } void SetIsValue(const AtomicString& is_value) { is_value_ = is_value; }
const AtomicString& IsValue() const { return is_value_; } const AtomicString& IsValue() const { return is_value_; }
void SetDidAttachInternals() { did_attach_internals_ = true; }
bool DidAttachInternals() const { return did_attach_internals_; }
AccessibleNode* GetAccessibleNode() const { return accessible_node_.Get(); } AccessibleNode* GetAccessibleNode() const { return accessible_node_.Get(); }
AccessibleNode* EnsureAccessibleNode(Element* owner_element) { AccessibleNode* EnsureAccessibleNode(Element* owner_element) {
...@@ -235,6 +237,7 @@ class ElementRareData : public NodeRareData { ...@@ -235,6 +237,7 @@ class ElementRareData : public NodeRareData {
TraceWrapperMember<AccessibleNode> accessible_node_; TraceWrapperMember<AccessibleNode> accessible_node_;
WeakMember<DisplayLockContext> display_lock_context_; WeakMember<DisplayLockContext> display_lock_context_;
bool did_attach_internals_ = false;
explicit ElementRareData(NodeRenderingData*); explicit ElementRareData(NodeRenderingData*);
}; };
......
...@@ -68,6 +68,8 @@ blink_core_sources("html") { ...@@ -68,6 +68,8 @@ blink_core_sources("html") {
"custom/custom_element_upgrade_reaction.h", "custom/custom_element_upgrade_reaction.h",
"custom/custom_element_upgrade_sorter.cc", "custom/custom_element_upgrade_sorter.cc",
"custom/custom_element_upgrade_sorter.h", "custom/custom_element_upgrade_sorter.h",
"custom/element_internals.cc",
"custom/element_internals.h",
"custom/v0_custom_element.cc", "custom/v0_custom_element.cc",
"custom/v0_custom_element.h", "custom/v0_custom_element.h",
"custom/v0_custom_element_async_import_microtask_queue.cc", "custom/v0_custom_element_async_import_microtask_queue.cc",
......
...@@ -103,6 +103,7 @@ class CORE_EXPORT CustomElementDefinition ...@@ -103,6 +103,7 @@ class CORE_EXPORT CustomElementDefinition
bool HasDefaultStyleSheets() const { bool HasDefaultStyleSheets() const {
return !default_style_sheets_.IsEmpty(); return !default_style_sheets_.IsEmpty();
} }
bool DisableInternals() const { return disable_internals_; }
class CORE_EXPORT ConstructionStackScope final { class CORE_EXPORT ConstructionStackScope final {
STACK_ALLOCATED(); STACK_ALLOCATED();
......
// Copyright 2018 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/element_internals.h"
#include "third_party/blink/renderer/core/html/html_element.h"
namespace blink {
ElementInternals::ElementInternals(HTMLElement& target) : target_(target) {}
void ElementInternals::Trace(Visitor* visitor) {
visitor->Trace(target_);
ScriptWrappable::Trace(visitor);
}
} // namespace blink
// Copyright 2018 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_ELEMENT_INTERNALS_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_CUSTOM_ELEMENT_INTERNALS_H_
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
namespace blink {
class HTMLElement;
class ElementInternals : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
ElementInternals(HTMLElement& target);
void Trace(Visitor* visitor) override;
private:
Member<HTMLElement> target_;
DISALLOW_COPY_AND_ASSIGN(ElementInternals);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_CUSTOM_ELEMENT_INTERNALS_H_
// Copyright 2018 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://docs.google.com/document/d/1JO8puctCSpW-ZYGU8lF-h4FWRIDQNDVexzHoOQ2iQmY/edit?pli=1#heading=h.pjt9nhs3gu3k
[
Exposed=Window,
RuntimeEnabled=ElementInternals
]
interface ElementInternals {
// TODO(tkent): Add operations.
};
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include "third_party/blink/renderer/core/css/style_change_reason.h" #include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/css_value_keywords.h" #include "third_party/blink/renderer/core/css_value_keywords.h"
#include "third_party/blink/renderer/core/dom/document_fragment.h" #include "third_party/blink/renderer/core/dom/document_fragment.h"
#include "third_party/blink/renderer/core/dom/element_rare_data.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h" #include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/events/event_listener.h" #include "third_party/blink/renderer/core/dom/events/event_listener.h"
#include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
...@@ -51,6 +52,9 @@ ...@@ -51,6 +52,9 @@
#include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/use_counter.h" #include "third_party/blink/renderer/core/frame/use_counter.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/element_internals.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h" #include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/html_br_element.h" #include "third_party/blink/renderer/core/html/html_br_element.h"
...@@ -1407,6 +1411,32 @@ void HTMLElement::OnXMLLangAttrChanged( ...@@ -1407,6 +1411,32 @@ void HTMLElement::OnXMLLangAttrChanged(
Element::ParseAttribute(params); Element::ParseAttribute(params);
} }
ElementInternals* HTMLElement::attachInternals(
ExceptionState& exception_state) {
auto* definition =
CustomElement::Registry(*this)->DefinitionForName(localName());
if (!definition) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Unable to attach ElementInternals to non-custom elements.");
return nullptr;
}
if (definition->DisableInternals()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"ElementInternals is disabled by disabledFeature static field.");
return nullptr;
}
if (DidAttachInternals()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"ElementInternals for the specified element was already attached.");
return nullptr;
}
SetDidAttachInternals();
return MakeGarbageCollected<ElementInternals>(*this);
}
} // namespace blink } // namespace blink
#ifndef NDEBUG #ifndef NDEBUG
......
...@@ -32,6 +32,7 @@ namespace blink { ...@@ -32,6 +32,7 @@ namespace blink {
struct AttributeTriggers; struct AttributeTriggers;
class Color; class Color;
class DocumentFragment; class DocumentFragment;
class ElementInternals;
class ExceptionState; class ExceptionState;
class FormAssociated; class FormAssociated;
class HTMLFormElement; class HTMLFormElement;
...@@ -129,6 +130,7 @@ class CORE_EXPORT HTMLElement : public Element { ...@@ -129,6 +130,7 @@ class CORE_EXPORT HTMLElement : public Element {
Element* unclosedOffsetParent(); Element* unclosedOffsetParent();
ElementInternals* attachInternals(ExceptionState& exception_state);
virtual FormAssociated* ToFormAssociatedOrNull() { return nullptr; }; virtual FormAssociated* ToFormAssociatedOrNull() { return nullptr; };
protected: protected:
......
...@@ -58,6 +58,10 @@ interface HTMLElement : Element { ...@@ -58,6 +58,10 @@ interface HTMLElement : Element {
// https://drafts.csswg.org/cssom/#the-elementcssinlinestyle-interface // https://drafts.csswg.org/cssom/#the-elementcssinlinestyle-interface
[Affects=Nothing, SameObject, PerWorldBindings, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; [Affects=Nothing, SameObject, PerWorldBindings, PutForwards=cssText] readonly attribute CSSStyleDeclaration style;
// Form-associated custom elements
// https://docs.google.com/document/d/1JO8puctCSpW-ZYGU8lF-h4FWRIDQNDVexzHoOQ2iQmY/edit?pli=1#heading=h.pjt9nhs3gu3k
[RuntimeEnabled=ElementInternals, RaisesException] ElementInternals attachInternals();
// Non-standard APIs // Non-standard APIs
[Affects=Nothing, CEReactions, CustomElementCallbacks, RaisesException=Setter, MeasureAs=HTMLElementInnerText] attribute ([TreatNullAs=EmptyString] DOMString or TrustedScript) innerText; [Affects=Nothing, CEReactions, CustomElementCallbacks, RaisesException=Setter, MeasureAs=HTMLElementInnerText] attribute ([TreatNullAs=EmptyString] DOMString or TrustedScript) innerText;
[Affects=Nothing, CEReactions, CustomElementCallbacks, RaisesException=Setter, MeasureAs=HTMLElementOuterText] attribute [TreatNullAs=EmptyString] DOMString outerText; [Affects=Nothing, CEReactions, CustomElementCallbacks, RaisesException=Setter, MeasureAs=HTMLElementOuterText] attribute [TreatNullAs=EmptyString] DOMString outerText;
......
...@@ -399,6 +399,7 @@ ...@@ -399,6 +399,7 @@
{ {
// http://crbug.com/905922 // http://crbug.com/905922
name: "ElementInternals", name: "ElementInternals",
status: "test",
}, },
{ {
// https://crbug.com/879270 // https://crbug.com/879270
......
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