Skip to content

Commit 8f4d32c

Browse files
mfreed7Commit Bot
authored and
Commit Bot
committed
Restrict attachInternals() to be run inside or after the constructor
Per the discussion at [1], the intention of this change is to prevent calls to attachInternals() prior to the constructor of the custom element having a chance to do so. The spec PR is at [2]. This change is gated behind the DeclarativeShadowDOM flag. With the flag disabled (the default), a use counter is added for checking on the web compatibility of this change. The use counter will measure the cases where attachInternals() is being called in a to-be-outlawed way. [1] WICG/webcomponents#871 (comment) [2] whatwg/html#5909 Bug: 1042130 Change-Id: Iacf97a49133b5f7f44710e5c0287f01cfebe4c44 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2392975 Reviewed-by: Alexei Svitkine <[email protected]> Reviewed-by: Kouhei Ueno <[email protected]> Commit-Queue: Mason Freed <[email protected]> Auto-Submit: Mason Freed <[email protected]> Cr-Commit-Position: refs/heads/master@{#806830}
1 parent 00bab91 commit 8f4d32c

File tree

11 files changed

+124
-21
lines changed

11 files changed

+124
-21
lines changed

Diff for: components/page_load_metrics/browser/observers/use_counter/ukm_features.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// (or highly likely be) rare, e.g. <1% of page views as measured by UMA.
1313
//
1414
// UKM-based UseCounter should be used to cover the case when UMA UseCounter
15-
// data shows a behaviour that is rare but too common to bindly change.
15+
// data shows a behaviour that is rare but too common to blindly change.
1616
// UKM-based UseCounter would allow use to find specific pages to reason about
1717
// either a breaking change is acceptable or not.
1818

@@ -179,6 +179,7 @@ UseCounterPageLoadMetricsObserver::GetAllowedUkmFeatures() {
179179
WebFeature::kSchemelesslySameSitePostMessage,
180180
WebFeature::kSchemelesslySameSitePostMessageSecureToInsecure,
181181
WebFeature::kSchemelesslySameSitePostMessageInsecureToSecure,
182+
WebFeature::kElementAttachInternalsBeforeConstructor,
182183
}));
183184
return *opt_in_features;
184185
}

Diff for: third_party/blink/public/mojom/web_feature/web_feature.mojom

+1
Original file line numberDiff line numberDiff line change
@@ -2794,6 +2794,7 @@ enum WebFeature {
27942794
kCSSContainAllWithoutContentVisibility = 3467,
27952795
kTimerInstallFromBeforeUnload = 3468,
27962796
kTimerInstallFromUnload = 3469,
2797+
kElementAttachInternalsBeforeConstructor = 3470,
27972798

27982799
// Add new features immediately above this line. Don't change assigned
27992800
// numbers of any item, and don't reuse removed slots.

Diff for: third_party/blink/renderer/bindings/core/v8/script_custom_element_definition.cc

+3
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ bool ScriptCustomElementDefinition::RunConstructor(Element& element) {
214214
return false;
215215
}
216216

217+
// 8.1.new: set custom element state to kPreCustomized.
218+
element.SetCustomElementState(CustomElementState::kPreCustomized);
219+
217220
Element* result = CallConstructor();
218221

219222
// To report exception thrown from callConstructor()

Diff for: third_party/blink/renderer/core/dom/node.cc

+6-1
Original file line numberDiff line numberDiff line change
@@ -3232,12 +3232,17 @@ void Node::SetCustomElementState(CustomElementState new_state) {
32323232

32333233
case CustomElementState::kCustom:
32343234
DCHECK(old_state == CustomElementState::kUndefined ||
3235-
old_state == CustomElementState::kFailed);
3235+
old_state == CustomElementState::kFailed ||
3236+
old_state == CustomElementState::kPreCustomized);
32363237
break;
32373238

32383239
case CustomElementState::kFailed:
32393240
DCHECK_NE(CustomElementState::kFailed, old_state);
32403241
break;
3242+
3243+
case CustomElementState::kPreCustomized:
3244+
DCHECK_EQ(CustomElementState::kFailed, old_state);
3245+
break;
32413246
}
32423247

32433248
DCHECK(IsHTMLElement());

Diff for: third_party/blink/renderer/core/dom/node.h

+13-12
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ enum class CustomElementState : uint32_t {
101101
// https://dom.spec.whatwg.org/#concept-element-custom-element-state
102102
kUncustomized = 0,
103103
kCustom = 1 << kNodeCustomElementShift,
104-
kUndefined = 2 << kNodeCustomElementShift,
105-
kFailed = 3 << kNodeCustomElementShift,
104+
kPreCustomized = 2 << kNodeCustomElementShift,
105+
kUndefined = 3 << kNodeCustomElementShift,
106+
kFailed = 4 << kNodeCustomElementShift,
106107
};
107108

