Skip to content

Commit

Permalink
[scoped-registry] Implement scoped custom element upgrade
Browse files Browse the repository at this point in the history
This patch:

1. Changes CustomElement::Registry() to return tree-scoped registries
   instead of the global registry. As this function is used by a lot
   of callers (including upgrade), this allows these callers to use
   the scoped registry associated with the tree scope.

2. When calling a custom element constructor, it temporarily tags the
   constructor with the current registry being used, so that later when
   calling `super()`, we can still know which custom element definition
   we are using. This part follows the discussion in
   WICG/webcomponents#969

Bug: 1304439
Change-Id: Id510ecea0f4c5cf6386f77a39d346918c9592e76
  • Loading branch information
xiaochengh authored and chromium-wpt-export-bot committed Jan 13, 2023
1 parent 440d4d9 commit 3314052
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<!DOCTYPE html>
<meta name="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
<meta name="assert" content="Custom element constructors can re-enter with different definitions">
<link rel="help" href="https://wicg.github.io/webcomponents/proposals/Scoped-Custom-Element-Registries">
<link rel="help" href="https://github.com/WICG/webcomponents/issues/969">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<div id="testdiv"></div>

<script>
class TestAutonomous extends HTMLElement {};
class TestCustomizedBuiltIn extends HTMLParagraphElement {};

function attachShadowForTest(t, registry) {
const host = document.createElement('div');
const shadow = host.attachShadow({mode: 'open', registry});
document.body.appendChild(host);
t.add_cleanup(() => host.remove());
return shadow;
}

test(t => {
const registry = new CustomElementRegistry;
registry.define('test-element', TestAutonomous);

const shadow = attachShadowForTest(t, registry);
shadow.innerHTML = '<test-element></test-element>';
assert_true(shadow.firstChild instanceof TestAutonomous, 'target tree scope');

// Verify that it doesn't pollute other tree scopes.
const shadow2 = attachShadowForTest(t);
shadow2.innerHTML = '<test-element></test-element>';
assert_false(shadow2.firstChild instanceof TestAutonomous, 'tree scope without registry');

const shadow3 = attachShadowForTest(t, new CustomElementRegistry);
shadow3.innerHTML = '<test-element></test-element>';
assert_false(shadow3.firstChild instanceof TestAutonomous, 'tree scope with different registry');

t.add_cleanup(() => testdiv.firstChild.remove());
testdiv.innerHTML = '<test-element></test-element>';
assert_false(testdiv.firstChild instanceof TestAutonomous, 'main document');
}, 'Upgrade into autonomous custom element when inserted via innerHTML');

test(t => {
const registry = new CustomElementRegistry;
const shadow = attachShadowForTest(t, registry);
shadow.innerHTML = '<test-element></test-element>';

const shadow2 = attachShadowForTest(t);
shadow2.innerHTML = '<test-element></test-element>';

const shadow3 = attachShadowForTest(t, new CustomElementRegistry);
shadow3.innerHTML = '<test-element></test-element>';

t.add_cleanup(() => testdiv.firstChild.remove());
testdiv.innerHTML = '<test-element></test-element>';

registry.define('test-element', TestAutonomous);

// Elements in the target tree scope should be upgraded.
assert_true(shadow.firstChild instanceof TestAutonomous, 'target tree scope');

// Verify that it doesn't pollute other tree scopes.
assert_false(shadow2.firstChild instanceof TestAutonomous, 'tree scope without registry');
assert_false(shadow3.firstChild instanceof TestAutonomous, 'tree scope with different registry');
assert_false(testdiv.firstChild instanceof TestAutonomous, 'main document');
}, 'Upgrade into autonomous custom element when definition is added');

test(t => {
const registry = new CustomElementRegistry;
registry.define('test-element', TestCustomizedBuiltIn, {extends: 'p'});

const shadow = attachShadowForTest(t, registry);
shadow.innerHTML = '<p is="test-element"></p>';
assert_true(shadow.firstChild instanceof TestCustomizedBuiltIn, 'target tree scope');

// Verify that it doesn't pollute other tree scopes.
const shadow2 = attachShadowForTest(t);
shadow2.innerHTML = '<p is="test-element"></p>';
assert_false(shadow2.firstChild instanceof TestCustomizedBuiltIn, 'tree scope without registry');

const shadow3 = attachShadowForTest(t, new CustomElementRegistry);
shadow3.innerHTML = '<p is="test-element"></p>';
assert_false(shadow3.firstChild instanceof TestCustomizedBuiltIn, 'tree scope with different registry');

t.add_cleanup(() => testdiv.firstChild.remove());
testdiv.innerHTML = '<p is="test-element"></p>';
assert_false(testdiv.firstChild instanceof TestCustomizedBuiltIn, 'main document');
}, 'Upgrade into customized built-in element when inserted via innerHTML');

