Skip to content

Commit

Permalink
Implement imperative slotting API changes
Browse files Browse the repository at this point in the history
The original implementation of the imperative slot distribution
API was done before the final spec PRs landed. In the process of
landing those PRs, several changes were made to the way the API
works. Primarily, there are two changes:

 1. The "auto" slotAssignment mode was renamed to "named".
 2. The "linkage" that is created by HTMLSlotElement.assign() was
    made more permanent. Previously, moving either the <slot> or
    the assigned node around in the tree (or across documents)
    would "break" the linkage. Now, the linkage is more permanent,
    and the only way to break it is through another call to .assign().

See [1] for the chromestatus entry, [2] for the intent to ship,
[3], [4], and [5] for the spec PRs, and [6]/[7] for the landed spec.

[1] https://chromestatus.com/feature/4979822998585344
[2] https://groups.google.com/a/chromium.org/g/blink-dev/c/6U78F3KWJ78
[3] whatwg/html#6561
[4] whatwg/html#6585
[5] whatwg/dom#966
[6] https://dom.spec.whatwg.org/#find-slotables
[7] https://html.spec.whatwg.org/#dom-slot-assign

Fixed: 1196842
Fixed: 1067153
Bug: 1067157
Change-Id: I0ee71043c23f3b49a1461296d722045f06eca540
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2824763
Reviewed-by: Joey Arhar <jarhar@chromium.org>
Commit-Queue: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/master@{#874413}
  • Loading branch information
mfreed7 authored and chromium-wpt-export-bot committed Apr 20, 2021
1 parent 37ffcaa commit db1be04
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
let tTree = setupShadowDOM(test_slotchange, test, data);
let [s1Promise, s2Promise] = monitorSlots(data);

assert_throws_dom('NotAllowedError', () => { tTree.s1.assign([tTree.c4]); });
tTree.s1.assign([]);;
tTree.s2.assign([]);
tTree.host.insertBefore(tTree.c4, tTree.c1);

Expand Down Expand Up @@ -135,7 +135,7 @@

[s1Promise] = monitorSlots(data);
tTree.s1.assign([tTree.c1, tTree.c2]);
tTree.s1.assign([tTree.c2, tTree.c1, tTree.c1, tTree.c2, tTree.c2]);
tTree.s1.assign([tTree.c1, tTree.c2, tTree.c1, tTree.c2, tTree.c2]);

s1Promise.then(test.step_func_done(() => {
assert_equals(data.s1EventCount, 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,47 +15,14 @@
let tTree = createTestTree(test_basic);
assert_not_equals(tTree.host1.attachShadow({ mode: 'open', slotAssignment: 'manual'}),
null, 'slot assignment manual should work');
assert_not_equals(tTree.host2.attachShadow({ mode: 'open', slotAssignment: 'name'}),
assert_not_equals(tTree.host2.attachShadow({ mode: 'open', slotAssignment: 'named'}),
null, 'slot assignment auto should work');
assert_throws_js(TypeError, () => {
tTree.host3.attachShadow({ mode: 'open', slotAssignment: 'exceptional' })},
'others should throw exception');
}, 'attachShadow can take slotAssignment parameter.');
</script>

<div id="test_errors">
<div id="host1">
<template data-mode="open" data-slot-assignment="name">
<slot id="s1"></slot>
</template>
<div id="c1"></div>
</div>
<div id="host2">
<template data-mode="open" data-slot-assignment="manual">
<slot id="s2"></slot>
</template>
</div>
<div id="c2"></div>
</div>
<script>
test(() => {
let tTree = createTestTree(test_errors);
assert_array_equals(tTree.s1.assignedElements(), [tTree.c1]);
assert_equals(tTree.c1.assignedSlot, tTree.s1);

assert_throws_dom('NotAllowedError', () => { tTree.s1.assign([]); });
assert_array_equals(tTree.s1.assignedElements(), [tTree.c1]);
assert_equals(tTree.c1.assignedSlot, tTree.s1);
}, 'Imperative slot API throws exception when not slotAssignment != \'manual\'.');

test(() => {
let tTree = createTestTree(test_errors);
assert_throws_dom('NotAllowedError', () => { tTree.s2.assign([tTree.c2]); });

assert_throws_dom('NotAllowedError', () => { tTree.s2.assign([tTree.host1]); });
}, 'Imperative slot API throws exception when slottable parentNode != slot\'s host.');
</script>

<div id="test_assign">
<div id="host">
<template id="shadow_root" data-mode="open" data-slot-assignment="manual">
Expand Down Expand Up @@ -155,34 +122,31 @@
let tTree = createTestTree(test_assign);

// tTree.c4 is invalid for tTree.host slot assignment.
try {
tTree.s1.assign([tTree.c1, tTree.c2, tTree.c4]);
assert_unreached('assign() should have failed) ');
} catch (err) {
assert_equals(err.name, 'NotAllowedError');
}
// No exception should be thrown here.
tTree.s1.assign([tTree.c1, tTree.c4, tTree.c2]);

assert_array_equals(tTree.s1.assignedNodes(), []);
assert_equals(tTree.c1.assignedSlot, null);
assert_equals(tTree.c2.assignedSlot, null);
// All observable assignments should skip c4.
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]);
assert_equals(tTree.c1.assignedSlot, tTree.s1);
assert_equals(tTree.c2.assignedSlot, tTree.s1);
assert_equals(tTree.c4.assignedSlot, null);

tTree.s1.assign([tTree.c2, tTree.c3, tTree.c1]);
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c2, tTree.c3, tTree.c1]);

