html icon indicating copy to clipboard operation
html copied to clipboard

HTMLSlotElement's assign method should "assign slottables for a tree" for slots in other shadow roots

Open rniwa opened this issue 3 years ago • 7 comments

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>

rniwa avatar Aug 11 '22 02:08 rniwa

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.

rniwa avatar Aug 11 '22 03:08 rniwa

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.

rniwa avatar Aug 11 '22 03:08 rniwa

Filed https://bugzilla.mozilla.org/show_bug.cgi?id=1784204 for Mozilla

rniwa avatar Aug 11 '22 04:08 rniwa

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]);

rniwa avatar Aug 11 '22 08:08 rniwa

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.

smaug---- avatar Aug 11 '22 09:08 smaug----

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.

rniwa avatar Aug 11 '22 09:08 rniwa

That sounds reasonable too.

smaug---- avatar Aug 11 '22 13:08 smaug----