Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a cache for the root of a node #3671

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 5 additions & 6 deletions lib/jsdom/living/events/EventTarget-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const DOMException = require("../generated/DOMException");

const reportException = require("../helpers/runtime-script-errors");
const idlUtils = require("../generated/utils");
const { nodeRoot } = require("../helpers/node");
const {
isNode, isShadowRoot, isSlotable, getEventTargetParent,
isShadowInclusiveAncestor, retarget
Expand Down Expand Up @@ -148,7 +147,7 @@ class EventTargetImpl {

slotable = null;

const parentRoot = nodeRoot(parent);
const parentRoot = parent.getRootNode();
if (isShadowRoot(parentRoot) && parentRoot.mode === "closed") {
slotInClosedTree = true;
}
Expand All @@ -161,7 +160,7 @@ class EventTargetImpl {
relatedTarget = retarget(eventImpl.relatedTarget, parent);

if (
(isNode(parent) && isShadowInclusiveAncestor(nodeRoot(targetImpl), parent)) ||
(isNode(parent) && isShadowInclusiveAncestor(targetImpl.getRootNode(), parent)) ||
idlUtils.wrapperForImpl(parent).constructor.name === "Window"
) {
if (isActivationEvent && eventImpl.bubbles && activationTarget === null &&
Expand Down Expand Up @@ -198,8 +197,8 @@ class EventTargetImpl {
const clearTargetsStruct = eventImpl._path[clearTargetsStructIndex];

clearTargets =
(isNode(clearTargetsStruct.target) && isShadowRoot(nodeRoot(clearTargetsStruct.target))) ||
(isNode(clearTargetsStruct.relatedTarget) && isShadowRoot(nodeRoot(clearTargetsStruct.relatedTarget)));
(isNode(clearTargetsStruct.target) && isShadowRoot(clearTargetsStruct.target.getRootNode())) ||
(isNode(clearTargetsStruct.relatedTarget) && isShadowRoot(clearTargetsStruct.relatedTarget.getRootNode()));

if (activationTarget !== null && activationTarget._legacyPreActivationBehavior) {
activationTarget._legacyPreActivationBehavior();
Expand Down Expand Up @@ -404,7 +403,7 @@ function normalizeEventHandlerOptions(options, defaultBoolKeys) {

// https://dom.spec.whatwg.org/#concept-event-path-append
function appendToEventPath(eventImpl, target, targetOverride, relatedTarget, touchTargets, slotInClosedTree) {
const itemInShadowTree = isNode(target) && isShadowRoot(nodeRoot(target));
const itemInShadowTree = isNode(target) && isShadowRoot(target.getRootNode());
const rootOfClosedTree = isShadowRoot(target) && target.mode === "closed";

eventImpl._path.push({
Expand Down
11 changes: 0 additions & 11 deletions lib/jsdom/living/helpers/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,6 @@ function nodeLength(node) {
}
}

// https://dom.spec.whatwg.org/#concept-tree-root
function nodeRoot(node) {
while (domSymbolTree.parent(node)) {
node = domSymbolTree.parent(node);
}

return node;
}

// https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
function isInclusiveAncestor(ancestorNode, node) {
while (node) {
Expand Down Expand Up @@ -61,8 +52,6 @@ function isFollowing(nodeA, nodeB) {

module.exports = {
nodeLength,
nodeRoot,

isInclusiveAncestor,
isFollowing
};
13 changes: 6 additions & 7 deletions lib/jsdom/living/helpers/shadow-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const NODE_TYPE = require("../node-type");

const { nodeRoot } = require("./node");
const { HTML_NS } = require("./namespaces");
const { domSymbolTree } = require("./internal-constants");
const { signalSlotList, queueMutationObserverMicrotask } = require("./mutation-observers");
Expand Down Expand Up @@ -79,15 +78,15 @@ function retarget(a, b) {
return a;
}

const aRoot = nodeRoot(a);
const aRoot = a.getRootNode();
if (
!isShadowRoot(aRoot) ||
(isNode(b) && isShadowInclusiveAncestor(aRoot, b))
) {
return a;
}

a = nodeRoot(a).host;
a = a.getRootNode().host;
}
}

Expand All @@ -101,7 +100,7 @@ function getEventTargetParent(eventTarget, event) {

// https://dom.spec.whatwg.org/#concept-shadow-including-root
function shadowIncludingRoot(node) {
const root = nodeRoot(node);
const root = node.getRootNode();
return isShadowRoot(root) ? shadowIncludingRoot(root.host) : root;
}

Expand Down Expand Up @@ -155,7 +154,7 @@ function assignSlotableForTree(root) {
function findSlotable(slot) {
const result = [];

const root = nodeRoot(slot);
const root = slot.getRootNode();
if (!isShadowRoot(root)) {
return result;
}
Expand All @@ -175,7 +174,7 @@ function findSlotable(slot) {
function findFlattenedSlotables(slot) {
const result = [];

const root = nodeRoot(slot);
const root = slot.getRootNode();
if (!isShadowRoot(root)) {
return result;
}
Expand All @@ -191,7 +190,7 @@ function findFlattenedSlotables(slot) {
}

for (const node of slotables) {
if (isSlot(node) && isShadowRoot(nodeRoot(node))) {
if (isSlot(node) && isShadowRoot(node.getRootNode())) {
const temporaryResult = findFlattenedSlotables(node);
result.push(...temporaryResult);
} else {
Expand Down
1 change: 1 addition & 0 deletions lib/jsdom/living/nodes/Document-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ class DocumentImpl extends NodeImpl {
}

for (const inclusiveDescendant of shadowIncludingInclusiveDescendantsIterator(node)) {
inclusiveDescendant._cachedRoot = null;
if (inclusiveDescendant._adoptingSteps) {
inclusiveDescendant._adoptingSteps(oldDocument);
}
Expand Down
3 changes: 1 addition & 2 deletions lib/jsdom/living/nodes/DocumentOrShadowRoot-impl.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use strict";
const NODE_TYPE = require("../node-type");
const { nodeRoot } = require("../helpers/node");
const { retarget } = require("../helpers/shadow-dom");

class DocumentOrShadowRootImpl {
Expand All @@ -10,7 +9,7 @@ class DocumentOrShadowRootImpl {
return null;
}
candidate = retarget(candidate, this);
if (nodeRoot(candidate) !== this) {
if (candidate.getRootNode() !== this) {
return null;
}
if (candidate.nodeType !== NODE_TYPE.DOCUMENT_NODE) {
Expand Down
3 changes: 1 addition & 2 deletions lib/jsdom/living/nodes/HTMLSlotElement-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const idlUtils = require("../generated/utils");
const HTMLElement = require("../generated/HTMLElement");
const HTMLElementImpl = require("./HTMLElement-impl").implementation;

const { nodeRoot } = require("../helpers/node");
const { assignSlotableForTree, findFlattenedSlotables } = require("../helpers/shadow-dom");

class HTMLSlotElementImpl extends HTMLElementImpl {
Expand Down Expand Up @@ -35,7 +34,7 @@ class HTMLSlotElementImpl extends HTMLElementImpl {
return;
}

assignSlotableForTree(nodeRoot(this));
assignSlotableForTree(this.getRootNode());
}
}

Expand Down
54 changes: 36 additions & 18 deletions lib/jsdom/living/nodes/Node-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const { setAnExistingAttributeValue } = require("../attributes");

const NodeList = require("../generated/NodeList");

const { nodeRoot, nodeLength, isInclusiveAncestor } = require("../helpers/node");
const { nodeLength, isInclusiveAncestor } = require("../helpers/node");
const { domSymbolTree } = require("../helpers/internal-constants");
const { documentBaseURLSerialized } = require("../helpers/document-base-url");
const { queueTreeMutationRecord } = require("../helpers/mutation-observers");
Expand Down Expand Up @@ -107,7 +107,7 @@ function isHostInclusiveAncestor(nodeImplA, nodeImplB) {
}
}

const rootImplB = nodeRoot(nodeImplB);
const rootImplB = nodeImplB.getRootNode();
if (rootImplB._host) {
return isHostInclusiveAncestor(nodeImplA, rootImplB._host);
}
Expand All @@ -126,6 +126,7 @@ class NodeImpl extends EventTargetImpl {
this._childNodesList = null;
this._childrenList = null;
this._version = 0;
this._cachedRoot = null;
this._memoizedQueries = {};
this._registeredObserverList = [];
this._referencedRanges = new Set();
Expand All @@ -144,7 +145,20 @@ class NodeImpl extends EventTargetImpl {
}

getRootNode(options) {
return options.composed ? shadowIncludingRoot(this) : nodeRoot(this);
if (options?.composed) {
return shadowIncludingRoot(this);
}

if (this._cachedRoot === null) {
let node = this;
while (domSymbolTree.parent(node)) {
node = domSymbolTree.parent(node);
}

this._cachedRoot = node;
}

return this._cachedRoot;
}

get nodeName() {
Expand Down Expand Up @@ -804,6 +818,7 @@ class NodeImpl extends EventTargetImpl {
let isConnected;

for (const node of nodesImpl) {
node._cachedRoot = null;
if (!childImpl) {
domSymbolTree.appendChild(this, node);
} else {
Expand All @@ -824,11 +839,11 @@ class NodeImpl extends EventTargetImpl {
this._childTextContentChangeSteps();
}

if (isSlot(this) && this._assignedNodes.length === 0 && isShadowRoot(nodeRoot(this))) {
if (isSlot(this) && this._assignedNodes.length === 0 && isShadowRoot(this.getRootNode())) {
signalSlotChange(this);
}

const root = nodeRoot(node);
const root = node.getRootNode();
if (isShadowRoot(root)) {
assignSlotableForTree(root);
}
Expand All @@ -843,14 +858,17 @@ class NodeImpl extends EventTargetImpl {
isConnected = node.isConnected;
}

if (isConnected) {
for (const inclusiveDescendant of shadowIncludingInclusiveDescendantsIterator(node)) {
for (const inclusiveDescendant of shadowIncludingInclusiveDescendantsIterator(node)) {
if (isConnected) {
if (inclusiveDescendant._ceState === "custom") {
enqueueCECallbackReaction(inclusiveDescendant, "connectedCallback", []);
} else {
tryUpgradeElement(inclusiveDescendant);
}
}
if (inclusiveDescendant !== node) {
inclusiveDescendant._cachedRoot = null;
}
}
}

Expand Down Expand Up @@ -1106,12 +1124,13 @@ class NodeImpl extends EventTargetImpl {
const oldNextSiblingImpl = domSymbolTree.nextSibling(nodeImpl);

domSymbolTree.remove(nodeImpl);
nodeImpl._cachedRoot = null;

if (nodeImpl._assignedSlot) {
assignSlotable(nodeImpl._assignedSlot);
}

if (isSlot(this) && this._assignedNodes.length === 0 && isShadowRoot(nodeRoot(this))) {
if (isSlot(this) && this._assignedNodes.length === 0 && isShadowRoot(this.getRootNode())) {
signalSlotChange(this);
}

Expand All @@ -1126,23 +1145,22 @@ class NodeImpl extends EventTargetImpl {
}

if (hasSlotDescendant) {
assignSlotableForTree(nodeRoot(this));
assignSlotableForTree(this.getRootNode());
assignSlotableForTree(nodeImpl);
}

this._modified();
nodeImpl._detach();
this._descendantRemoved(this, nodeImpl);

if (this.isConnected) {
if (nodeImpl._ceState === "custom") {
enqueueCECallbackReaction(nodeImpl, "disconnectedCallback", []);
}

for (const descendantImpl of shadowIncludingDescendantsIterator(nodeImpl)) {
if (descendantImpl._ceState === "custom") {
enqueueCECallbackReaction(descendantImpl, "disconnectedCallback", []);
}
const { isConnected } = this;
if (isConnected && nodeImpl._ceState === "custom") {
enqueueCECallbackReaction(nodeImpl, "disconnectedCallback", []);
}
for (const descendantImpl of shadowIncludingDescendantsIterator(nodeImpl)) {
descendantImpl._cachedRoot = null;
if (isConnected && descendantImpl._ceState === "custom") {
enqueueCECallbackReaction(descendantImpl, "disconnectedCallback", []);
}
}

Expand Down
3 changes: 1 addition & 2 deletions lib/jsdom/living/nodes/ShadowRoot-impl.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use strict";

const { nodeRoot } = require("../helpers/node");
const { mixin } = require("../../utils");

const DocumentFragment = require("./DocumentFragment-impl").implementation;
Expand All @@ -17,7 +16,7 @@ class ShadowRootImpl extends DocumentFragment {
}

_getTheParent(event) {
if (!event.composed && this === nodeRoot(event._path[0].item)) {
if (!event.composed && this === event._path[0].item.getRootNode()) {
return null;
}

Expand Down
14 changes: 7 additions & 7 deletions lib/jsdom/living/range/Range-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { parseFragment } = require("../../browser/parser/index");
const { HTML_NS } = require("../helpers/namespaces");
const { domSymbolTree } = require("../helpers/internal-constants");
const { compareBoundaryPointsPosition } = require("./boundary-point");
const { nodeRoot, nodeLength, isInclusiveAncestor } = require("../helpers/node");
const { nodeLength, isInclusiveAncestor } = require("../helpers/node");
const { createElement } = require("../helpers/create-element");

const AbstractRangeImpl = require("./AbstractRange-impl").implementation;
Expand Down Expand Up @@ -319,7 +319,7 @@ class RangeImpl extends AbstractRangeImpl {

// https://dom.spec.whatwg.org/#dom-range-ispointinrange
isPointInRange(node, offset) {
if (nodeRoot(node) !== this._root) {
if (node.getRootNode() !== this._root) {
return false;
}

Expand All @@ -339,7 +339,7 @@ class RangeImpl extends AbstractRangeImpl {

// https://dom.spec.whatwg.org/#dom-range-comparepoint
comparePoint(node, offset) {
if (nodeRoot(node) !== this._root) {
if (node.getRootNode() !== this._root) {
throw DOMException.create(this._globalObject, [
"The given Node and the Range are not in the same tree.",
"WrongDocumentError"
Expand All @@ -360,7 +360,7 @@ class RangeImpl extends AbstractRangeImpl {

// https://dom.spec.whatwg.org/#dom-range-intersectsnode
intersectsNode(node) {
if (nodeRoot(node) !== this._root) {
if (node.getRootNode() !== this._root) {
return false;
}

Expand Down Expand Up @@ -446,7 +446,7 @@ class RangeImpl extends AbstractRangeImpl {

// https://dom.spec.whatwg.org/#concept-range-root
get _root() {
return nodeRoot(this._start.node);
return this._start.node.getRootNode();
}

_setLiveRangeStart(node, offset) {
Expand Down Expand Up @@ -519,7 +519,7 @@ function setBoundaryPointStart(range, node, offset) {

const bp = { node, offset };
if (
nodeRoot(node) !== range._root ||
node.getRootNode() !== range._root ||
compareBoundaryPointsPosition(bp, range._end) === 1
) {
range._setLiveRangeEnd(node, offset);
Expand All @@ -532,7 +532,7 @@ function setBoundaryPointEnd(range, node, offset) {

const bp = { node, offset };
if (
nodeRoot(node) !== range._root ||
node.getRootNode() !== range._root ||
compareBoundaryPointsPosition(bp, range._start) === -1
) {
range._setLiveRangeStart(node, offset);
Expand Down