try {
tTree.s1.assign([tTree.c4]);
assert_unreached('assign() should have failed) ');
} catch (err) {
assert_equals(err.name, 'NotAllowedError');
}

// Previous state is preserved.
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c2, tTree.c3, tTree.c1]);
// Moving c4 into place should reveal the assignment.
tTree.host.append(tTree.c4);
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c4, tTree.c2]);
assert_equals(tTree.c1.assignedSlot, tTree.s1);
assert_equals(tTree.c2.assignedSlot, tTree.s1);
assert_equals(tTree.c3.assignedSlot, tTree.s1);
}, 'Assigning invalid nodes causes exception and slot returns to its previous state.');
assert_equals(tTree.c4.assignedSlot, tTree.s1);

// Moving c4 into a different shadow host and back should
// also not break the assignment.
tTree.host4.append(tTree.c4)
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]);
assert_equals(tTree.c4.assignedSlot, null);
tTree.host.append(tTree.c4);
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c4, tTree.c2]);
assert_equals(tTree.c4.assignedSlot, tTree.s1);
}, 'Assigning invalid nodes should be allowed.');

test(() => {
let tTree = createTestTree(test_assign);
Expand Down Expand Up @@ -227,21 +191,6 @@
assert_equals(tTree.c1.assignedSlot, tTree.s4);
}, 'Appending slottable to different host, it loses slot assignment. It can be re-assigned within a new host.');

test(() => {
let tTree = createTestTree(test_assign);

tTree.s1.assign([tTree.c1]);
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]);

tTree.shadow_root4.insertBefore(tTree.s1, tTree.s4);
assert_array_equals(tTree.s1.assignedNodes(), []);
// Don't trigger slot assignment on previous shadow root.
// assert_array_equals(tTree.s2.assignedNodes(), []);

tTree.shadow_root.insertBefore(tTree.s1, tTree.s2);
assert_array_equals(tTree.s1.assignedNodes(), []);
}, 'Previously assigned node should not be assigned if slot moved to a new shadow root. The slot remains empty when moved back, no trigger recalc.');

test(() => {
let tTree = createTestTree(test_assign);

Expand All @@ -264,8 +213,8 @@
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]);

tTree.s1.assign([tTree.c1, tTree.c1, tTree.c2, tTree.c2, tTree.c1]);
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c2, tTree.c1]);
}, 'Assignment with the same node in parameters should be ignored, last one wins.');
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]);
}, 'Assignment with the same node in parameters should be ignored, first one wins.');

test(() => {
let tTree = createTestTree(test_assign);
Expand All @@ -281,25 +230,45 @@
test(() => {
let tTree = createTestTree(test_assign);

tTree.s1.assign([tTree.c1]);
tTree.s2.assign([tTree.c2]);
tTree.s3.assign([tTree.c3]);
tTree.shadow_root.insertBefore(tTree.s2, tTree.s1);
tTree.shadow_root.insertBefore(tTree.s3, tTree.s1);
const isolatedDocNode = document.implementation.createHTMLDocument("").body;
isolatedDocNode.appendChild(tTree.c1);
const isolatedDocNode2 = document.implementation.createHTMLDocument("").body;
isolatedDocNode2.appendChild(tTree.s1);

assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]);
assert_array_equals(tTree.s2.assignedNodes(), []);
assert_array_equals(tTree.s3.assignedNodes(), []);
assert_equals(tTree.c1.assignedSlot, tTree.s1);
tTree.s1.assign([tTree.c1, tTree.c2]);
assert_array_equals(tTree.s1.assignedNodes(), [], 's1 not inside shadow root');
assert_equals(tTree.c1.assignedSlot, null);
assert_equals(tTree.c2.assignedSlot, null);
assert_equals(tTree.c3.assignedSlot, null);

tTree.s2.remove();
tTree.shadow_root.appendChild(tTree.s1);
tTree.host.appendChild(tTree.c1);
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]);
assert_equals(tTree.c1.assignedSlot, tTree.s1);
assert_equals(tTree.c2.assignedSlot, tTree.s1);
}, 'Nodes can be assigned even if slots or nodes aren\'t in the same tree.');

assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]);
assert_array_equals(tTree.s3.assignedNodes(), []);
test(() => {
let tTree = createTestTree(test_assign);

tTree.s1.assign([tTree.c1, tTree.c2]);
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]);
assert_equals(tTree.c1.assignedSlot, tTree.s1);
assert_equals(tTree.c2.assignedSlot, tTree.s1);

const isolatedDocNode = document.implementation.createHTMLDocument("").body;
isolatedDocNode.appendChild(tTree.c1);
const isolatedDocNode2 = document.implementation.createHTMLDocument("").body;
isolatedDocNode2.appendChild(tTree.s1);

assert_array_equals(tTree.s1.assignedNodes(), [], 's1 not inside shadow root');
assert_equals(tTree.c1.assignedSlot, null);
assert_equals(tTree.c2.assignedSlot, null);
assert_equals(tTree.c3.assignedSlot, null);
}, 'A slot should be cleared of assigned nodes even if it\'s re-inserted into the same shadow root.');

tTree.shadow_root.appendChild(tTree.s1);
tTree.host.appendChild(tTree.c1);
assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]);
assert_equals(tTree.c1.assignedSlot, tTree.s1);
assert_equals(tTree.c2.assignedSlot, tTree.s1);
}, 'Removing a node from the document does not break manually assigned slot linkage.');

</script>

0 comments on commit db1be04

Please sign in to comment.