108109
enum class SlotChangeType {
@@ -970,24 +971,24 @@ class CORE_EXPORT Node : public EventTarget {
970971
kChildNeedsStyleRecalcFlag = 1 << 16,
971972
kStyleChangeMask = 0x3 << kNodeStyleChangeShift,
972973

973-
kCustomElementStateMask = 0x3 << kNodeCustomElementShift,
974+
kCustomElementStateMask = 0x7 << kNodeCustomElementShift,
974975

975-
kHasNameOrIsEditingTextFlag = 1 << 21,
976-
kHasEventTargetDataFlag = 1 << 22,
976+
kHasNameOrIsEditingTextFlag = 1 << 22,
977+
kHasEventTargetDataFlag = 1 << 23,
977978

978-
kV0CustomElementFlag = 1 << 23,
979-
kV0CustomElementUpgradedFlag = 1 << 24,
979+
kV0CustomElementFlag = 1 << 24,
980+
kV0CustomElementUpgradedFlag = 1 << 25,
980981

981-
kNeedsReattachLayoutTree = 1 << 25,
982-
kChildNeedsReattachLayoutTree = 1 << 26,
982+
kNeedsReattachLayoutTree = 1 << 26,
983+
kChildNeedsReattachLayoutTree = 1 << 27,
983984

984-
kHasDuplicateAttributes = 1 << 27,
985+
kHasDuplicateAttributes = 1 << 28,
985986

986-
kForceReattachLayoutTree = 1 << 28,
987+
kForceReattachLayoutTree = 1 << 29,
987988

988989
kDefaultNodeFlags = kIsFinishedParsingChildrenFlag,
989990

990-
// 4 bits remaining.
991+
// 3 bits remaining.
991992
};
992993

993994
ALWAYS_INLINE bool GetFlag(NodeFlags mask) const {

Diff for: third_party/blink/renderer/core/html/custom/custom_element_definition.cc

+4-4
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,10 @@ CustomElementDefinition::ConstructionStackScope::~ConstructionStackScope() {
196196

197197
// https://html.spec.whatwg.org/C/#concept-upgrade-an-element
198198
void CustomElementDefinition::Upgrade(Element& element) {
199-
// 4.13.5.1 If element is custom, then return.
200-
// 4.13.5.2 If element's custom element state is "failed", then return.
201-
if (element.GetCustomElementState() == CustomElementState::kCustom ||
202-
element.GetCustomElementState() == CustomElementState::kFailed) {
199+
// 4.13.5.1 If element's custom element state is not "undefined" or
200+
// "uncustomized", then return.
201+
if (element.GetCustomElementState() != CustomElementState::kUndefined &&
202+
element.GetCustomElementState() != CustomElementState::kUncustomized) {
203203
return;
204204
}
205205

Diff for: third_party/blink/renderer/core/html/custom/element_internals.cc

+3-2
Original file line numberDiff line numberDiff line change
@@ -329,10 +329,11 @@ bool ElementInternals::IsTargetFormAssociated() const {
329329
if (Target().IsFormAssociatedCustomElement())
330330
return true;
331331
// Custom element could be in the process of upgrading here, during which
332-
// it will have state kFailed according to:
332+
// it will have state kFailed or kPreCustomized according to:
333333
// https://html.spec.whatwg.org/multipage/custom-elements.html#upgrades
334334
if (Target().GetCustomElementState() != CustomElementState::kUndefined &&
335-
Target().GetCustomElementState() != CustomElementState::kFailed) {
335+
Target().GetCustomElementState() != CustomElementState::kFailed &&
336+
Target().GetCustomElementState() != CustomElementState::kPreCustomized) {
336337
return false;
337338
}
338339
// An element is in "undefined" state in its constructor JavaScript code.

Diff for: third_party/blink/renderer/core/html/html_element.cc

+19
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,7 @@ ElementInternals* HTMLElement::attachInternals(
15831583
"Unable to attach ElementInternals to a customized built-in element.");
15841584
return nullptr;
15851585
}
1586+
15861587
CustomElementRegistry* registry = CustomElement::Registry(*this);
15871588
auto* definition =
15881589
registry ? registry->DefinitionForName(localName()) : nullptr;
@@ -1592,6 +1593,7 @@ ElementInternals* HTMLElement::attachInternals(
15921593
"Unable to attach ElementInternals to non-custom elements.");
15931594
return nullptr;
15941595
}
1596+
15951597
if (definition->DisableInternals()) {
15961598
exception_state.ThrowDOMException(
15971599
DOMExceptionCode::kNotSupportedError,
@@ -1604,6 +1606,23 @@ ElementInternals* HTMLElement::attachInternals(
16041606
"ElementInternals for the specified element was already attached.");
16051607
return nullptr;
16061608
}
1609+
1610+
// If element's custom element state is not "precustomized" or "custom",
1611+
// throw "NotSupportedError" DOMException.
1612+
if (GetCustomElementState() != CustomElementState::kCustom &&
1613+
GetCustomElementState() != CustomElementState::kPreCustomized) {
1614+
if (RuntimeEnabledFeatures::DeclarativeShadowDOMEnabled(
1615+
GetExecutionContext())) {
1616+
exception_state.ThrowDOMException(
1617+
DOMExceptionCode::kNotSupportedError,
1618+
"The attachInternals() function cannot be called prior to the "
1619+
"execution of the custom element constructor.");
1620+
return nullptr;
1621+
}
1622+
UseCounter::Count(GetDocument(),
1623+
WebFeature::kElementAttachInternalsBeforeConstructor);
1624+
}
1625+
16071626
UseCounter::Count(GetDocument(), WebFeature::kElementAttachInternals);
16081627
SetDidAttachInternals();
16091628
return &EnsureElementInternals();

Diff for: third_party/blink/renderer/core/html/parser/html_construction_site.cc

+3-1
Original file line numberDiff line numberDiff line change
@@ -989,8 +989,10 @@ Element* HTMLConstructionSite::CreateElement(
989989
document, tag_name, GetCreateElementFlags(), is);
990990
}
991991
// Definition for the created element does not exist here and it cannot be
992-
// custom or failed.
992+
// custom, precustomized, or failed.
993993
DCHECK_NE(element->GetCustomElementState(), CustomElementState::kCustom);
994+
DCHECK_NE(element->GetCustomElementState(),
995+
CustomElementState::kPreCustomized);
994996
DCHECK_NE(element->GetCustomElementState(), CustomElementState::kFailed);
995997

996998
// TODO(dominicc): Move these steps so they happen for custom

Diff for: third_party/blink/web_tests/external/wpt/shadow-dom/declarative/element-internals-shadowroot.tentative.html

+69
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,73 @@
4141
assert_true(constructed);
4242
}, 'ElementInternals.shadowRoot allows access to closed shadow root');
4343

44+
test(() => {
45+
let constructed = false;
46+
const element = document.createElement('x-1');
47+
assert_throws_dom('NotSupportedError', () => element.attachInternals(),'attachInternals cannot be called before definition exists');
48+
customElements.define('x-1', class extends HTMLElement {
49+
constructor() {
50+
super();
51+
assert_true(!!this.attachInternals());
52+
constructed = true;
53+
}
54+
});
55+
assert_false(constructed);
56+
assert_throws_dom('NotSupportedError', () => element.attachInternals(),'attachInternals cannot be called before constructor');
57+
customElements.upgrade(element);
58+
assert_true(constructed);
59+
assert_throws_dom('NotSupportedError', () => element.attachInternals(),'attachInternals already called');
60+
}, 'ElementInternals cannot be called before constructor, upgrade case');
61+
62+
test(() => {
63+
let constructed = false;
64+
const element = document.createElement('x-2');
65+
customElements.define('x-2', class extends HTMLElement {
66+
constructor() {
67+
super();
68+
// Don't attachInternals() here
69+
constructed = true;
70+
}
71+
});
72+
assert_throws_dom('NotSupportedError', () => element.attachInternals(),'attachInternals cannot be called before constructor');
73+
assert_false(constructed);
74+
customElements.upgrade(element);
75+
assert_true(constructed);
76+
assert_true(!!element.attachInternals(),'After the constructor, ok to call from outside');
77+
}, 'ElementInternals *can* be called after constructor, upgrade case');
78+
79+
test(() => {
80+
let constructed = false;
81+
customElements.define('x-3', class extends HTMLElement {
82+
constructor() {
83+
super();
84+
assert_true(!!this.attachInternals());
85+
constructed = true;
86+
}
87+
});
88+
const element = document.createElement('x-3');
89+
assert_true(constructed);
90+
assert_throws_dom('NotSupportedError', () => element.attachInternals(), 'attachInternals already called');
91+
}, 'ElementInternals cannot be called after constructor calls it, create case');
92+
93+
test(() => {
94+
let constructed = false;
95+
const element = document.createElement('x-5');
96+
customElements.define('x-5', class extends HTMLElement {
97+
static disabledFeatures = [ 'internals' ];
98+
constructor() {
99+
super();
100+
assert_throws_dom('NotSupportedError', () => this.attachInternals(), 'attachInternals forbidden by disabledFeatures, constructor');
101+
constructed = true;
102+
}
103+
});
104+
assert_false(constructed);
105+
assert_throws_dom('NotSupportedError', () => element.attachInternals(), 'attachInternals forbidden by disabledFeatures, pre-upgrade');
106+
customElements.upgrade(element);
107+
assert_true(constructed);
108+
assert_throws_dom('NotSupportedError', () => element.attachInternals(), 'attachInternals forbidden by disabledFeatures, post-upgrade');
109+
}, 'ElementInternals disabled by disabledFeatures');
110+
111+
112+
44113
</script>

Diff for: tools/metrics/histograms/enums.xml

+1
Original file line numberDiff line numberDiff line change
@@ -29157,6 +29157,7 @@ Called by update_use_counter_feature_enum.py.-->
2915729157
<int value="3467" label="CSSContainAllWithoutContentVisibility"/>
2915829158
<int value="3468" label="TimerInstallFromBeforeUnload"/>
2915929159
<int value="3469" label="TimerInstallFromUnload"/>
29160+
<int value="3470" label="ElementAttachInternalsBeforeConstructor"/>
2916029161
</enum>
2916129162

2916229163
<enum name="FeaturePolicyAllowlistType">

0 commit comments

Comments
 (0)