HTMLSlotElement's assign method should "assign slottables for a tree" for slots in other shadow roots
assign(...nodes) need to run assign slottable for a tree on slots that belong to other shadow trees.
Consider the following example. As of this writing, Chrome fires events in the expected order, and Firefox doesn't fire any slotchange events:
<!DOCTYPE html>
<html>
<body>
<div id="hostA"><div id="childA1"></div><div id="childA2"></div><div id="childA3"></div><div id="childA4"></div></div>
<div id="hostB"><div id="childB1"></div><div id="childB2"></div></div>
<div id="hostC"></div>
<script src="/resources/testharness.js"></script>
<script>
promise_test(async () => {
let logs = [];
function logger(event) { logs.push(this); }
const shadowRootB = hostB.attachShadow({mode: 'closed', slotAssignment: 'manual'});
const slotB = shadowRootB.appendChild(document.createElement('slot'));
slotB.id = 'B';
slotB.assign(childB1, childB2);
slotB.addEventListener('slotchange', logger);
const shadowRootA = hostA.attachShadow({mode: 'closed', slotAssignment: 'manual'});
const slotA1 = shadowRootA.appendChild(document.createElement('slot'));
slotA1.id = 'A1';
slotA1.assign(childA1, childA2);
slotA1.addEventListener('slotchange', logger);
const slotA2 = shadowRootA.appendChild(document.createElement('slot'));
slotA2.id = 'A2';
slotA2.assign(childA3, childA4);
slotA2.addEventListener('slotchange', logger);
await new Promise((resolve) => setTimeout(resolve, 0));
assert_array_equals(logs, [slotB, slotA1, slotA2]);
logs = [];
const slotC = document.createElement('slot');
slotC.assign(childB1, childA3, childA2);
await new Promise((resolve) => setTimeout(resolve, 0));
assert_array_equals(logs, [slotB, slotA2, slotA1]);
});
</script>
</body>
</html>
Spec'ing Chrome's behavior is probably most sensible in this scenario. slotchange most definitely need to fire on slotB, slotA2, slotA1 in this example since the slot's assigned nodes are mutated by the virtue of those nodes being manually assigned to another slot whether they belong to the same shadow tree or not.
Since determining the order of each slot across different shadow trees is going to be rather expensive, we don't want to schedule slotchange events in the tree order either.
On the other hand, it's rather odd that slotchange event will be fired in the tree oder if other slots belong to the same tree but they're enqueued in the order they were affected if they belong to different shadow trees. So I suggest we change that to always in the order they're affected. This will avoid unnecessary tree walking to sort slots.
Filed https://bugzilla.mozilla.org/show_bug.cgi?id=1784204 for Mozilla
In this example, Chrome schedules slotchange event on slotB as well as slotA. I don't think that's expected given slotB's assigned nodes have changed. So both Chrome and Firefox seem to have some odd behaviors around this.
let logs = [];
function logger(event) { logs.push(this); }
container.innerHTML = `<div id="hostA"><div id="childA1"></div><div id="childA2"></div></div><div id="hostB"></div>`;
const shadowRootA = hostA.attachShadow({mode: 'closed', slotAssignment: 'manual'});
const slotA = shadowRootA.appendChild(document.createElement('slot'));
slotA.id = 'A';
slotA.assign(childA1, childA2);
slotA.addEventListener('slotchange', logger);
await new Promise((resolve) => setTimeout(resolve, 0));
assert_array_equals(logs, [slotA]);
logs = [];
const shadowRootB = hostB.attachShadow({mode: 'closed', slotAssignment: 'manual'});
const slotB = shadowRootB.appendChild(document.createElement('slot'));
slotB.id = 'B';
slotB.assign(childA1);
slotB.addEventListener('slotchange', logger);
await new Promise((resolve) => setTimeout(resolve, 0));
assert_array_equals(logs, [slotA]);
Why the latter example is surprising? Assuming you get slotchange event with manual slot assignment at all, don't you want to get it for all the cases.
And I'd assume then also slotC in the first example to get slotchange.
Why the latter example is surprising? Assuming you get slotchange event with manual slot assignment at all, don't you want to get it for all the cases.
It's surprising because slot.assignedNodes() don't change in those cases. With named slot API, we only get a slotchange event when the contents of slot.assignedNodes() change.
That sounds reasonable too.