mutation-summary
Version:
Makes observing the DOM fast and easy
1,127 lines (1,112 loc) • 81.4 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MutationSummary = {}));
}(this, (function (exports) { 'use strict';
/**
* A helper class that maps from a DOM Node to an arbitrary value.
*/
var NodeMap = /** @class */ (function () {
/**
* Constructs a new and empty NodeMap.
*/
function NodeMap() {
this._nodes = [];
this._values = [];
}
NodeMap._isIndex = function (s) {
return +s === s >>> 0;
};
NodeMap._nodeId = function (node) {
var id = node[NodeMap._ID_PROP];
if (!id)
id = node[NodeMap._ID_PROP] = NodeMap._NEXT_ID++;
return id;
};
/**
* Sets the value of a node within the map.
* @param node The node to set the value for.
* @param value the value to associate with the node.
*/
NodeMap.prototype.set = function (node, value) {
var id = NodeMap._nodeId(node);
this._nodes[id] = node;
this._values[id] = value;
};
/**
* Gets the value for the given node.
*
* @param node The node to get the value of.
* @returns The value for the given node, or undefined if the node is not
* present in the map.
*/
NodeMap.prototype.get = function (node) {
var id = NodeMap._nodeId(node);
return id !== undefined ? this._values[id] : undefined;
};
/**
* Determines if a given node is in the NodeMap.
*
* @param node The node to determine if it is in the map.
*
* @returns true if the Node is contained in the map, false otherwise.
*/
NodeMap.prototype.has = function (node) {
return NodeMap._nodeId(node) in this._nodes;
};
/**
* Deletes a node from the NodeMap.
*
* @param node The node to delete.
*/
NodeMap.prototype.delete = function (node) {
var id = NodeMap._nodeId(node);
delete this._nodes[id];
this._values[id] = undefined;
};
/**
* @returns an array that holds the nodes that are the keys of the map.
*/
NodeMap.prototype.keys = function () {
var nodes = [];
for (var id in this._nodes) {
if (!NodeMap._isIndex(id))
continue;
nodes.push(this._nodes[id]);
}
return nodes;
};
NodeMap._ID_PROP = '__mutation_summary_node_map_id__';
NodeMap._NEXT_ID = 1;
return NodeMap;
}());
var ChildListChange = /** @class */ (function () {
function ChildListChange() {
this.added = new NodeMap();
this.removed = new NodeMap();
this.maybeMoved = new NodeMap();
this.oldPrevious = new NodeMap();
this.moved = undefined;
}
return ChildListChange;
}());
exports.Movement = void 0;
(function (Movement) {
Movement[Movement["STAYED_OUT"] = 0] = "STAYED_OUT";
Movement[Movement["ENTERED"] = 1] = "ENTERED";
Movement[Movement["STAYED_IN"] = 2] = "STAYED_IN";
Movement[Movement["REPARENTED"] = 3] = "REPARENTED";
Movement[Movement["REORDERED"] = 4] = "REORDERED";
Movement[Movement["EXITED"] = 5] = "EXITED";
})(exports.Movement || (exports.Movement = {}));
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var NodeChange = /** @class */ (function () {
function NodeChange(node, childList, attributes, characterData, oldParentNode, added, attributeOldValues, characterDataOldValue) {
if (childList === void 0) { childList = false; }
if (attributes === void 0) { attributes = false; }
if (characterData === void 0) { characterData = false; }
if (oldParentNode === void 0) { oldParentNode = null; }
if (added === void 0) { added = false; }
if (attributeOldValues === void 0) { attributeOldValues = null; }
if (characterDataOldValue === void 0) { characterDataOldValue = null; }
this.node = node;
this.childList = childList;
this.attributes = attributes;
this.characterData = characterData;
this.oldParentNode = oldParentNode;
this.added = added;
this.attributeOldValues = attributeOldValues;
this.characterDataOldValue = characterDataOldValue;
this.isCaseInsensitive =
this.node.nodeType === Node.ELEMENT_NODE &&
this.node instanceof HTMLElement &&
this.node.ownerDocument instanceof HTMLDocument;
}
NodeChange.prototype.getAttributeOldValue = function (name) {
if (!this.attributeOldValues)
return undefined;
if (this.isCaseInsensitive)
name = name.toLowerCase();
return this.attributeOldValues[name];
};
NodeChange.prototype.getAttributeNamesMutated = function () {
var names = [];
if (!this.attributeOldValues)
return names;
for (var name_1 in this.attributeOldValues) {
if (this.attributeOldValues.hasOwnProperty(name_1)) {
names.push(name_1);
}
}
return names;
};
NodeChange.prototype.attributeMutated = function (name, oldValue) {
this.attributes = true;
this.attributeOldValues = this.attributeOldValues || {};
if (name in this.attributeOldValues)
return;
this.attributeOldValues[name] = oldValue;
};
NodeChange.prototype.characterDataMutated = function (oldValue) {
if (this.characterData)
return;
this.characterData = true;
this.characterDataOldValue = oldValue;
};
// Note: is it possible to receive a removal followed by a removal. This
// can occur if the removed node is added to an non-observed node, that
// node is added to the observed area, and then the node removed from
// it.
NodeChange.prototype.removedFromParent = function (parent) {
this.childList = true;
if (this.added || this.oldParentNode)
this.added = false;
else
this.oldParentNode = parent;
};
NodeChange.prototype.insertedIntoParent = function () {
this.childList = true;
this.added = true;
};
// An node's oldParent is
// -its present parent, if its parentNode was not changed.
// -null if the first thing that happened to it was an add.
// -the node it was removed from if the first thing that happened to it
// was a remove.
NodeChange.prototype.getOldParent = function () {
if (this.childList) {
if (this.oldParentNode)
return this.oldParentNode;
if (this.added)
return null;
}
return this.node.parentNode;
};
return NodeChange;
}());
var TreeChanges = /** @class */ (function (_super) {
__extends(TreeChanges, _super);
function TreeChanges(rootNode, mutations) {
var _this = _super.call(this) || this;
_this.rootNode = rootNode;
_this.reachableCache = undefined;
_this.wasReachableCache = undefined;
_this.anyParentsChanged = false;
_this.anyAttributesChanged = false;
_this.anyCharacterDataChanged = false;
for (var m = 0; m < mutations.length; m++) {
var mutation = mutations[m];
switch (mutation.type) {
case 'childList':
_this.anyParentsChanged = true;
for (var i = 0; i < mutation.removedNodes.length; i++) {
var node = mutation.removedNodes[i];
_this.getChange(node).removedFromParent(mutation.target);
}
for (var i = 0; i < mutation.addedNodes.length; i++) {
var node = mutation.addedNodes[i];
_this.getChange(node).insertedIntoParent();
}
break;
case 'attributes': {
_this.anyAttributesChanged = true;
var change = _this.getChange(mutation.target);
change.attributeMutated(mutation.attributeName, mutation.oldValue);
break;
}
case 'characterData': {
_this.anyCharacterDataChanged = true;
var change = _this.getChange(mutation.target);
change.characterDataMutated(mutation.oldValue);
break;
}
}
}
return _this;
}
TreeChanges.prototype.getChange = function (node) {
var change = this.get(node);
if (!change) {
change = new NodeChange(node);
this.set(node, change);
}
return change;
};
TreeChanges.prototype.getOldParent = function (node) {
var change = this.get(node);
return change ? change.getOldParent() : node.parentNode;
};
TreeChanges.prototype.getIsReachable = function (node) {
if (node === this.rootNode)
return true;
if (!node)
return false;
this.reachableCache = this.reachableCache || new NodeMap();
var isReachable = this.reachableCache.get(node);
if (isReachable === undefined) {
isReachable = this.getIsReachable(node.parentNode);
this.reachableCache.set(node, isReachable);
}
return isReachable;
};
// A node wasReachable if its oldParent wasReachable.
TreeChanges.prototype.getWasReachable = function (node) {
if (node === this.rootNode)
return true;
if (!node)
return false;
this.wasReachableCache = this.wasReachableCache || new NodeMap();
var wasReachable = this.wasReachableCache.get(node);
if (wasReachable === undefined) {
wasReachable = this.getWasReachable(this.getOldParent(node));
this.wasReachableCache.set(node, wasReachable);
}
return wasReachable;
};
TreeChanges.prototype.reachabilityChange = function (node) {
if (this.getIsReachable(node)) {
return this.getWasReachable(node) ?
exports.Movement.STAYED_IN : exports.Movement.ENTERED;
}
return this.getWasReachable(node) ?
exports.Movement.EXITED : exports.Movement.STAYED_OUT;
};
return TreeChanges;
}(NodeMap));
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(rootNode, mutations);
this.entered = [];
this.exited = [];
this.stayedIn = new NodeMap();
this.visited = new 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 === exports.Movement.STAYED_OUT)
return;
// Cache match results for sub-patterns.
this.matchabilityChange(node);
if (reachable === exports.Movement.ENTERED) {
this.entered.push(node);
}
else if (reachable === exports.Movement.EXITED) {
this.exited.push(node);
this.ensureHasOldPreviousSiblingIfNeeded(node);
}
else if (reachable === exports.Movement.STAYED_IN) {
var movement = exports.Movement.STAYED_IN;
if (change && change.childList) {
if (change.oldParentNode !== node.parentNode) {
movement = exports.Movement.REPARENTED;
this.ensureHasOldPreviousSiblingIfNeeded(node);
}
else if (this.calcReordered && this.wasReordered(node)) {
movement = exports.Movement.REORDERED;
}
}
this.stayedIn.set(node, movement);
}
if (reachable === exports.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();
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 === exports.Movement.ENTERED || matchable === exports.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 === exports.Movement.ENTERED) {
summary.added.push(node);
}
else if (matchable === exports.Movement.EXITED) {
summary.removed.push(node);
}
else if (matchable === exports.Movement.STAYED_IN && (summary.reparented || summary.reordered)) {
var movement = this.stayedIn.get(node);
if (summary.reparented && movement === exports.Movement.REPARENTED)
summary.reparented.push(node);
else if (summary.reordered && movement === exports.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 === exports.Movement.EXITED || matchable === exports.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 === exports.Movement.STAYED_OUT || reachabilityChange === exports.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 (exports.Movement.STAYED_IN !== this.treeChanges.reachabilityChange(node) ||
exports.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 (exports.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();
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 exports.Movement.STAYED_IN;
default:
return exports.Movement.STAYED_OUT;
}
}
// No element filter. Include all nodes.
if (!this.selectors)
return exports.Movement.STAYED_IN;
// Element filter. Exclude non-elements.
if (node.nodeType !== Node.ELEMENT_NODE)
return exports.Movement.STAYED_OUT;
var el = node;
var matchChanges = this.selectors.map(function (selector) {
return _this.computeMatchabilityChange(selector, el);
});
var accum = exports.Movement.STAYED_OUT;
var i = 0;
while (accum !== exports.Movement.STAYED_IN && i < matchChanges.length) {
switch (matchChanges[i]) {
case exports.Movement.STAYED_IN:
accum = exports.Movement.STAYED_IN;
break;
case exports.Movement.ENTERED:
if (accum === exports.Movement.EXITED)
accum = exports.Movement.STAYED_IN;
else
accum = exports.Movement.ENTERED;
break;
case exports.Movement.EXITED:
if (accum === exports.Movement.ENTERED)
accum = exports.Movement.STAYED_IN;
else
accum = exports.Movement.EXITED;
break;
}
i++;
}
return accum;
};
MutationProjection.prototype.getChildlistChange = function (el) {
var change = this.childListChangeMap.get(el);
if (!change) {
change = new ChildListChange();
this.childListChangeMap.set(el, change);
}
return change;
};
MutationProjection.prototype.processChildlistChanges = function () {
if (this.childListChangeMap)
return;
this.childListChangeMap = new 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) !== exports.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();
var pendingMoveDecision = new 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();
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();
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;
}());
/**
* Represents a set of changes made to the DOM.
*/
var Summary = /** @class */ (function () {
/**
* Creates a new Summary instance given a [[MutationProjection]] and the
* [[IQuery]] that was responsible for this summary being generated.
*
* @param projection The projection containing the changes.
* @param query The query that cause the summary to be created.
*/
function Summary(projection, query) {
var _this = this;
this.projection = projection;
this.added = [];
this.removed = [];
this.reparented = query.all || query.element || query.characterData ? [] : undefined;
this.reordered = query.all ? [] : undefined;
projection.getChanged(this, query.elementFilter, query.characterData);
if (query.all || query.attribute || query.attributeList) {
var filter = query.attribute ? [query.attribute] : query.attributeList;
var attributeChanged = projection.attributeChangedNodes(filter);
if (query.attribute) {
this.valueChanged = attributeChanged[query.attribute] || [];
}
else {
this.attributeChanged = attributeChanged;
if (query.attributeList) {
query.attributeList.forEach(function (attrName) {
if (!_this.attributeChanged.hasOwnProperty(attrName))
_this.attributeChanged[attrName] = [];
});
}
}
}
if (query.all || query.characterData) {
var characterDataChanged = projection.getCharacterDataChanged();
if (query.characterData)
this.valueChanged = characterDataChanged;
else
this.characterDataChanged = characterDataChanged;
}
// TODO this seems unnecessary.
if (this.reordered)
this.getOldPreviousSibling = projection.getOldPreviousSibling.bind(projection);
}
/**
* Will retrieve the previous parentNode for and node. The node must be
* contained in the removed element array, otherwise the function throws an
* error.
*
* @param node The node to get the previous parent for.
*/
Summary.prototype.getOldParentNode = function (node) {
return this.projection.getOldParentNode(node);
};
/**
* Retrieves the previous value of an attribute for an element. The Element
* must be contained in the valueChanged element array, otherwise the
* function throws an error.
*
* @param element The element to ge the old value for.
* @param name The name off the attribute on the element to get the old value
* for.
*/
Summary.prototype.getOldAttribute = function (element, name) {
return this.projection.getOldAttribute(element, name);
};
/**
* Retrieves the previous text of `node`. `node` must be contained in the
* `valueChanged` node array, otherwise the function throws an error.
*
* @param node The node to get the old character data for.
*/
Summary.prototype.getOldCharacterData = function (node) {
return this.projection.getOldCharacterData(node);
};
/**
* Retrieves the previous previousSibling for a node. The node must be
* contained in the reordered element array, otherwise the function throws
* an error.
*
* @param node The node to get the previous sibling for.
*/
Summary.prototype.getOldPreviousSibling = function (node) {
return this.projection.getOldPreviousSibling(node);
};
return Summary;
}());
var Qualifier = /** @class */ (function () {
function Qualifier() {
}
Qualifier.prototype.matches = function (oldValue) {
if (oldValue === null)
return false;
if (this.attrValue === undefined)
return true;
if (!this.contains)
return this.attrValue == oldValue;
var tokens = oldValue.split(' ');
for (var i = 0; i < tokens.length; i++) {
if (this.attrValue === tokens[i])
return true;
}
return false;
};
Qualifier.prototype.toString = function () {
if (this.attrName === 'class' && this.contains)
return '.' + this.attrValue;
if (this.attrName === 'id' && !this.contains)
return '#' + this.attrValue;
if (this.contains)
return '[' + this.attrName + '~=' + escapeQuotes(this.attrValue) + ']';
if ('attrValue' in this)
return '[' + this.attrName + '=' + escapeQuotes(this.attrValue) + ']';
//@ts-ignore
return '[' + this.attrName + ']';
};
return Qualifier;
}());
function escapeQuotes(value) {
return '"' + value.replace(/"/, '\\\"') + '"';
}
// TODO(rafaelw): Allow ':' and '.' as valid name characters.
var validNameInitialChar = /[a-zA-Z_]+/;
var validNameNonInitialChar = /[a-zA-Z0-9_\-]+/;
var Selector = /** @class */ (function () {
function Selector() {
this.uid = Selector.nextUid++;
this.qualifiers = [];
}
Object.defineProperty(Selector.prototype, "caseInsensitiveTagName", {
get: function () {
return this.tagName.toUpperCase();
},
enumerable: false,
configurable: true
});
Object.defineProperty(Selector.prototype, "selectorString", {
get: function () {
return this.tagName + this.qualifiers.join('');
},
enumerable: false,
configurable: true
});
Selector.prototype.isMatching = function (el) {
return el[Selector.matchesSelector](this.selectorString);
};
Selector.prototype.wasMatching = function (el, change, isMatching) {
if (!change || !change.attributes)
return isMatching;
var tagName = change.isCaseInsensitive ? this.caseInsensitiveTagName : this.tagName;
if (tagName !== '*' && tagName !== el.tagName)
return false;
var attributeOldValues = [];
var anyChanged = false;
for (var i = 0; i < this.qualifiers.length; i++) {
var qualifier = this.qualifiers[i];
var oldValue = change.getAttributeOldValue(qualifier.attrName);
attributeOldValues.push(oldValue);
anyChanged = anyChanged || (oldValue !== undefined);
}
if (!anyChanged)
return isMatching;
for (var i = 0; i < this.qualifiers.length; i++) {
var qualifier = this.qualifiers[i];
var oldValue = attributeOldValues[i];
if (oldValue === undefined)
oldValue = el.getAttribute(qualifier.attrName);
if (!qualifier.matches(oldValue))
return false;
}
return true;
};
Selector.prototype.matchabilityChange = function (el, change) {
var isMatching = this.isMatching(el);
if (isMatching)
return this.wasMatching(el, change, isMatching) ? exports.Movement.STAYED_IN : exports.Movement.ENTERED;
else
return this.wasMatching(el, change, isMatching) ? exports.Movement.EXITED : exports.Movement.STAYED_OUT;
};
Selector.parseSelectors = function (input) {
var selectors = [];
var currentSelector;
var currentQualifier;
function newSelector() {
if (currentSelector) {
if (currentQualifier) {
currentSelector.qualifiers.push(currentQualifier);
currentQualifier = undefined;
}
selectors.push(currentSelector);
}
currentSelector = new Selector();
}
function newQualifier() {
if (currentQualifier)
currentSelector.qualifiers.push(currentQualifier);
currentQualifier = new Qualifier();
}
var WHITESPACE = /\s/;
var valueQuoteChar = undefined;
var SYNTAX_ERROR = 'Invalid or unsupported selector syntax.';
var SELECTOR = 1;
var TAG_NAME = 2;
var QUALIFIER = 3;
var QUALIFIER_NAME_FIRST_CHAR = 4;
var QUALIFIER_NAME = 5;
var ATTR_NAME_FIRST_CHAR = 6;
var ATTR_NAME = 7;
var EQUIV_OR_ATTR_QUAL_END = 8;
var EQUAL = 9;
var ATTR_QUAL_END = 10;
var VALUE_FIRST_CHAR = 11;
var VALUE = 12;
var QUOTED_VALUE = 13;
var SELECTOR_SEPARATOR = 14;
var state = SELECTOR;
var i = 0;
while (i < input.length) {
var c = input[i++];
switch (state) {
case SELECTOR:
if (c.match(validNameInitialChar)) {
newSelector();
currentSelector.tagName = c;
state = TAG_NAME;
break;
}
if (c == '*') {
newSelector();
currentSelector.tagName = '*';
state = QUALIFIER;
break;
}
if (c == '.') {
newSelector();
newQualifier();
currentSelector.tagName = '*';
currentQualifier.attrName = 'class';
currentQualifier.contains = true;
state = QUALIFIER_NAME_FIRST_CHAR;
break;
}
if (c == '#') {
newSelector();
newQualifier();
currentSelector.tagName = '*';
currentQualifier.attrName = 'id';
state = QUALIFIER_NAME_FIRST_CHAR;
break;
}
if (c == '[') {
newSelector();
newQualifier();
currentSelector.tagName = '*';
currentQualifier.attrName = '';
state = ATTR_NAME_FIRST_CHAR;
break;
}
if (c.match(WHITESPACE))
break;
throw Error(SYNTAX_ERROR);
case TAG_NAME:
if (c.match(validNameNonInitialChar)) {
currentSelector.tagName += c;
break;
}
if (c == '.') {
newQualifier();
currentQualifier.attrName = 'class';
currentQualifier.contains = true;
state = QUALIFIER_NAME_FIRST_CHAR;
break;
}
if (c == '#') {
newQualifier();
currentQualifier.attrName = 'id';
state = QUALIFIER_NAME_FIRST_CHAR;
break;
}
if (c == '[') {
newQualifier();
currentQualifier.attrName = '';
state = ATTR_NAME_FIRST_CHAR;
break;
}
if (c.match(WHITESPACE)) {
state = SELECTOR_SEPARATOR;
break;
}
if (c == ',') {
state = SELECTOR;
break;
}
throw Error(SYNTAX_ERROR);
case QUALIFIER:
if (c == '.') {
newQualifier();
currentQualifier.attrName = 'class';
currentQualifier.contains = true;
state = QUALIFIER_NAME_FIRST_CHAR;
break;
}
if (c == '#') {
newQualifier();
currentQualifier.attrName = 'id';
state = QUALIFIER_NAME_FIRST_CHAR;
break;
}
if (c == '[') {
newQualifier();
currentQualifier.attrName = '';
state = ATTR_NAME_FIRST_CHAR;
break;
}
if (c.match(WHITESPACE)) {
state = SELECTOR_SEPARATOR;
break;
}
if (c == ',') {
state = SELECTOR;
break;
}
throw Error(SYNTAX_ERROR);
case QUALIFIER_NAME_FIRST_CHAR:
if (c.match(validNameInitialChar)) {
currentQualifier.attrValue = c;
state = QUALIFIER_NAME;
break;
}
throw Error(SYNTAX_ERROR);
case QUALIFIER_NAME:
if (c.match(validNameNonInitialChar)) {
currentQualifier.attrValue += c;
break;
}
if (c == '.') {
newQualifier();
currentQualifier.attrName = 'class';
currentQualifier.contains = true;
state = QUALIFIER_NAME_FIRST_CHAR;
break;
}
if (c == '#') {
newQualifier();
currentQualifier.attrName = 'id';
state = QUALIFIER_NAME_FIRST_CHAR;
break;
}
if (c == '[') {
newQualifier();
state = ATTR_NAME_FIRST_CHAR;
break;
}
if (c.match(WHITESPACE)) {
state = SELECTOR_SEPARATOR;
break;
}
if (c == ',') {
state = SELECTOR;
break;
}
throw Error(SYNTAX_ERROR);
case ATTR_NAME_FIRST_CHAR:
if (c.match(validNameInitialChar)) {
currentQualifier.attrName = c;
state = ATTR_NAME;
break;
}
if (c.match(WHITESPACE))
break;
throw Error(SYNTAX_ERROR);
case ATTR_NAME:
if (c.match(validNameNonInitialChar)) {
currentQualifier.attrName += c;
break;
}
if (c.match(WHITESPACE)) {
state = EQUIV_OR_ATTR_QUAL_END;
break;
}
if (c == '~') {
currentQualifier.contains = true;
state = EQUAL;
break;
}
if (c == '=') {
currentQualifier.attrValue = '';
state = VALUE_FIRST_CHAR;
break;
}
if (c == ']') {
state = QUALIFIER;
break;
}
throw Error(SYNTAX_ERROR);
case EQUIV_OR_ATTR_QUAL_END:
if (c == '~') {
currentQualifier.contains = true;
state = EQUAL;
break;
}
if (c == '=') {
currentQualifier.attrValue = '';
state = VALUE_FIRST_CHAR;