Mutation Summary

Mutation Summary is a JavaScript library that makes observing changes to the DOM fast, easy and safe. It's built on top of (and requires) a new browser API called DOM Mutation Observers.

بۇ قوليازمىنى بىۋاسىتە قاچىلاشقا بولمايدۇ. بۇ باشقا قوليازمىلارنىڭ ئىشلىتىشى ئۈچۈن تەمىنلەنگەن ئامبار بولۇپ، ئىشلىتىش ئۈچۈن مېتا كۆرسەتمىسىگە قىستۇرىدىغان كود: // @require https://update.greatest.deepsurf.us/scripts/12036/70722/Mutation%20Summary.js

  1. // Copyright 2011 Google Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. var __extends = this.__extends || function (d, b) {
  15. for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
  16. function __() { this.constructor = d; }
  17. __.prototype = b.prototype;
  18. d.prototype = new __();
  19. };
  20. var MutationObserverCtor;
  21. if (typeof WebKitMutationObserver !== 'undefined')
  22. MutationObserverCtor = WebKitMutationObserver;
  23. else
  24. MutationObserverCtor = MutationObserver;
  25. if (MutationObserverCtor === undefined) {
  26. console.error('DOM Mutation Observers are required.');
  27. console.error('https://developer.mozilla.org/en-US/docs/DOM/MutationObserver');
  28. throw Error('DOM Mutation Observers are required');
  29. }
  30. var NodeMap = (function () {
  31. function NodeMap() {
  32. this.nodes = [];
  33. this.values = [];
  34. }
  35. NodeMap.prototype.isIndex = function (s) {
  36. return +s === s >>> 0;
  37. };
  38. NodeMap.prototype.nodeId = function (node) {
  39. var id = node[NodeMap.ID_PROP];
  40. if (!id)
  41. id = node[NodeMap.ID_PROP] = NodeMap.nextId_++;
  42. return id;
  43. };
  44. NodeMap.prototype.set = function (node, value) {
  45. var id = this.nodeId(node);
  46. this.nodes[id] = node;
  47. this.values[id] = value;
  48. };
  49. NodeMap.prototype.get = function (node) {
  50. var id = this.nodeId(node);
  51. return this.values[id];
  52. };
  53. NodeMap.prototype.has = function (node) {
  54. return this.nodeId(node) in this.nodes;
  55. };
  56. NodeMap.prototype.delete = function (node) {
  57. var id = this.nodeId(node);
  58. delete this.nodes[id];
  59. this.values[id] = undefined;
  60. };
  61. NodeMap.prototype.keys = function () {
  62. var nodes = [];
  63. for (var id in this.nodes) {
  64. if (!this.isIndex(id))
  65. continue;
  66. nodes.push(this.nodes[id]);
  67. }
  68. return nodes;
  69. };
  70. NodeMap.ID_PROP = '__mutation_summary_node_map_id__';
  71. NodeMap.nextId_ = 1;
  72. return NodeMap;
  73. })();
  74. /**
  75. * var reachableMatchableProduct = [
  76. * // STAYED_OUT, ENTERED, STAYED_IN, EXITED
  77. * [ STAYED_OUT, STAYED_OUT, STAYED_OUT, STAYED_OUT ], // STAYED_OUT
  78. * [ STAYED_OUT, ENTERED, ENTERED, STAYED_OUT ], // ENTERED
  79. * [ STAYED_OUT, ENTERED, STAYED_IN, EXITED ], // STAYED_IN
  80. * [ STAYED_OUT, STAYED_OUT, EXITED, EXITED ] // EXITED
  81. * ];
  82. */
  83. var Movement;
  84. (function (Movement) {
  85. Movement[Movement["STAYED_OUT"] = 0] = "STAYED_OUT";
  86. Movement[Movement["ENTERED"] = 1] = "ENTERED";
  87. Movement[Movement["STAYED_IN"] = 2] = "STAYED_IN";
  88. Movement[Movement["REPARENTED"] = 3] = "REPARENTED";
  89. Movement[Movement["REORDERED"] = 4] = "REORDERED";
  90. Movement[Movement["EXITED"] = 5] = "EXITED";
  91. })(Movement || (Movement = {}));
  92. function enteredOrExited(changeType) {
  93. return changeType === Movement.ENTERED || changeType === Movement.EXITED;
  94. }
  95. var NodeChange = (function () {
  96. function NodeChange(node, childList, attributes, characterData, oldParentNode, added, attributeOldValues, characterDataOldValue) {
  97. if (childList === void 0) { childList = false; }
  98. if (attributes === void 0) { attributes = false; }
  99. if (characterData === void 0) { characterData = false; }
  100. if (oldParentNode === void 0) { oldParentNode = null; }
  101. if (added === void 0) { added = false; }
  102. if (attributeOldValues === void 0) { attributeOldValues = null; }
  103. if (characterDataOldValue === void 0) { characterDataOldValue = null; }
  104. this.node = node;
  105. this.childList = childList;
  106. this.attributes = attributes;
  107. this.characterData = characterData;
  108. this.oldParentNode = oldParentNode;
  109. this.added = added;
  110. this.attributeOldValues = attributeOldValues;
  111. this.characterDataOldValue = characterDataOldValue;
  112. this.isCaseInsensitive =
  113. this.node.nodeType === Node.ELEMENT_NODE &&
  114. this.node instanceof HTMLElement &&
  115. this.node.ownerDocument instanceof HTMLDocument;
  116. }
  117. NodeChange.prototype.getAttributeOldValue = function (name) {
  118. if (!this.attributeOldValues)
  119. return undefined;
  120. if (this.isCaseInsensitive)
  121. name = name.toLowerCase();
  122. return this.attributeOldValues[name];
  123. };
  124. NodeChange.prototype.getAttributeNamesMutated = function () {
  125. var names = [];
  126. if (!this.attributeOldValues)
  127. return names;
  128. for (var name in this.attributeOldValues) {
  129. names.push(name);
  130. }
  131. return names;
  132. };
  133. NodeChange.prototype.attributeMutated = function (name, oldValue) {
  134. this.attributes = true;
  135. this.attributeOldValues = this.attributeOldValues || {};
  136. if (name in this.attributeOldValues)
  137. return;
  138. this.attributeOldValues[name] = oldValue;
  139. };
  140. NodeChange.prototype.characterDataMutated = function (oldValue) {
  141. if (this.characterData)
  142. return;
  143. this.characterData = true;
  144. this.characterDataOldValue = oldValue;
  145. };
  146. // Note: is it possible to receive a removal followed by a removal. This
  147. // can occur if the removed node is added to an non-observed node, that
  148. // node is added to the observed area, and then the node removed from
  149. // it.
  150. NodeChange.prototype.removedFromParent = function (parent) {
  151. this.childList = true;
  152. if (this.added || this.oldParentNode)
  153. this.added = false;
  154. else
  155. this.oldParentNode = parent;
  156. };
  157. NodeChange.prototype.insertedIntoParent = function () {
  158. this.childList = true;
  159. this.added = true;
  160. };
  161. // An node's oldParent is
  162. // -its present parent, if its parentNode was not changed.
  163. // -null if the first thing that happened to it was an add.
  164. // -the node it was removed from if the first thing that happened to it
  165. // was a remove.
  166. NodeChange.prototype.getOldParent = function () {
  167. if (this.childList) {
  168. if (this.oldParentNode)
  169. return this.oldParentNode;
  170. if (this.added)
  171. return null;
  172. }
  173. return this.node.parentNode;
  174. };
  175. return NodeChange;
  176. })();
  177. var ChildListChange = (function () {
  178. function ChildListChange() {
  179. this.added = new NodeMap();
  180. this.removed = new NodeMap();
  181. this.maybeMoved = new NodeMap();
  182. this.oldPrevious = new NodeMap();
  183. this.moved = undefined;
  184. }
  185. return ChildListChange;
  186. })();
  187. var TreeChanges = (function (_super) {
  188. __extends(TreeChanges, _super);
  189. function TreeChanges(rootNode, mutations) {
  190. _super.call(this);
  191. this.rootNode = rootNode;
  192. this.reachableCache = undefined;
  193. this.wasReachableCache = undefined;
  194. this.anyParentsChanged = false;
  195. this.anyAttributesChanged = false;
  196. this.anyCharacterDataChanged = false;
  197. for (var m = 0; m < mutations.length; m++) {
  198. var mutation = mutations[m];
  199. switch (mutation.type) {
  200. case 'childList':
  201. this.anyParentsChanged = true;
  202. for (var i = 0; i < mutation.removedNodes.length; i++) {
  203. var node = mutation.removedNodes[i];
  204. this.getChange(node).removedFromParent(mutation.target);
  205. }
  206. for (var i = 0; i < mutation.addedNodes.length; i++) {
  207. var node = mutation.addedNodes[i];
  208. this.getChange(node).insertedIntoParent();
  209. }
  210. break;
  211. case 'attributes':
  212. this.anyAttributesChanged = true;
  213. var change = this.getChange(mutation.target);
  214. change.attributeMutated(mutation.attributeName, mutation.oldValue);
  215. break;
  216. case 'characterData':
  217. this.anyCharacterDataChanged = true;
  218. var change = this.getChange(mutation.target);
  219. change.characterDataMutated(mutation.oldValue);
  220. break;
  221. }
  222. }
  223. }
  224. TreeChanges.prototype.getChange = function (node) {
  225. var change = this.get(node);
  226. if (!change) {
  227. change = new NodeChange(node);
  228. this.set(node, change);
  229. }
  230. return change;
  231. };
  232. TreeChanges.prototype.getOldParent = function (node) {
  233. var change = this.get(node);
  234. return change ? change.getOldParent() : node.parentNode;
  235. };
  236. TreeChanges.prototype.getIsReachable = function (node) {
  237. if (node === this.rootNode)
  238. return true;
  239. if (!node)
  240. return false;
  241. this.reachableCache = this.reachableCache || new NodeMap();
  242. var isReachable = this.reachableCache.get(node);
  243. if (isReachable === undefined) {
  244. isReachable = this.getIsReachable(node.parentNode);
  245. this.reachableCache.set(node, isReachable);
  246. }
  247. return isReachable;
  248. };
  249. // A node wasReachable if its oldParent wasReachable.
  250. TreeChanges.prototype.getWasReachable = function (node) {
  251. if (node === this.rootNode)
  252. return true;
  253. if (!node)
  254. return false;
  255. this.wasReachableCache = this.wasReachableCache || new NodeMap();
  256. var wasReachable = this.wasReachableCache.get(node);
  257. if (wasReachable === undefined) {
  258. wasReachable = this.getWasReachable(this.getOldParent(node));
  259. this.wasReachableCache.set(node, wasReachable);
  260. }
  261. return wasReachable;
  262. };
  263. TreeChanges.prototype.reachabilityChange = function (node) {
  264. if (this.getIsReachable(node)) {
  265. return this.getWasReachable(node) ?
  266. Movement.STAYED_IN : Movement.ENTERED;
  267. }
  268. return this.getWasReachable(node) ?
  269. Movement.EXITED : Movement.STAYED_OUT;
  270. };
  271. return TreeChanges;
  272. })(NodeMap);
  273. var MutationProjection = (function () {
  274. // TOOD(any)
  275. function MutationProjection(rootNode, mutations, selectors, calcReordered, calcOldPreviousSibling) {
  276. this.rootNode = rootNode;
  277. this.mutations = mutations;
  278. this.selectors = selectors;
  279. this.calcReordered = calcReordered;
  280. this.calcOldPreviousSibling = calcOldPreviousSibling;
  281. this.treeChanges = new TreeChanges(rootNode, mutations);
  282. this.entered = [];
  283. this.exited = [];
  284. this.stayedIn = new NodeMap();
  285. this.visited = new NodeMap();
  286. this.childListChangeMap = undefined;
  287. this.characterDataOnly = undefined;
  288. this.matchCache = undefined;
  289. this.processMutations();
  290. }
  291. MutationProjection.prototype.processMutations = function () {
  292. if (!this.treeChanges.anyParentsChanged &&
  293. !this.treeChanges.anyAttributesChanged)
  294. return;
  295. var changedNodes = this.treeChanges.keys();
  296. for (var i = 0; i < changedNodes.length; i++) {
  297. this.visitNode(changedNodes[i], undefined);
  298. }
  299. };
  300. MutationProjection.prototype.visitNode = function (node, parentReachable) {
  301. if (this.visited.has(node))
  302. return;
  303. this.visited.set(node, true);
  304. var change = this.treeChanges.get(node);
  305. var reachable = parentReachable;
  306. // node inherits its parent's reachability change unless
  307. // its parentNode was mutated.
  308. if ((change && change.childList) || reachable == undefined)
  309. reachable = this.treeChanges.reachabilityChange(node);
  310. if (reachable === Movement.STAYED_OUT)
  311. return;
  312. // Cache match results for sub-patterns.
  313. this.matchabilityChange(node);
  314. if (reachable === Movement.ENTERED) {
  315. this.entered.push(node);
  316. }
  317. else if (reachable === Movement.EXITED) {
  318. this.exited.push(node);
  319. this.ensureHasOldPreviousSiblingIfNeeded(node);
  320. }
  321. else if (reachable === Movement.STAYED_IN) {
  322. var movement = Movement.STAYED_IN;
  323. if (change && change.childList) {
  324. if (change.oldParentNode !== node.parentNode) {
  325. movement = Movement.REPARENTED;
  326. this.ensureHasOldPreviousSiblingIfNeeded(node);
  327. }
  328. else if (this.calcReordered && this.wasReordered(node)) {
  329. movement = Movement.REORDERED;
  330. }
  331. }
  332. this.stayedIn.set(node, movement);
  333. }
  334. if (reachable === Movement.STAYED_IN)
  335. return;
  336. // reachable === ENTERED || reachable === EXITED.
  337. for (var child = node.firstChild; child; child = child.nextSibling) {
  338. this.visitNode(child, reachable);
  339. }
  340. };
  341. MutationProjection.prototype.ensureHasOldPreviousSiblingIfNeeded = function (node) {
  342. if (!this.calcOldPreviousSibling)
  343. return;
  344. this.processChildlistChanges();
  345. var parentNode = node.parentNode;
  346. var nodeChange = this.treeChanges.get(node);
  347. if (nodeChange && nodeChange.oldParentNode)
  348. parentNode = nodeChange.oldParentNode;
  349. var change = this.childListChangeMap.get(parentNode);
  350. if (!change) {
  351. change = new ChildListChange();
  352. this.childListChangeMap.set(parentNode, change);
  353. }
  354. if (!change.oldPrevious.has(node)) {
  355. change.oldPrevious.set(node, node.previousSibling);
  356. }
  357. };
  358. MutationProjection.prototype.getChanged = function (summary, selectors, characterDataOnly) {
  359. this.selectors = selectors;
  360. this.characterDataOnly = characterDataOnly;
  361. for (var i = 0; i < this.entered.length; i++) {
  362. var node = this.entered[i];
  363. var matchable = this.matchabilityChange(node);
  364. if (matchable === Movement.ENTERED || matchable === Movement.STAYED_IN)
  365. summary.added.push(node);
  366. }
  367. var stayedInNodes = this.stayedIn.keys();
  368. for (var i = 0; i < stayedInNodes.length; i++) {
  369. var node = stayedInNodes[i];
  370. var matchable = this.matchabilityChange(node);
  371. if (matchable === Movement.ENTERED) {
  372. summary.added.push(node);
  373. }
  374. else if (matchable === Movement.EXITED) {
  375. summary.removed.push(node);
  376. }
  377. else if (matchable === Movement.STAYED_IN && (summary.reparented || summary.reordered)) {
  378. var movement = this.stayedIn.get(node);
  379. if (summary.reparented && movement === Movement.REPARENTED)
  380. summary.reparented.push(node);
  381. else if (summary.reordered && movement === Movement.REORDERED)
  382. summary.reordered.push(node);
  383. }
  384. }
  385. for (var i = 0; i < this.exited.length; i++) {
  386. var node = this.exited[i];
  387. var matchable = this.matchabilityChange(node);
  388. if (matchable === Movement.EXITED || matchable === Movement.STAYED_IN)
  389. summary.removed.push(node);
  390. }
  391. };
  392. MutationProjection.prototype.getOldParentNode = function (node) {
  393. var change = this.treeChanges.get(node);
  394. if (change && change.childList)
  395. return change.oldParentNode ? change.oldParentNode : null;
  396. var reachabilityChange = this.treeChanges.reachabilityChange(node);
  397. if (reachabilityChange === Movement.STAYED_OUT || reachabilityChange === Movement.ENTERED)
  398. throw Error('getOldParentNode requested on invalid node.');
  399. return node.parentNode;
  400. };
  401. MutationProjection.prototype.getOldPreviousSibling = function (node) {
  402. var parentNode = node.parentNode;
  403. var nodeChange = this.treeChanges.get(node);
  404. if (nodeChange && nodeChange.oldParentNode)
  405. parentNode = nodeChange.oldParentNode;
  406. var change = this.childListChangeMap.get(parentNode);
  407. if (!change)
  408. throw Error('getOldPreviousSibling requested on invalid node.');
  409. return change.oldPrevious.get(node);
  410. };
  411. MutationProjection.prototype.getOldAttribute = function (element, attrName) {
  412. var change = this.treeChanges.get(element);
  413. if (!change || !change.attributes)
  414. throw Error('getOldAttribute requested on invalid node.');
  415. var value = change.getAttributeOldValue(attrName);
  416. if (value === undefined)
  417. throw Error('getOldAttribute requested for unchanged attribute name.');
  418. return value;
  419. };
  420. MutationProjection.prototype.attributeChangedNodes = function (includeAttributes) {
  421. if (!this.treeChanges.anyAttributesChanged)
  422. return {}; // No attributes mutations occurred.
  423. var attributeFilter;
  424. var caseInsensitiveFilter;
  425. if (includeAttributes) {
  426. attributeFilter = {};
  427. caseInsensitiveFilter = {};
  428. for (var i = 0; i < includeAttributes.length; i++) {
  429. var attrName = includeAttributes[i];
  430. attributeFilter[attrName] = true;
  431. caseInsensitiveFilter[attrName.toLowerCase()] = attrName;
  432. }
  433. }
  434. var result = {};
  435. var nodes = this.treeChanges.keys();
  436. for (var i = 0; i < nodes.length; i++) {
  437. var node = nodes[i];
  438. var change = this.treeChanges.get(node);
  439. if (!change.attributes)
  440. continue;
  441. if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(node) ||
  442. Movement.STAYED_IN !== this.matchabilityChange(node)) {
  443. continue;
  444. }
  445. var element = node;
  446. var changedAttrNames = change.getAttributeNamesMutated();
  447. for (var j = 0; j < changedAttrNames.length; j++) {
  448. var attrName = changedAttrNames[j];
  449. if (attributeFilter &&
  450. !attributeFilter[attrName] &&
  451. !(change.isCaseInsensitive && caseInsensitiveFilter[attrName])) {
  452. continue;
  453. }
  454. var oldValue = change.getAttributeOldValue(attrName);
  455. if (oldValue === element.getAttribute(attrName))
  456. continue;
  457. if (caseInsensitiveFilter && change.isCaseInsensitive)
  458. attrName = caseInsensitiveFilter[attrName];
  459. result[attrName] = result[attrName] || [];
  460. result[attrName].push(element);
  461. }
  462. }
  463. return result;
  464. };
  465. MutationProjection.prototype.getOldCharacterData = function (node) {
  466. var change = this.treeChanges.get(node);
  467. if (!change || !change.characterData)
  468. throw Error('getOldCharacterData requested on invalid node.');
  469. return change.characterDataOldValue;
  470. };
  471. MutationProjection.prototype.getCharacterDataChanged = function () {
  472. if (!this.treeChanges.anyCharacterDataChanged)
  473. return []; // No characterData mutations occurred.
  474. var nodes = this.treeChanges.keys();
  475. var result = [];
  476. for (var i = 0; i < nodes.length; i++) {
  477. var target = nodes[i];
  478. if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(target))
  479. continue;
  480. var change = this.treeChanges.get(target);
  481. if (!change.characterData ||
  482. target.textContent == change.characterDataOldValue)
  483. continue;
  484. result.push(target);
  485. }
  486. return result;
  487. };
  488. MutationProjection.prototype.computeMatchabilityChange = function (selector, el) {
  489. if (!this.matchCache)
  490. this.matchCache = [];
  491. if (!this.matchCache[selector.uid])
  492. this.matchCache[selector.uid] = new NodeMap();
  493. var cache = this.matchCache[selector.uid];
  494. var result = cache.get(el);
  495. if (result === undefined) {
  496. result = selector.matchabilityChange(el, this.treeChanges.get(el));
  497. cache.set(el, result);
  498. }
  499. return result;
  500. };
  501. MutationProjection.prototype.matchabilityChange = function (node) {
  502. var _this = this;
  503. // TODO(rafaelw): Include PI, CDATA?
  504. // Only include text nodes.
  505. if (this.characterDataOnly) {
  506. switch (node.nodeType) {
  507. case Node.COMMENT_NODE:
  508. case Node.TEXT_NODE:
  509. return Movement.STAYED_IN;
  510. default:
  511. return Movement.STAYED_OUT;
  512. }
  513. }
  514. // No element filter. Include all nodes.
  515. if (!this.selectors)
  516. return Movement.STAYED_IN;
  517. // Element filter. Exclude non-elements.
  518. if (node.nodeType !== Node.ELEMENT_NODE)
  519. return Movement.STAYED_OUT;
  520. var el = node;
  521. var matchChanges = this.selectors.map(function (selector) {
  522. return _this.computeMatchabilityChange(selector, el);
  523. });
  524. var accum = Movement.STAYED_OUT;
  525. var i = 0;
  526. while (accum !== Movement.STAYED_IN && i < matchChanges.length) {
  527. switch (matchChanges[i]) {
  528. case Movement.STAYED_IN:
  529. accum = Movement.STAYED_IN;
  530. break;
  531. case Movement.ENTERED:
  532. if (accum === Movement.EXITED)
  533. accum = Movement.STAYED_IN;
  534. else
  535. accum = Movement.ENTERED;
  536. break;
  537. case Movement.EXITED:
  538. if (accum === Movement.ENTERED)
  539. accum = Movement.STAYED_IN;
  540. else
  541. accum = Movement.EXITED;
  542. break;
  543. }
  544. i++;
  545. }
  546. return accum;
  547. };
  548. MutationProjection.prototype.getChildlistChange = function (el) {
  549. var change = this.childListChangeMap.get(el);
  550. if (!change) {
  551. change = new ChildListChange();
  552. this.childListChangeMap.set(el, change);
  553. }
  554. return change;
  555. };
  556. MutationProjection.prototype.processChildlistChanges = function () {
  557. if (this.childListChangeMap)
  558. return;
  559. this.childListChangeMap = new NodeMap();
  560. for (var i = 0; i < this.mutations.length; i++) {
  561. var mutation = this.mutations[i];
  562. if (mutation.type != 'childList')
  563. continue;
  564. if (this.treeChanges.reachabilityChange(mutation.target) !== Movement.STAYED_IN &&
  565. !this.calcOldPreviousSibling)
  566. continue;
  567. var change = this.getChildlistChange(mutation.target);
  568. var oldPrevious = mutation.previousSibling;
  569. function recordOldPrevious(node, previous) {
  570. if (!node ||
  571. change.oldPrevious.has(node) ||
  572. change.added.has(node) ||
  573. change.maybeMoved.has(node))
  574. return;
  575. if (previous &&
  576. (change.added.has(previous) ||
  577. change.maybeMoved.has(previous)))
  578. return;
  579. change.oldPrevious.set(node, previous);
  580. }
  581. for (var j = 0; j < mutation.removedNodes.length; j++) {
  582. var node = mutation.removedNodes[j];
  583. recordOldPrevious(node, oldPrevious);
  584. if (change.added.has(node)) {
  585. change.added.delete(node);
  586. }
  587. else {
  588. change.removed.set(node, true);
  589. change.maybeMoved.delete(node);
  590. }
  591. oldPrevious = node;
  592. }
  593. recordOldPrevious(mutation.nextSibling, oldPrevious);
  594. for (var j = 0; j < mutation.addedNodes.length; j++) {
  595. var node = mutation.addedNodes[j];
  596. if (change.removed.has(node)) {
  597. change.removed.delete(node);
  598. change.maybeMoved.set(node, true);
  599. }
  600. else {
  601. change.added.set(node, true);
  602. }
  603. }
  604. }
  605. };
  606. MutationProjection.prototype.wasReordered = function (node) {
  607. if (!this.treeChanges.anyParentsChanged)
  608. return false;
  609. this.processChildlistChanges();
  610. var parentNode = node.parentNode;
  611. var nodeChange = this.treeChanges.get(node);
  612. if (nodeChange && nodeChange.oldParentNode)
  613. parentNode = nodeChange.oldParentNode;
  614. var change = this.childListChangeMap.get(parentNode);
  615. if (!change)
  616. return false;
  617. if (change.moved)
  618. return change.moved.get(node);
  619. change.moved = new NodeMap();
  620. var pendingMoveDecision = new NodeMap();
  621. function isMoved(node) {
  622. if (!node)
  623. return false;
  624. if (!change.maybeMoved.has(node))
  625. return false;
  626. var didMove = change.moved.get(node);
  627. if (didMove !== undefined)
  628. return didMove;
  629. if (pendingMoveDecision.has(node)) {
  630. didMove = true;
  631. }
  632. else {
  633. pendingMoveDecision.set(node, true);
  634. didMove = getPrevious(node) !== getOldPrevious(node);
  635. }
  636. if (pendingMoveDecision.has(node)) {
  637. pendingMoveDecision.delete(node);
  638. change.moved.set(node, didMove);
  639. }
  640. else {
  641. didMove = change.moved.get(node);
  642. }
  643. return didMove;
  644. }
  645. var oldPreviousCache = new NodeMap();
  646. function getOldPrevious(node) {
  647. var oldPrevious = oldPreviousCache.get(node);
  648. if (oldPrevious !== undefined)
  649. return oldPrevious;
  650. oldPrevious = change.oldPrevious.get(node);
  651. while (oldPrevious &&
  652. (change.removed.has(oldPrevious) || isMoved(oldPrevious))) {
  653. oldPrevious = getOldPrevious(oldPrevious);
  654. }
  655. if (oldPrevious === undefined)
  656. oldPrevious = node.previousSibling;
  657. oldPreviousCache.set(node, oldPrevious);
  658. return oldPrevious;
  659. }
  660. var previousCache = new NodeMap();
  661. function getPrevious(node) {
  662. if (previousCache.has(node))
  663. return previousCache.get(node);
  664. var previous = node.previousSibling;
  665. while (previous && (change.added.has(previous) || isMoved(previous)))
  666. previous = previous.previousSibling;
  667. previousCache.set(node, previous);
  668. return previous;
  669. }
  670. change.maybeMoved.keys().forEach(isMoved);
  671. return change.moved.get(node);
  672. };
  673. return MutationProjection;
  674. })();
  675. var Summary = (function () {
  676. function Summary(projection, query) {
  677. var _this = this;
  678. this.projection = projection;
  679. this.added = [];
  680. this.removed = [];
  681. this.reparented = query.all || query.element || query.characterData ? [] : undefined;
  682. this.reordered = query.all ? [] : undefined;
  683. projection.getChanged(this, query.elementFilter, query.characterData);
  684. if (query.all || query.attribute || query.attributeList) {
  685. var filter = query.attribute ? [query.attribute] : query.attributeList;
  686. var attributeChanged = projection.attributeChangedNodes(filter);
  687. if (query.attribute) {
  688. this.valueChanged = attributeChanged[query.attribute] || [];
  689. }
  690. else {
  691. this.attributeChanged = attributeChanged;
  692. if (query.attributeList) {
  693. query.attributeList.forEach(function (attrName) {
  694. if (!_this.attributeChanged.hasOwnProperty(attrName))
  695. _this.attributeChanged[attrName] = [];
  696. });
  697. }
  698. }
  699. }
  700. if (query.all || query.characterData) {
  701. var characterDataChanged = projection.getCharacterDataChanged();
  702. if (query.characterData)
  703. this.valueChanged = characterDataChanged;
  704. else
  705. this.characterDataChanged = characterDataChanged;
  706. }
  707. if (this.reordered)
  708. this.getOldPreviousSibling = projection.getOldPreviousSibling.bind(projection);
  709. }
  710. Summary.prototype.getOldParentNode = function (node) {
  711. return this.projection.getOldParentNode(node);
  712. };
  713. Summary.prototype.getOldAttribute = function (node, name) {
  714. return this.projection.getOldAttribute(node, name);
  715. };
  716. Summary.prototype.getOldCharacterData = function (node) {
  717. return this.projection.getOldCharacterData(node);
  718. };
  719. Summary.prototype.getOldPreviousSibling = function (node) {
  720. return this.projection.getOldPreviousSibling(node);
  721. };
  722. return Summary;
  723. })();
  724. // TODO(rafaelw): Allow ':' and '.' as valid name characters.
  725. var validNameInitialChar = /[a-zA-Z_]+/;
  726. var validNameNonInitialChar = /[a-zA-Z0-9_\-]+/;
  727. // TODO(rafaelw): Consider allowing backslash in the attrValue.
  728. // TODO(rafaelw): There's got a to be way to represent this state machine
  729. // more compactly???
  730. function escapeQuotes(value) {
  731. return '"' + value.replace(/"/, '\\\"') + '"';
  732. }
  733. var Qualifier = (function () {
  734. function Qualifier() {
  735. }
  736. Qualifier.prototype.matches = function (oldValue) {
  737. if (oldValue === null)
  738. return false;
  739. if (this.attrValue === undefined)
  740. return true;
  741. if (!this.contains)
  742. return this.attrValue == oldValue;
  743. var tokens = oldValue.split(' ');
  744. for (var i = 0; i < tokens.length; i++) {
  745. if (this.attrValue === tokens[i])
  746. return true;
  747. }
  748. return false;
  749. };
  750. Qualifier.prototype.toString = function () {
  751. if (this.attrName === 'class' && this.contains)
  752. return '.' + this.attrValue;
  753. if (this.attrName === 'id' && !this.contains)
  754. return '#' + this.attrValue;
  755. if (this.contains)
  756. return '[' + this.attrName + '~=' + escapeQuotes(this.attrValue) + ']';
  757. if ('attrValue' in this)
  758. return '[' + this.attrName + '=' + escapeQuotes(this.attrValue) + ']';
  759. return '[' + this.attrName + ']';
  760. };
  761. return Qualifier;
  762. })();
  763. var Selector = (function () {
  764. function Selector() {
  765. this.uid = Selector.nextUid++;
  766. this.qualifiers = [];
  767. }
  768. Object.defineProperty(Selector.prototype, "caseInsensitiveTagName", {
  769. get: function () {
  770. return this.tagName.toUpperCase();
  771. },
  772. enumerable: true,
  773. configurable: true
  774. });
  775. Object.defineProperty(Selector.prototype, "selectorString", {
  776. get: function () {
  777. return this.tagName + this.qualifiers.join('');
  778. },
  779. enumerable: true,
  780. configurable: true
  781. });
  782. Selector.prototype.isMatching = function (el) {
  783. return el[Selector.matchesSelector](this.selectorString);
  784. };
  785. Selector.prototype.wasMatching = function (el, change, isMatching) {
  786. if (!change || !change.attributes)
  787. return isMatching;
  788. var tagName = change.isCaseInsensitive ? this.caseInsensitiveTagName : this.tagName;
  789. if (tagName !== '*' && tagName !== el.tagName)
  790. return false;
  791. var attributeOldValues = [];
  792. var anyChanged = false;
  793. for (var i = 0; i < this.qualifiers.length; i++) {
  794. var qualifier = this.qualifiers[i];
  795. var oldValue = change.getAttributeOldValue(qualifier.attrName);
  796. attributeOldValues.push(oldValue);
  797. anyChanged = anyChanged || (oldValue !== undefined);
  798. }
  799. if (!anyChanged)
  800. return isMatching;
  801. for (var i = 0; i < this.qualifiers.length; i++) {
  802. var qualifier = this.qualifiers[i];
  803. var oldValue = attributeOldValues[i];
  804. if (oldValue === undefined)
  805. oldValue = el.getAttribute(qualifier.attrName);
  806. if (!qualifier.matches(oldValue))
  807. return false;
  808. }
  809. return true;
  810. };
  811. Selector.prototype.matchabilityChange = function (el, change) {
  812. var isMatching = this.isMatching(el);
  813. if (isMatching)
  814. return this.wasMatching(el, change, isMatching) ? Movement.STAYED_IN : Movement.ENTERED;
  815. else
  816. return this.wasMatching(el, change, isMatching) ? Movement.EXITED : Movement.STAYED_OUT;
  817. };
  818. Selector.parseSelectors = function (input) {
  819. var selectors = [];
  820. var currentSelector;
  821. var currentQualifier;
  822. function newSelector() {
  823. if (currentSelector) {
  824. if (currentQualifier) {
  825. currentSelector.qualifiers.push(currentQualifier);
  826. currentQualifier = undefined;
  827. }
  828. selectors.push(currentSelector);
  829. }
  830. currentSelector = new Selector();
  831. }
  832. function newQualifier() {
  833. if (currentQualifier)
  834. currentSelector.qualifiers.push(currentQualifier);
  835. currentQualifier = new Qualifier();
  836. }
  837. var WHITESPACE = /\s/;
  838. var valueQuoteChar;
  839. var SYNTAX_ERROR = 'Invalid or unsupported selector syntax.';
  840. var SELECTOR = 1;
  841. var TAG_NAME = 2;
  842. var QUALIFIER = 3;
  843. var QUALIFIER_NAME_FIRST_CHAR = 4;
  844. var QUALIFIER_NAME = 5;
  845. var ATTR_NAME_FIRST_CHAR = 6;
  846. var ATTR_NAME = 7;
  847. var EQUIV_OR_ATTR_QUAL_END = 8;
  848. var EQUAL = 9;
  849. var ATTR_QUAL_END = 10;
  850. var VALUE_FIRST_CHAR = 11;
  851. var VALUE = 12;
  852. var QUOTED_VALUE = 13;
  853. var SELECTOR_SEPARATOR = 14;
  854. var state = SELECTOR;
  855. var i = 0;
  856. while (i < input.length) {
  857. var c = input[i++];
  858. switch (state) {
  859. case SELECTOR:
  860. if (c.match(validNameInitialChar)) {
  861. newSelector();
  862. currentSelector.tagName = c;
  863. state = TAG_NAME;
  864. break;
  865. }
  866. if (c == '*') {
  867. newSelector();
  868. currentSelector.tagName = '*';
  869. state = QUALIFIER;
  870. break;
  871. }
  872. if (c == '.') {
  873. newSelector();
  874. newQualifier();
  875. currentSelector.tagName = '*';
  876. currentQualifier.attrName = 'class';
  877. currentQualifier.contains = true;
  878. state = QUALIFIER_NAME_FIRST_CHAR;
  879. break;
  880. }
  881. if (c == '#') {
  882. newSelector();
  883. newQualifier();
  884. currentSelector.tagName = '*';
  885. currentQualifier.attrName = 'id';
  886. state = QUALIFIER_NAME_FIRST_CHAR;
  887. break;
  888. }
  889. if (c == '[') {
  890. newSelector();
  891. newQualifier();
  892. currentSelector.tagName = '*';
  893. currentQualifier.attrName = '';
  894. state = ATTR_NAME_FIRST_CHAR;
  895. break;
  896. }
  897. if (c.match(WHITESPACE))
  898. break;
  899. throw Error(SYNTAX_ERROR);
  900. case TAG_NAME:
  901. if (c.match(validNameNonInitialChar)) {
  902. currentSelector.tagName += c;
  903. break;
  904. }
  905. if (c == '.') {
  906. newQualifier();
  907. currentQualifier.attrName = 'class';
  908. currentQualifier.contains = true;
  909. state = QUALIFIER_NAME_FIRST_CHAR;
  910. break;
  911. }
  912. if (c == '#') {
  913. newQualifier();
  914. currentQualifier.attrName = 'id';
  915. state = QUALIFIER_NAME_FIRST_CHAR;
  916. break;
  917. }
  918. if (c == '[') {
  919. newQualifier();
  920. currentQualifier.attrName = '';
  921. state = ATTR_NAME_FIRST_CHAR;
  922. break;
  923. }
  924. if (c.match(WHITESPACE)) {
  925. state = SELECTOR_SEPARATOR;
  926. break;
  927. }
  928. if (c == ',') {
  929. state = SELECTOR;
  930. break;
  931. }
  932. throw Error(SYNTAX_ERROR);
  933. case QUALIFIER:
  934. if (c == '.') {
  935. newQualifier();
  936. currentQualifier.attrName = 'class';
  937. currentQualifier.contains = true;
  938. state = QUALIFIER_NAME_FIRST_CHAR;
  939. break;
  940. }
  941. if (c == '#') {
  942. newQualifier();
  943. currentQualifier.attrName = 'id';
  944. state = QUALIFIER_NAME_FIRST_CHAR;
  945. break;
  946. }
  947. if (c == '[') {
  948. newQualifier();
  949. currentQualifier.attrName = '';
  950. state = ATTR_NAME_FIRST_CHAR;
  951. break;
  952. }
  953. if (c.match(WHITESPACE)) {
  954. state = SELECTOR_SEPARATOR;
  955. break;
  956. }
  957. if (c == ',') {
  958. state = SELECTOR;
  959. break;
  960. }
  961. throw Error(SYNTAX_ERROR);
  962. case QUALIFIER_NAME_FIRST_CHAR:
  963. if (c.match(validNameInitialChar)) {
  964. currentQualifier.attrValue = c;
  965. state = QUALIFIER_NAME;
  966. break;
  967. }
  968. throw Error(SYNTAX_ERROR);
  969. case QUALIFIER_NAME:
  970. if (c.match(validNameNonInitialChar)) {
  971. currentQualifier.attrValue += c;
  972. break;
  973. }
  974. if (c == '.') {
  975. newQualifier();
  976. currentQualifier.attrName = 'class';
  977. currentQualifier.contains = true;
  978. state = QUALIFIER_NAME_FIRST_CHAR;
  979. break;
  980. }
  981. if (c == '#') {
  982. newQualifier();
  983. currentQualifier.attrName = 'id';
  984. state = QUALIFIER_NAME_FIRST_CHAR;
  985. break;
  986. }
  987. if (c == '[') {
  988. newQualifier();
  989. state = ATTR_NAME_FIRST_CHAR;
  990. break;
  991. }
  992. if (c.match(WHITESPACE)) {
  993. state = SELECTOR_SEPARATOR;
  994. break;
  995. }
  996. if (c == ',') {
  997. state = SELECTOR;
  998. break;
  999. }
  1000. throw Error(SYNTAX_ERROR);
  1001. case ATTR_NAME_FIRST_CHAR:
  1002. if (c.match(validNameInitialChar)) {
  1003. currentQualifier.attrName = c;
  1004. state = ATTR_NAME;
  1005. break;
  1006. }
  1007. if (c.match(WHITESPACE))
  1008. break;
  1009. throw Error(SYNTAX_ERROR);
  1010. case ATTR_NAME:
  1011. if (c.match(validNameNonInitialChar)) {
  1012. currentQualifier.attrName += c;
  1013. break;
  1014. }
  1015. if (c.match(WHITESPACE)) {
  1016. state = EQUIV_OR_ATTR_QUAL_END;
  1017. break;
  1018. }
  1019. if (c == '~') {
  1020. currentQualifier.contains = true;
  1021. state = EQUAL;
  1022. break;
  1023. }
  1024. if (c == '=') {
  1025. currentQualifier.attrValue = '';
  1026. state = VALUE_FIRST_CHAR;
  1027. break;
  1028. }
  1029. if (c == ']') {
  1030. state = QUALIFIER;
  1031. break;
  1032. }
  1033. throw Error(SYNTAX_ERROR);
  1034. case EQUIV_OR_ATTR_QUAL_END:
  1035. if (c == '~') {
  1036. currentQualifier.contains = true;
  1037. state = EQUAL;
  1038. break;
  1039. }
  1040. if (c == '=') {
  1041. currentQualifier.attrValue = '';
  1042. state = VALUE_FIRST_CHAR;
  1043. break;
  1044. }
  1045. if (c == ']') {
  1046. state = QUALIFIER;
  1047. break;
  1048. }
  1049. if (c.match(WHITESPACE))
  1050. break;
  1051. throw Error(SYNTAX_ERROR);
  1052. case EQUAL:
  1053. if (c == '=') {
  1054. currentQualifier.attrValue = '';
  1055. state = VALUE_FIRST_CHAR;
  1056. break;
  1057. }
  1058. throw Error(SYNTAX_ERROR);
  1059. case ATTR_QUAL_END:
  1060. if (c == ']') {
  1061. state = QUALIFIER;
  1062. break;
  1063. }
  1064. if (c.match(WHITESPACE))
  1065. break;
  1066. throw Error(SYNTAX_ERROR);
  1067. case VALUE_FIRST_CHAR:
  1068. if (c.match(WHITESPACE))
  1069. break;
  1070. if (c == '"' || c == "'") {
  1071. valueQuoteChar = c;
  1072. state = QUOTED_VALUE;
  1073. break;
  1074. }
  1075. currentQualifier.attrValue += c;
  1076. state = VALUE;
  1077. break;
  1078. case VALUE:
  1079. if (c.match(WHITESPACE)) {
  1080. state = ATTR_QUAL_END;
  1081. break;
  1082. }
  1083. if (c == ']') {
  1084. state = QUALIFIER;
  1085. break;
  1086. }
  1087. if (c == "'" || c == '"')
  1088. throw Error(SYNTAX_ERROR);
  1089. currentQualifier.attrValue += c;
  1090. break;
  1091. case QUOTED_VALUE:
  1092. if (c == valueQuoteChar) {
  1093. state = ATTR_QUAL_END;
  1094. break;
  1095. }
  1096. currentQualifier.attrValue += c;
  1097. break;
  1098. case SELECTOR_SEPARATOR:
  1099. if (c.match(WHITESPACE))
  1100. break;
  1101. if (c == ',') {
  1102. state = SELECTOR;
  1103. break;
  1104. }
  1105. throw Error(SYNTAX_ERROR);
  1106. }
  1107. }
  1108. switch (state) {
  1109. case SELECTOR:
  1110. case TAG_NAME:
  1111. case QUALIFIER:
  1112. case QUALIFIER_NAME:
  1113. case SELECTOR_SEPARATOR:
  1114. // Valid end states.
  1115. newSelector();
  1116. break;
  1117. default:
  1118. throw Error(SYNTAX_ERROR);
  1119. }
  1120. if (!selectors.length)
  1121. throw Error(SYNTAX_ERROR);
  1122. return selectors;
  1123. };
  1124. Selector.nextUid = 1;
  1125. Selector.matchesSelector = (function () {
  1126. var element = document.createElement('div');
  1127. if (typeof element['webkitMatchesSelector'] === 'function')
  1128. return 'webkitMatchesSelector';
  1129. if (typeof element['mozMatchesSelector'] === 'function')
  1130. return 'mozMatchesSelector';
  1131. if (typeof element['msMatchesSelector'] === 'function')
  1132. return 'msMatchesSelector';
  1133. return 'matchesSelector';
  1134. })();
  1135. return Selector;
  1136. })();
  1137. var attributeFilterPattern = /^([a-zA-Z:_]+[a-zA-Z0-9_\-:\.]*)$/;
  1138. function validateAttribute(attribute) {
  1139. if (typeof attribute != 'string')
  1140. throw Error('Invalid request opion. attribute must be a non-zero length string.');
  1141. attribute = attribute.trim();
  1142. if (!attribute)
  1143. throw Error('Invalid request opion. attribute must be a non-zero length string.');
  1144. if (!attribute.match(attributeFilterPattern))
  1145. throw Error('Invalid request option. invalid attribute name: ' + attribute);
  1146. return attribute;
  1147. }
  1148. function validateElementAttributes(attribs) {
  1149. if (!attribs.trim().length)
  1150. throw Error('Invalid request option: elementAttributes must contain at least one attribute.');
  1151. var lowerAttributes = {};
  1152. var attributes = {};
  1153. var tokens = attribs.split(/\s+/);
  1154. for (var i = 0; i < tokens.length; i++) {
  1155. var name = tokens[i];
  1156. if (!name)
  1157. continue;
  1158. var name = validateAttribute(name);
  1159. var nameLower = name.toLowerCase();
  1160. if (lowerAttributes[nameLower])
  1161. throw Error('Invalid request option: observing multiple case variations of the same attribute is not supported.');
  1162. attributes[name] = true;
  1163. lowerAttributes[nameLower] = true;
  1164. }
  1165. return Object.keys(attributes);
  1166. }
  1167. function elementFilterAttributes(selectors) {
  1168. var attributes = {};
  1169. selectors.forEach(function (selector) {
  1170. selector.qualifiers.forEach(function (qualifier) {
  1171. attributes[qualifier.attrName] = true;
  1172. });
  1173. });
  1174. return Object.keys(attributes);
  1175. }
  1176. var MutationSummary = (function () {
  1177. function MutationSummary(opts) {
  1178. var _this = this;
  1179. this.connected = false;
  1180. this.options = MutationSummary.validateOptions(opts);
  1181. this.observerOptions = MutationSummary.createObserverOptions(this.options.queries);
  1182. this.root = this.options.rootNode;
  1183. this.callback = this.options.callback;
  1184. this.elementFilter = Array.prototype.concat.apply([], this.options.queries.map(function (query) {
  1185. return query.elementFilter ? query.elementFilter : [];
  1186. }));
  1187. if (!this.elementFilter.length)
  1188. this.elementFilter = undefined;
  1189. this.calcReordered = this.options.queries.some(function (query) {
  1190. return query.all;
  1191. });
  1192. this.queryValidators = []; // TODO(rafaelw): Shouldn't always define this.
  1193. if (MutationSummary.createQueryValidator) {
  1194. this.queryValidators = this.options.queries.map(function (query) {
  1195. return MutationSummary.createQueryValidator(_this.root, query);
  1196. });
  1197. }
  1198. this.observer = new MutationObserverCtor(function (mutations) {
  1199. _this.observerCallback(mutations);
  1200. });
  1201. this.reconnect();
  1202. }
  1203. MutationSummary.createObserverOptions = function (queries) {
  1204. var observerOptions = {
  1205. childList: true,
  1206. subtree: true
  1207. };
  1208. var attributeFilter;
  1209. function observeAttributes(attributes) {
  1210. if (observerOptions.attributes && !attributeFilter)
  1211. return; // already observing all.
  1212. observerOptions.attributes = true;
  1213. observerOptions.attributeOldValue = true;
  1214. if (!attributes) {
  1215. // observe all.
  1216. attributeFilter = undefined;
  1217. return;
  1218. }
  1219. // add to observed.
  1220. attributeFilter = attributeFilter || {};
  1221. attributes.forEach(function (attribute) {
  1222. attributeFilter[attribute] = true;
  1223. attributeFilter[attribute.toLowerCase()] = true;
  1224. });
  1225. }
  1226. queries.forEach(function (query) {
  1227. if (query.characterData) {
  1228. observerOptions.characterData = true;
  1229. observerOptions.characterDataOldValue = true;
  1230. return;
  1231. }
  1232. if (query.all) {
  1233. observeAttributes();
  1234. observerOptions.characterData = true;
  1235. observerOptions.characterDataOldValue = true;
  1236. return;
  1237. }
  1238. if (query.attribute) {
  1239. observeAttributes([query.attribute.trim()]);
  1240. return;
  1241. }
  1242. var attributes = elementFilterAttributes(query.elementFilter).concat(query.attributeList || []);
  1243. if (attributes.length)
  1244. observeAttributes(attributes);
  1245. });
  1246. if (attributeFilter)
  1247. observerOptions.attributeFilter = Object.keys(attributeFilter);
  1248. return observerOptions;
  1249. };
  1250. MutationSummary.validateOptions = function (options) {
  1251. for (var prop in options) {
  1252. if (!(prop in MutationSummary.optionKeys))
  1253. throw Error('Invalid option: ' + prop);
  1254. }
  1255. if (typeof options.callback !== 'function')
  1256. throw Error('Invalid options: callback is required and must be a function');
  1257. if (!options.queries || !options.queries.length)
  1258. throw Error('Invalid options: queries must contain at least one query request object.');
  1259. var opts = {
  1260. callback: options.callback,
  1261. rootNode: options.rootNode || document,
  1262. observeOwnChanges: !!options.observeOwnChanges,
  1263. oldPreviousSibling: !!options.oldPreviousSibling,
  1264. queries: []
  1265. };
  1266. for (var i = 0; i < options.queries.length; i++) {
  1267. var request = options.queries[i];
  1268. // all
  1269. if (request.all) {
  1270. if (Object.keys(request).length > 1)
  1271. throw Error('Invalid request option. all has no options.');
  1272. opts.queries.push({ all: true });
  1273. continue;
  1274. }
  1275. // attribute
  1276. if ('attribute' in request) {
  1277. var query = {
  1278. attribute: validateAttribute(request.attribute)
  1279. };
  1280. query.elementFilter = Selector.parseSelectors('*[' + query.attribute + ']');
  1281. if (Object.keys(request).length > 1)
  1282. throw Error('Invalid request option. attribute has no options.');
  1283. opts.queries.push(query);
  1284. continue;
  1285. }
  1286. // element
  1287. if ('element' in request) {
  1288. var requestOptionCount = Object.keys(request).length;
  1289. var query = {
  1290. element: request.element,
  1291. elementFilter: Selector.parseSelectors(request.element)
  1292. };
  1293. if (request.hasOwnProperty('elementAttributes')) {
  1294. query.attributeList = validateElementAttributes(request.elementAttributes);
  1295. requestOptionCount--;
  1296. }
  1297. if (requestOptionCount > 1)
  1298. throw Error('Invalid request option. element only allows elementAttributes option.');
  1299. opts.queries.push(query);
  1300. continue;
  1301. }
  1302. // characterData
  1303. if (request.characterData) {
  1304. if (Object.keys(request).length > 1)
  1305. throw Error('Invalid request option. characterData has no options.');
  1306. opts.queries.push({ characterData: true });
  1307. continue;
  1308. }
  1309. throw Error('Invalid request option. Unknown query request.');
  1310. }
  1311. return opts;
  1312. };
  1313. MutationSummary.prototype.createSummaries = function (mutations) {
  1314. if (!mutations || !mutations.length)
  1315. return [];
  1316. var projection = new MutationProjection(this.root, mutations, this.elementFilter, this.calcReordered, this.options.oldPreviousSibling);
  1317. var summaries = [];
  1318. for (var i = 0; i < this.options.queries.length; i++) {
  1319. summaries.push(new Summary(projection, this.options.queries[i]));
  1320. }
  1321. return summaries;
  1322. };
  1323. MutationSummary.prototype.checkpointQueryValidators = function () {
  1324. this.queryValidators.forEach(function (validator) {
  1325. if (validator)
  1326. validator.recordPreviousState();
  1327. });
  1328. };
  1329. MutationSummary.prototype.runQueryValidators = function (summaries) {
  1330. this.queryValidators.forEach(function (validator, index) {
  1331. if (validator)
  1332. validator.validate(summaries[index]);
  1333. });
  1334. };
  1335. MutationSummary.prototype.changesToReport = function (summaries) {
  1336. return summaries.some(function (summary) {
  1337. var summaryProps = ['added', 'removed', 'reordered', 'reparented',
  1338. 'valueChanged', 'characterDataChanged'];
  1339. if (summaryProps.some(function (prop) { return summary[prop] && summary[prop].length; }))
  1340. return true;
  1341. if (summary.attributeChanged) {
  1342. var attrNames = Object.keys(summary.attributeChanged);
  1343. var attrsChanged = attrNames.some(function (attrName) {
  1344. return !!summary.attributeChanged[attrName].length;
  1345. });
  1346. if (attrsChanged)
  1347. return true;
  1348. }
  1349. return false;
  1350. });
  1351. };
  1352. MutationSummary.prototype.observerCallback = function (mutations) {
  1353. if (!this.options.observeOwnChanges)
  1354. this.observer.disconnect();
  1355. var summaries = this.createSummaries(mutations);
  1356. this.runQueryValidators(summaries);
  1357. if (this.options.observeOwnChanges)
  1358. this.checkpointQueryValidators();
  1359. if (this.changesToReport(summaries))
  1360. this.callback(summaries);
  1361. // disconnect() may have been called during the callback.
  1362. if (!this.options.observeOwnChanges && this.connected) {
  1363. this.checkpointQueryValidators();
  1364. this.observer.observe(this.root, this.observerOptions);
  1365. }
  1366. };
  1367. MutationSummary.prototype.reconnect = function () {
  1368. if (this.connected)
  1369. throw Error('Already connected');
  1370. this.observer.observe(this.root, this.observerOptions);
  1371. this.connected = true;
  1372. this.checkpointQueryValidators();
  1373. };
  1374. MutationSummary.prototype.takeSummaries = function () {
  1375. if (!this.connected)
  1376. throw Error('Not connected');
  1377. var summaries = this.createSummaries(this.observer.takeRecords());
  1378. return this.changesToReport(summaries) ? summaries : undefined;
  1379. };
  1380. MutationSummary.prototype.disconnect = function () {
  1381. var summaries = this.takeSummaries();
  1382. this.observer.disconnect();
  1383. this.connected = false;
  1384. return summaries;
  1385. };
  1386. MutationSummary.NodeMap = NodeMap; // exposed for use in TreeMirror.
  1387. MutationSummary.parseElementFilter = Selector.parseSelectors; // exposed for testing.
  1388. MutationSummary.optionKeys = {
  1389. 'callback': true,
  1390. 'queries': true,
  1391. 'rootNode': true,
  1392. 'oldPreviousSibling': true,
  1393. 'observeOwnChanges': true
  1394. };
  1395. return MutationSummary;
  1396. })();