Complete ShadowRoot child mutation semantics
This commit is contained in:
1 parent
2117fa0b8c
commit
a4a3ea49ba
2 files changed
+105
-13
No files matched your search
Vendored
+50
-11
@@ -9004,6 +9004,17 @@ Element.prototype.attachShadow = function attachShadow(opts) {
|
||||
}
|
||||
const host = this;
|
||||
const children = [];
|
||||
const assertInsertable = (node, operation) => {
|
||||
const createsComposedCycle = node instanceof ShadowRoot
|
||||
|| node === host
|
||||
|| !!(node?.contains && node.contains(host));
|
||||
if (createsComposedCycle) {
|
||||
throw new DOMException(
|
||||
`Failed to execute '${operation}' on 'Node': The new child would contain the parent.`,
|
||||
"HierarchyRequestError"
|
||||
);
|
||||
}
|
||||
};
|
||||
const shadow = {
|
||||
mode: opts.mode,
|
||||
host: host,
|
||||
@@ -9022,44 +9033,72 @@ Element.prototype.attachShadow = function attachShadow(opts) {
|
||||
get firstElementChild() { return children.find(c => c.nodeType === 1) || null; },
|
||||
get children() { return children.filter(c => c.nodeType === 1); },
|
||||
appendChild(c) {
|
||||
if (c) {
|
||||
if (!c) return c;
|
||||
assertInsertable(c, "appendChild");
|
||||
if (c instanceof DocumentFragment) {
|
||||
for (const child of Array.from(c.childNodes)) shadow.appendChild(child);
|
||||
return c;
|
||||
}
|
||||
if (c._shadowParent) c._shadowParent.removeChild(c);
|
||||
else if (c.parentNode) c.parentNode.removeChild(c);
|
||||
children.push(c);
|
||||
c._shadowParent = shadow;
|
||||
}
|
||||
return c;
|
||||
},
|
||||
insertBefore(n, ref) {
|
||||
if (!n) return n;
|
||||
if (!ref) { shadow.appendChild(n); return n; }
|
||||
const idx = children.indexOf(ref);
|
||||
if (idx >= 0) {
|
||||
if (!children.includes(ref)) {
|
||||
throw new DOMException(
|
||||
"Failed to execute 'insertBefore' on 'Node': The reference node is not a child of this node.",
|
||||
"NotFoundError"
|
||||
);
|
||||
}
|
||||
if (n === ref) return n;
|
||||
assertInsertable(n, "insertBefore");
|
||||
if (n instanceof DocumentFragment) {
|
||||
for (const child of Array.from(n.childNodes)) shadow.insertBefore(child, ref);
|
||||
return n;
|
||||
}
|
||||
if (n._shadowParent) n._shadowParent.removeChild(n);
|
||||
else if (n.parentNode) n.parentNode.removeChild(n);
|
||||
const idx = children.indexOf(ref);
|
||||
children.splice(idx, 0, n);
|
||||
n._shadowParent = shadow;
|
||||
}
|
||||
else shadow.appendChild(n);
|
||||
return n;
|
||||
},
|
||||
removeChild(c) {
|
||||
const idx = children.indexOf(c);
|
||||
if (idx >= 0) {
|
||||
if (idx < 0) {
|
||||
throw new DOMException(
|
||||
"Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.",
|
||||
"NotFoundError"
|
||||
);
|
||||
}
|
||||
children.splice(idx, 1);
|
||||
if (c._shadowParent === shadow) c._shadowParent = null;
|
||||
}
|
||||
return c;
|
||||
},
|
||||
replaceChild(n, o) {
|
||||
const idx = children.indexOf(o);
|
||||
if (idx >= 0) {
|
||||
if (!children.includes(o)) {
|
||||
throw new DOMException(
|
||||
"Failed to execute 'replaceChild' on 'Node': The node to be replaced is not a child of this node.",
|
||||
"NotFoundError"
|
||||
);
|
||||
}
|
||||
if (n === o) return o;
|
||||
assertInsertable(n, "replaceChild");
|
||||
if (n instanceof DocumentFragment) {
|
||||
for (const child of Array.from(n.childNodes)) shadow.insertBefore(child, o);
|
||||
shadow.removeChild(o);
|
||||
return o;
|
||||
}
|
||||
if (n._shadowParent) n._shadowParent.removeChild(n);
|
||||
else if (n.parentNode) n.parentNode.removeChild(n);
|
||||
const idx = children.indexOf(o);
|
||||
children[idx] = n;
|
||||
n._shadowParent = shadow;
|
||||
if (o._shadowParent === shadow) o._shadowParent = null;
|
||||
}
|
||||
return o;
|
||||
},
|
||||
querySelector(s) {
|
||||
|
||||
@@ -1993,7 +1993,50 @@ mod tests {
|
||||
inside.getRootNode() === root,
|
||||
root.textContent
|
||||
];
|
||||
return [inserted, removed, moved, parsed];
|
||||
|
||||
const a = document.createElement("a");
|
||||
const b = document.createElement("b");
|
||||
const c = document.createElement("i");
|
||||
root.replaceChildren(a, b, c);
|
||||
root.insertBefore(a, c);
|
||||
const movedWithin = Array.from(root.children, el => el.localName);
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
const x = document.createElement("x-one");
|
||||
const y = document.createElement("x-two");
|
||||
fragment.append(x, y);
|
||||
root.insertBefore(fragment, c);
|
||||
const flattened = [
|
||||
Array.from(root.children, el => el.localName),
|
||||
fragment.childNodes.length,
|
||||
x.parentNode === root,
|
||||
y.parentNode === root
|
||||
];
|
||||
|
||||
root.replaceChild(b, c);
|
||||
const replaced = [
|
||||
Array.from(root.children, el => el.localName),
|
||||
c.parentNode === null,
|
||||
b.parentNode === root
|
||||
];
|
||||
|
||||
const detached = document.createElement("detached-node");
|
||||
const errors = [];
|
||||
for (const operation of [
|
||||
() => root.insertBefore(detached, c),
|
||||
() => root.removeChild(c),
|
||||
() => root.replaceChild(detached, c),
|
||||
() => root.appendChild(root),
|
||||
() => root.appendChild(host)
|
||||
]) {
|
||||
try {
|
||||
operation();
|
||||
errors.push("none");
|
||||
} catch (error) {
|
||||
errors.push(error.name);
|
||||
}
|
||||
}
|
||||
return [inserted, removed, moved, parsed, movedWithin, flattened, replaced, errors];
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -2016,7 +2059,17 @@ mod tests {
|
||||
],
|
||||
[true, true, true],
|
||||
[true, true, true],
|
||||
[true, true, "inside"]
|
||||
[true, true, "inside"],
|
||||
["b", "a", "i"],
|
||||
[["b", "a", "x-one", "x-two", "i"], 0, true, true],
|
||||
[["a", "x-one", "x-two", "b"], true, true],
|
||||
[
|
||||
"NotFoundError",
|
||||
"NotFoundError",
|
||||
"NotFoundError",
|
||||
"HierarchyRequestError",
|
||||
"HierarchyRequestError"
|
||||
]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user