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. 14var __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}; 20var MutationObserverCtor; 21if (typeof WebKitMutationObserver !== 'undefined') 22 MutationObserverCtor = WebKitMutationObserver; 23else 24 MutationObserverCtor = MutationObserver; 25if (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} 30var 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 */ 83var 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 = {})); 92function enteredOrExited(changeType) { 93 return changeType === Movement.ENTERED || changeType === Movement.EXITED; 94} 95var 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})(); 177var 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})(); 187var 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); 273var 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})(); 675var 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. 725var validNameInitialChar = /[a-zA-Z_]+/; 726var 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??? 730function escapeQuotes(value) { 731 return '"' + value.replace(/"/, '\\\"') + '"'; 732} 733var 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})(); 763var 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})(); 1137var attributeFilterPattern = /^([a-zA-Z:_]+[a-zA-Z0-9_\-:\.]*)$/; 1138function 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} 1148function 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} 1167function 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} 1176var 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])
1319); 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})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.