mutation-observer

Simple wrapper for using DOM mutation events

Bu script direkt olarak kurulamaz. Başka scriptler için bir kütüphanedir ve meta yönergeleri içerir // @require https://update.greasyfork.org/scripts/7602/32979/mutation-observer.js

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