mutation-summary
Version:
Makes observing the DOM fast and easy
415 lines • 18.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MutationProjection = void 0;
var TreeChanges_1 = require("./TreeChanges");
var NodeMap_1 = require("./NodeMap");
var Movement_1 = require("./Movement");
var ChildListChange_1 = require("./ChildListChange");
var MutationProjection = /** @class */ (function () {
// TOOD(any)
function MutationProjection(rootNode, mutations, selectors, calcReordered, calcOldPreviousSibling) {
this.rootNode = rootNode;
this.mutations = mutations;
this.selectors = selectors;
this.calcReordered = calcReordered;
this.calcOldPreviousSibling = calcOldPreviousSibling;
this.treeChanges = new TreeChanges_1.TreeChanges(rootNode, mutations);
this.entered = [];
this.exited = [];
this.stayedIn = new NodeMap_1.NodeMap();
this.visited = new NodeMap_1.NodeMap();
this.childListChangeMap = undefined;
this.characterDataOnly = undefined;
this.matchCache = undefined;
this.processMutations();
}
MutationProjection.prototype.processMutations = function () {
if (!this.treeChanges.anyParentsChanged &&
!this.treeChanges.anyAttributesChanged)
return;
var changedNodes = this.treeChanges.keys();
for (var i = 0; i < changedNodes.length; i++) {
this.visitNode(changedNodes[i], undefined);
}
};
MutationProjection.prototype.visitNode = function (node, parentReachable) {
if (this.visited.has(node))
return;
this.visited.set(node, true);
var change = this.treeChanges.get(node);
var reachable = parentReachable;
// node inherits its parent's reachability change unless
// its parentNode was mutated.
if ((change && change.childList) || reachable == undefined)
reachable = this.treeChanges.reachabilityChange(node);
if (reachable === Movement_1.Movement.STAYED_OUT)
return;
// Cache match results for sub-patterns.
this.matchabilityChange(node);
if (reachable === Movement_1.Movement.ENTERED) {
this.entered.push(node);
}
else if (reachable === Movement_1.Movement.EXITED) {
this.exited.push(node);
this.ensureHasOldPreviousSiblingIfNeeded(node);
}
else if (reachable === Movement_1.Movement.STAYED_IN) {
var movement = Movement_1.Movement.STAYED_IN;
if (change && change.childList) {
if (change.oldParentNode !== node.parentNode) {
movement = Movement_1.Movement.REPARENTED;
this.ensureHasOldPreviousSiblingIfNeeded(node);
}
else if (this.calcReordered && this.wasReordered(node)) {
movement = Movement_1.Movement.REORDERED;
}
}
this.stayedIn.set(node, movement);
}
if (reachable === Movement_1.Movement.STAYED_IN)
return;
// reachable === ENTERED || reachable === EXITED.
for (var child = node.firstChild; child; child = child.nextSibling) {
this.visitNode(child, reachable);
}
};
MutationProjection.prototype.ensureHasOldPreviousSiblingIfNeeded = function (node) {
if (!this.calcOldPreviousSibling)
return;
this.processChildlistChanges();
var parentNode = node.parentNode;
var nodeChange = this.treeChanges.get(node);
if (nodeChange && nodeChange.oldParentNode)
parentNode = nodeChange.oldParentNode;
var change = this.childListChangeMap.get(parentNode);
if (!change) {
change = new ChildListChange_1.ChildListChange();
this.childListChangeMap.set(parentNode, change);
}
if (!change.oldPrevious.has(node)) {
change.oldPrevious.set(node, node.previousSibling);
}
};
MutationProjection.prototype.getChanged = function (summary, selectors, characterDataOnly) {
this.selectors = selectors;
this.characterDataOnly = characterDataOnly;
for (var i = 0; i < this.entered.length; i++) {
var node = this.entered[i];
var matchable = this.matchabilityChange(node);
if (matchable === Movement_1.Movement.ENTERED || matchable === Movement_1.Movement.STAYED_IN)
summary.added.push(node);
}
var stayedInNodes = this.stayedIn.keys();
for (var i = 0; i < stayedInNodes.length; i++) {
var node = stayedInNodes[i];
var matchable = this.matchabilityChange(node);
if (matchable === Movement_1.Movement.ENTERED) {
summary.added.push(node);
}
else if (matchable === Movement_1.Movement.EXITED) {
summary.removed.push(node);
}
else if (matchable === Movement_1.Movement.STAYED_IN && (summary.reparented || summary.reordered)) {
var movement = this.stayedIn.get(node);
if (summary.reparented && movement === Movement_1.Movement.REPARENTED)
summary.reparented.push(node);
else if (summary.reordered && movement === Movement_1.Movement.REORDERED)
summary.reordered.push(node);
}
}
for (var i = 0; i < this.exited.length; i++) {
var node = this.exited[i];
var matchable = this.matchabilityChange(node);
if (matchable === Movement_1.Movement.EXITED || matchable === Movement_1.Movement.STAYED_IN)
summary.removed.push(node);
}
};
MutationProjection.prototype.getOldParentNode = function (node) {
var change = this.treeChanges.get(node);
if (change && change.childList)
return change.oldParentNode ? change.oldParentNode : null;
var reachabilityChange = this.treeChanges.reachabilityChange(node);
if (reachabilityChange === Movement_1.Movement.STAYED_OUT || reachabilityChange === Movement_1.Movement.ENTERED)
throw Error('getOldParentNode requested on invalid node.');
return node.parentNode;
};
MutationProjection.prototype.getOldPreviousSibling = function (node) {
var parentNode = node.parentNode;
var nodeChange = this.treeChanges.get(node);
if (nodeChange && nodeChange.oldParentNode)
parentNode = nodeChange.oldParentNode;
var change = this.childListChangeMap.get(parentNode);
if (!change)
throw Error('getOldPreviousSibling requested on invalid node.');
return change.oldPrevious.get(node);
};
MutationProjection.prototype.getOldAttribute = function (element, attrName) {
var change = this.treeChanges.get(element);
if (!change || !change.attributes)
throw Error('getOldAttribute requested on invalid node.');
var value = change.getAttributeOldValue(attrName);
if (value === undefined)
throw Error('getOldAttribute requested for unchanged attribute name.');
return value;
};
MutationProjection.prototype.attributeChangedNodes = function (includeAttributes) {
if (!this.treeChanges.anyAttributesChanged)
return {}; // No attributes mutations occurred.
var attributeFilter;
var caseInsensitiveFilter;
if (includeAttributes) {
attributeFilter = {};
caseInsensitiveFilter = {};
for (var i = 0; i < includeAttributes.length; i++) {
var attrName = includeAttributes[i];
attributeFilter[attrName] = true;
caseInsensitiveFilter[attrName.toLowerCase()] = attrName;
}
}
var result = {};
var nodes = this.treeChanges.keys();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var change = this.treeChanges.get(node);
if (!change.attributes)
continue;
if (Movement_1.Movement.STAYED_IN !== this.treeChanges.reachabilityChange(node) ||
Movement_1.Movement.STAYED_IN !== this.matchabilityChange(node)) {
continue;
}
var element = node;
var changedAttrNames = change.getAttributeNamesMutated();
for (var j = 0; j < changedAttrNames.length; j++) {
var attrName = changedAttrNames[j];
if (attributeFilter &&
!attributeFilter[attrName] &&
!(change.isCaseInsensitive && caseInsensitiveFilter[attrName])) {
continue;
}
var oldValue = change.getAttributeOldValue(attrName);
if (oldValue === element.getAttribute(attrName))
continue;
if (caseInsensitiveFilter && change.isCaseInsensitive)
attrName = caseInsensitiveFilter[attrName];
result[attrName] = result[attrName] || [];
result[attrName].push(element);
}
}
return result;
};
MutationProjection.prototype.getOldCharacterData = function (node) {
var change = this.treeChanges.get(node);
if (!change || !change.characterData)
throw Error('getOldCharacterData requested on invalid node.');
return change.characterDataOldValue;
};
MutationProjection.prototype.getCharacterDataChanged = function () {
if (!this.treeChanges.anyCharacterDataChanged)
return []; // No characterData mutations occurred.
var nodes = this.treeChanges.keys();
var result = [];
for (var i = 0; i < nodes.length; i++) {
var target = nodes[i];
if (Movement_1.Movement.STAYED_IN !== this.treeChanges.reachabilityChange(target))
continue;
var change = this.treeChanges.get(target);
if (!change.characterData ||
target.textContent == change.characterDataOldValue)
continue;
result.push(target);
}
return result;
};
MutationProjection.prototype.computeMatchabilityChange = function (selector, el) {
if (!this.matchCache)
this.matchCache = [];
if (!this.matchCache[selector.uid])
this.matchCache[selector.uid] = new NodeMap_1.NodeMap();
var cache = this.matchCache[selector.uid];
var result = cache.get(el);
if (result === undefined) {
result = selector.matchabilityChange(el, this.treeChanges.get(el));
cache.set(el, result);
}
return result;
};
MutationProjection.prototype.matchabilityChange = function (node) {
var _this = this;
// TODO(rafaelw): Include PI, CDATA?
// Only include text nodes.
if (this.characterDataOnly) {
switch (node.nodeType) {
case Node.COMMENT_NODE:
case Node.TEXT_NODE:
return Movement_1.Movement.STAYED_IN;
default:
return Movement_1.Movement.STAYED_OUT;
}
}
// No element filter. Include all nodes.
if (!this.selectors)
return Movement_1.Movement.STAYED_IN;
// Element filter. Exclude non-elements.
if (node.nodeType !== Node.ELEMENT_NODE)
return Movement_1.Movement.STAYED_OUT;
var el = node;
var matchChanges = this.selectors.map(function (selector) {
return _this.computeMatchabilityChange(selector, el);
});
var accum = Movement_1.Movement.STAYED_OUT;
var i = 0;
while (accum !== Movement_1.Movement.STAYED_IN && i < matchChanges.length) {
switch (matchChanges[i]) {
case Movement_1.Movement.STAYED_IN:
accum = Movement_1.Movement.STAYED_IN;
break;
case Movement_1.Movement.ENTERED:
if (accum === Movement_1.Movement.EXITED)
accum = Movement_1.Movement.STAYED_IN;
else
accum = Movement_1.Movement.ENTERED;
break;
case Movement_1.Movement.EXITED:
if (accum === Movement_1.Movement.ENTERED)
accum = Movement_1.Movement.STAYED_IN;
else
accum = Movement_1.Movement.EXITED;
break;
}
i++;
}
return accum;
};
MutationProjection.prototype.getChildlistChange = function (el) {
var change = this.childListChangeMap.get(el);
if (!change) {
change = new ChildListChange_1.ChildListChange();
this.childListChangeMap.set(el, change);
}
return change;
};
MutationProjection.prototype.processChildlistChanges = function () {
if (this.childListChangeMap)
return;
this.childListChangeMap = new NodeMap_1.NodeMap();
var _loop_1 = function (i) {
var mutation = this_1.mutations[i];
if (mutation.type != 'childList')
return "continue";
if (this_1.treeChanges.reachabilityChange(mutation.target) !== Movement_1.Movement.STAYED_IN &&
!this_1.calcOldPreviousSibling)
return "continue";
var change = this_1.getChildlistChange(mutation.target);
var oldPrevious = mutation.previousSibling;
var recordOldPrevious = function (node, previous) {
if (!node ||
change.oldPrevious.has(node) ||
change.added.has(node) ||
change.maybeMoved.has(node))
return;
if (previous &&
(change.added.has(previous) ||
change.maybeMoved.has(previous)))
return;
change.oldPrevious.set(node, previous);
};
for (var j = 0; j < mutation.removedNodes.length; j++) {
var node = mutation.removedNodes[j];
recordOldPrevious(node, oldPrevious);
if (change.added.has(node)) {
change.added.delete(node);
}
else {
change.removed.set(node, true);
change.maybeMoved.delete(node);
}
oldPrevious = node;
}
recordOldPrevious(mutation.nextSibling, oldPrevious);
for (var j = 0; j < mutation.addedNodes.length; j++) {
var node = mutation.addedNodes[j];
if (change.removed.has(node)) {
change.removed.delete(node);
change.maybeMoved.set(node, true);
}
else {
change.added.set(node, true);
}
}
};
var this_1 = this;
for (var i = 0; i < this.mutations.length; i++) {
_loop_1(i);
}
};
MutationProjection.prototype.wasReordered = function (node) {
if (!this.treeChanges.anyParentsChanged)
return false;
this.processChildlistChanges();
var parentNode = node.parentNode;
var nodeChange = this.treeChanges.get(node);
if (nodeChange && nodeChange.oldParentNode)
parentNode = nodeChange.oldParentNode;
var change = this.childListChangeMap.get(parentNode);
if (!change)
return false;
if (change.moved)
return change.moved.get(node);
change.moved = new NodeMap_1.NodeMap();
var pendingMoveDecision = new NodeMap_1.NodeMap();
function isMoved(node) {
if (!node)
return false;
if (!change.maybeMoved.has(node))
return false;
var didMove = change.moved.get(node);
if (didMove !== undefined)
return didMove;
if (pendingMoveDecision.has(node)) {
didMove = true;
}
else {
pendingMoveDecision.set(node, true);
didMove = getPrevious(node) !== getOldPrevious(node);
}
if (pendingMoveDecision.has(node)) {
pendingMoveDecision.delete(node);
change.moved.set(node, didMove);
}
else {
didMove = change.moved.get(node);
}
return didMove;
}
var oldPreviousCache = new NodeMap_1.NodeMap();
function getOldPrevious(node) {
var oldPrevious = oldPreviousCache.get(node);
if (oldPrevious !== undefined)
return oldPrevious;
oldPrevious = change.oldPrevious.get(node);
while (oldPrevious &&
(change.removed.has(oldPrevious) || isMoved(oldPrevious))) {
oldPrevious = getOldPrevious(oldPrevious);
}
if (oldPrevious === undefined)
oldPrevious = node.previousSibling;
oldPreviousCache.set(node, oldPrevious);
return oldPrevious;
}
var previousCache = new NodeMap_1.NodeMap();
function getPrevious(node) {
if (previousCache.has(node))
return previousCache.get(node);
var previous = node.previousSibling;
while (previous && (change.added.has(previous) || isMoved(previous)))
previous = previous.previousSibling;
previousCache.set(node, previous);
return previous;
}
change.maybeMoved.keys().forEach(isMoved);
return change.moved.get(node);
};
return MutationProjection;
}());
exports.MutationProjection = MutationProjection;
//# sourceMappingURL=MutationProjection.js.map