test(t => {
const registry = new CustomElementRegistry;
const shadow = attachShadowForTest(t, registry);
shadow.innerHTML = '<p is="test-element"></p>';

const shadow2 = attachShadowForTest(t);
shadow2.innerHTML = '<p is="test-element"></p>';

const shadow3 = attachShadowForTest(t, new CustomElementRegistry);
shadow3.innerHTML = '<p is="test-element"></p>';

t.add_cleanup(() => testdiv.firstChild.remove());
testdiv.innerHTML = '<p is="test-element"></p>';

registry.define('test-element', TestCustomizedBuiltIn, {extends: 'p'});

// Elements in the target tree scope should be upgraded.
assert_true(shadow.firstChild instanceof TestCustomizedBuiltIn, 'target tree scope');

// Verify that it doesn't pollute other tree scopes.
assert_false(shadow2.firstChild instanceof TestCustomizedBuiltIn, 'tree scope without registry');
assert_false(shadow3.firstChild instanceof TestCustomizedBuiltIn, 'tree scope with different registry');
assert_false(testdiv.firstChild instanceof TestCustomizedBuiltIn, 'main document');
}, 'Upgrade into customized built-in element when definition is added');
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<meta name="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
<meta name="assert" content="Custom element constructors can re-enter with different definitions">
<link rel="help" href="https://wicg.github.io/webcomponents/proposals/Scoped-Custom-Element-Registries">
<link rel="help" href="https://github.com/WICG/webcomponents/issues/969">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<div id='test-container-1'></div>
<div id='test-container-2'></div>

<script>
function createShadowForTest(t, registry) {
const host = document.createElement('div');
const shadow = host.attachShadow({mode: 'open', registry});
document.body.appendChild(host);
t.add_cleanup(() => host.remove());
return shadow;
}

test(t => {
let needsTest = true;
class ReentryBeforeSuper extends HTMLElement {
constructor() {
if (needsTest) {
needsTest = false;
document.getElementById('test-container-1').innerHTML = '<test-element-1></test-element-1>';
}
super();
}
};
window.customElements.define('test-element-1', ReentryBeforeSuper);

let registry = new CustomElementRegistry;
registry.define('shadow-test-element-1', ReentryBeforeSuper);

let shadow = createShadowForTest(t, registry);
shadow.innerHTML = '<shadow-test-element-1></shadow-test-element-1>';

let shadowElement = shadow.firstChild;
assert_true(shadow.firstChild instanceof ReentryBeforeSuper);
assert_equals(shadow.firstChild.localName, 'shadow-test-element-1');

let mainDocElement = document.getElementById('test-container-1').firstChild;
assert_true(mainDocElement instanceof ReentryBeforeSuper);
assert_equals(mainDocElement.localName, 'test-element-1');
}, 'Constructor re-entry before calling super()');

test(t => {
let needsTest = true;
class ReentryAfterSuper extends HTMLElement {
constructor() {
super();
if (needsTest) {
needsTest = false;
document.getElementById('test-container-2').innerHTML = '<test-element-2></test-element-2>';
}
}
};
window.customElements.define('test-element-2', ReentryAfterSuper);

let registry = new CustomElementRegistry;
registry.define('shadow-test-element-2', ReentryAfterSuper);

let shadow = createShadowForTest(t, registry);
shadow.innerHTML = '<shadow-test-element-2></shadow-test-element-2>';

let shadowElement = shadow.firstChild;
assert_true(shadow.firstChild instanceof ReentryAfterSuper);
assert_equals(shadow.firstChild.localName, 'shadow-test-element-2');

let mainDocElement = document.getElementById('test-container-2').firstChild;
assert_true(mainDocElement instanceof ReentryAfterSuper);
assert_equals(mainDocElement.localName, 'test-element-2');
}, 'Constructor re-entry after calling super()');
</script>

0 comments on commit 3314052

Please sign in to comment.