Skip to content

Commit

Permalink
Disallow recursive custom element constructions
Browse files Browse the repository at this point in the history
With this CL, recursive custom element constructions are no
longer allowed. I.e. this will now only run the constructor once:
  class extends HTMLElement {
    constructor() {
      super();
      customElements.upgrade(this);
    }
  }

Previously, the code and spec had a bug which caused the above
code snippet to infinitely recurse. In [1] the spec has changed,
to set the custom element state to "failed" before the constructor
is called. With this change in place, recursive calls will
early-out at step #2 (of [2]), and avoid the recursion.

[1] whatwg/html#5126
[2] https://html.spec.whatwg.org/multipage/custom-elements.html#upgrades

Bug: 966472
Change-Id: I76e88c0b70132eee2482c304ef9e727ae1fe8fc7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1931644
Reviewed-by: Kent Tamura <tkent@chromium.org>
Commit-Queue: Mason Freed <masonfreed@chromium.org>
Auto-Submit: Mason Freed <masonfreed@chromium.org>
Cr-Commit-Position: refs/heads/master@{#727841}
  • Loading branch information
mfreed7 authored and chromium-wpt-export-bot committed Dec 31, 2019
1 parent 6e3355f commit 4f24cab
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 0 deletions.
18 changes: 18 additions & 0 deletions custom-elements/pseudo-class-defined.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,22 @@
assert_equals(style.color, expected ? defined : not_defined, 'getComputedStyle');
}, `${description} should ${expected ? 'be' : 'not be'} :defined`);
}

test(function () {
var log = [];
var instance = document.createElement('my-custom-element-2');
document.body.appendChild(instance);
customElements.define('my-custom-element-2',class extends HTMLElement {
constructor() {
super();
log.push([this, 'begin']);
assert_false(this.matches(":defined"), "During construction, this should not match :defined");
log.push([this, 'end']);
}
});
assert_equals(log.length, 2);
assert_array_equals(log[0], [instance, 'begin']);
assert_array_equals(log[1], [instance, 'end']);
}, 'this.matches(:defined) should not match during an upgrade');

</script>
50 changes: 50 additions & 0 deletions custom-elements/upgrading.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
<script src="resources/custom-elements-helpers.js"></script>
</head>
<body>
<infinite-cloning-element-1></infinite-cloning-element-1>
<infinite-cloning-element-2 id="a"></infinite-cloning-element-2>
<infinite-cloning-element-2 id="b"></infinite-cloning-element-2>
<div id="log"></div>
<script>
setup({allow_uncaught_exception:true});
Expand Down Expand Up @@ -203,6 +206,53 @@
}, 'If definition\'s disable shadow is true and element\'s shadow root is ' +
'non-null, then throw a "NotSupportedError" DOMException.');

test(() => {
var log = [];

customElements.define('infinite-cloning-element-1',class extends HTMLElement {
constructor() {
super();
log.push([this, 'begin']);
// Potential infinite recursion:
customElements.upgrade(this);
log.push([this, 'end']);
}
});

assert_equals(log.length, 2);
const instance = document.querySelector("infinite-cloning-element-1");
assert_array_equals(log[0], [instance, 'begin']);
assert_array_equals(log[1], [instance, 'end']);
}, 'Infinite constructor recursion with upgrade(this) should not be possible');

test(() => {
var log = [];

customElements.define('infinite-cloning-element-2',class extends HTMLElement {
constructor() {
super();
log.push([this, 'begin']);
const b = document.querySelector("#b");
b.remove();
// While this constructor is running for "a", "b" is still
// undefined, and so inserting it into the document will enqueue a
// second upgrade reaction for "b" in addition to the one enqueued
// by defining x-foo.
document.body.appendChild(b);
log.push([this, 'end']);
}
});

assert_equals(log.length, 4);
const instanceA = document.querySelector("#a");
const instanceB = document.querySelector("#b");
assert_array_equals(log[0], [instanceA, 'begin']);
assert_array_equals(log[1], [instanceB, 'begin']);
assert_array_equals(log[2], [instanceB, 'end']);
assert_array_equals(log[3], [instanceA, 'end']);
}, 'Infinite constructor recursion with appendChild should not be possible');


</script>
</body>
</html>

0 comments on commit 4f24cab

Please sign in to comment.