vendor: 24,572 bytes, lines 1-610
1/*! 2 * Shim for MutationObserver interface 3 * Author: Graeme Yeates (github.com/megawac) 4 * Repository: https://github.com/megawac/MutationObserver.js 5 * License: WTFPL V2, 2004 (wtfpl.net). 6 * Though credit and staring the repo will make me feel pretty, you can modify and redistribute as you please. 7 * Attempts to follow spec (http:// www.w3.org/TR/dom/#mutation-observers) as closely as possible for native javascript 8 * See https://github.com/WebKit/webkit/blob/master/Source/WebCore/dom/MutationObserver.cpp for current webkit source c++ implementation 9 */ 10 11/** 12 * prefix bugs: 13 - https://bugs.webkit.org/show_bug.cgi?id=85161 14 - https://bugzilla.mozilla.org/show_bug.cgi?id=749920 15 * Don't use WebKitMutationObserver as Safari (6.0.5-6.1) use a buggy implementation 16*/ 17window.MutationObserver = window.MutationObserver || (function(undefined) { 18 "use strict"; 19 /** 20 * @param {function(Array.<MutationRecord>, MutationObserver)} listener 21 * @constructor 22 */ 23 function MutationObserver(listener) { 24 /** 25 * @type {Array.<Object>} 26 * @private 27 */ 28 this._watched = []; 29 /** @private */ 30 this._listener = listener; 31 } 32 33 /** 34 * Start a recursive timeout function to check all items being observed for mutations 35 * @type {MutationObserver} observer 36 * @private 37 */ 38 function startMutationChecker(observer) { 39 (function check() { 40 var mutations = observer.takeRecords(); 41 42 if (mutations.length) { // fire away 43 // calling the listener with context is not spec but currently consistent with FF and WebKit 44 observer._listener(mutations, observer); 45 } 46 /** @private */ 47 observer._timeout = setTimeout(check, MutationObserver._period); 48 })(); 49 } 50 51 /** 52 * Period to check for mutations (~32 times/sec) 53 * @type {number} 54 * @expose 55 */ 56 MutationObserver._period = 30 /*ms+runtime*/ ; 57 58 /** 59 * Exposed API 60 * @expose 61 * @final 62 */ 63 MutationObserver.prototype = { 64 /** 65 * see http:// dom.spec.whatwg.org/#dom-mutationobserver-observe 66 * not going to throw here but going to follow the current spec config sets 67 * @param {Node|null} $target 68 * @param {Object|null} config : MutationObserverInit configuration dictionary 69 * @expose 70 * @return undefined 71 */ 72 observe: function($target, config) { 73 /** 74 * Using slightly different names so closure can go ham 75 * @type {!Object} : A custom mutation config 76 */ 77 var settings = { 78 attr: !! (config.attributes || config.attributeFilter || config.attributeOldValue), 79 80 // some browsers enforce that subtree must be set with childList, attributes or characterData. 81 // We don't care as spec doesn't specify this rule. 82 kids: !! config.childList, 83 descendents: !! config.subtree, 84 charData: !! (config.characterData || config.characterDataOldValue) 85 }; 86 87 var watched = this._watched; 88 89 // remove already observed target element from pool 90 for (var i = 0; i < watched.length; i++) { 91 if (watched[i].tar === $target) watched.splice(i, 1); 92 } 93 94 if (config.attributeFilter) { 95 /** 96 * converts to a {key: true} dict for faster lookup 97 * @type {Object.<String,Boolean>} 98 */ 99 settings.afilter = reduce(config.attributeFilter, function(a, b) { 100 a[b] = true; 101 return a; 102 }, {}); 103 } 104 105 watched.push({ 106 tar: $target, 107 fn: createMutationSearcher($target, settings) 108 }); 109 110 // reconnect if not connected 111 if (!this._timeout) { 112 startMutationChecker(this); 113 } 114 }, 115 116 /** 117 * Finds mutations since last check and empties the "record queue" i.e. mutations will only be found once 118 * @expose 119 * @return {Array.<MutationRecord>} 120 */ 121 takeRecords: function() { 122 var mutations = []; 123 var watched = this._watched; 124 125 for (var i = 0; i < watched.length; i++) { 126 watched[i].fn(mutations); 127 } 128 129 return mutations; 130 }, 131 132 /** 133 * @expose 134 * @return undefined 135 */ 136 disconnect: function() { 137 this._watched = []; // clear the stuff being observed 138 clearTimeout(this._timeout); // ready for garbage collection 139 /** @private */ 140 this._timeout = null; 141 } 142 }; 143 144 /** 145 * Simple MutationRecord pseudoclass. No longer exposing as its not fully compliant 146 * @param {Object} data 147 * @return {Object} a MutationRecord 148 */ 149 function MutationRecord(data) { 150 var settings = { // technically these should be on proto so hasOwnProperty will return false for non explicitly props 151 type: null, 152 target: null, 153 addedNodes: [], 154 removedNodes: [], 155 previousSibling: null, 156 nextSibling: null, 157 attributeName: null, 158 attributeNamespace: null, 159 oldValue: null 160 }; 161 for (var prop in data) { 162 if (has(settings, prop) && data[prop] !== undefined) settings[prop] = data[prop]; 163 } 164 return settings; 165 } 166 167 /** 168 * Creates a func to find all the mutations 169 * 170 * @param {Node} $target 171 * @param {!Object} config : A custom mutation config 172 */ 173 function createMutationSearcher($target, config) { 174 /** type {Elestuct} */ 175 var $oldstate = clone($target, config); // create the cloned datastructure 176 177 /** 178 * consumes array of mutations we can push to 179 * 180 * @param {Array.<MutationRecord>} mutations 181 */ 182 return function(mutations) { 183 var olen = mutations.length, dirty; 184 185 // Alright we check base level changes in attributes... easy 186 if (config.attr && $oldstate.attr) { 187 findAttributeMutations(mutations, $target, $oldstate.attr, config.afilter); 188 } 189 190 // check childlist or subtree for mutations 191 if (config.kids || config.descendents) { 192 dirty = searchSubtree(mutations, $target, $oldstate, config); 193 } 194 195 // reclone data structure if theres changes 196 if (dirty || mutations.length !== olen) { 197 /** type {Elestuct} */ 198 $oldstate = clone($target, config); 199 } 200 }; 201 } 202 203 /* attributes + attributeFilter helpers */ 204 205 // Check if the environment has the attribute bug (#4) which cause 206 // element.attributes.style to always be null. 207 var hasAttributeBug = document.createElement("i"); 208 hasAttributeBug.style.top = 0; 209 hasAttributeBug = hasAttributeBug.attributes.style.value != "null"; 210 211 /** 212 * Gets an attribute value in an environment without attribute bug 213 * 214 * @param {Node} el 215 * @param {Attr} attr 216 * @return {String} an attribute value 217 */ 218 function getAttributeSimple(el, attr) { 219 // There is a potential for a warning to occur here if the attribute is a 220 // custom attribute in IE<9 with a custom .toString() method. This is 221 // just a warning and doesn't affect execution (see #21) 222 return attr.value; 223 } 224 225 /** 226 * Gets an attribute value with special hack for style attribute (see #4) 227 * 228 * @param {Node} el 229 * @param {Attr} attr 230 * @return {String} an attribute value 231 */ 232 function getAttributeWithStyleHack(el, attr) { 233 // As with getAttributeSimple there is a potential warning for custom attribtues in IE7. 234 return attr.name !== "style" ? attr.value : el.style.cssText; 235 } 236 237 var getAttributeValue = hasAttributeBug ? getAttributeSimple : getAttributeWithStyleHack; 238 239 /** 240 * fast helper to check to see if attributes object of an element has changed 241 * doesnt handle the textnode case 242 * 243 * @param {Array.<MutationRecord>} mutations 244 * @param {Node} $target 245 * @param {Object.<string, string>} $oldstate : Custom attribute clone data structure from clone 246 * @param {Object} filter 247 */ 248 function findAttributeMutations(mutations, $target, $oldstate, filter) { 249 var checked = {}; 250 var attributes = $target.attributes; 251 var attr; 252 var name; 253 var i = attributes.length; 254 while (i--) { 255 attr = attributes[i]; 256 name = attr.name; 257 if (!filter || has(filter, name)) { 258 if (getAttributeValue($target, attr) !== $oldstate[name]) { 259 // The pushing is redundant but gzips very nicely 260 mutations.push(MutationRecord({ 261 type: "attributes", 262 target: $target, 263 attributeName: name, 264 oldValue: $oldstate[name], 265 attributeNamespace: attr.namespaceURI // in ie<8 it incorrectly will return undefined 266 })); 267 } 268 checked[name] = true; 269 } 270 } 271 for (name in $oldstate) { 272 if (!(checked[name])) { 273 mutations.push(MutationRecord({ 274 target: $target, 275 type: "attributes", 276 attributeName: name, 277 oldValue: $oldstate[name] 278 })); 279 } 280 } 281 } 282 283 /** 284 * searchSubtree: array of mutations so far, element, element clone, bool 285 * synchronous dfs comparision of two nodes 286 * This function is applied to any observed element with childList or subtree specified 287 * Sorry this is kind of confusing as shit, tried to comment it a bit... 288 * codereview.stackexchange.com/questions/38351 discussion of an earlier version of this func 289 * 290 * @param {Array} mutations 291 * @param {Node} $target 292 * @param {!Object} $oldstate : A custom cloned node from clone() 293 * @param {!Object} config : A custom mutation config 294 */ 295 function searchSubtree(mutations, $target, $oldstate, config) { 296 // Track if the tree is dirty and has to be recomputed (#14). 297 var dirty; 298 /* 299 * Helper to identify node rearrangment and stuff... 300 * There is no gaurentee that the same node will be identified for both added and removed nodes 301 * if the positions have been shuffled. 302 * conflicts array will be emptied by end of operation 303 */ 304 function resolveConflicts(conflicts, node, $kids, $oldkids, numAddedNodes) { 305 // the distance between the first conflicting node and the last 306 var distance = conflicts.length - 1; 307 // prevents same conflict being resolved twice consider when two nodes switch places. 308 // only one should be given a mutation event (note -~ is used as a math.ceil shorthand) 309 var counter = -~((distance - numAddedNodes) / 2); 310 var $cur; 311 var oldstruct; 312 var conflict; 313 while ((conflict = conflicts.pop())) { 314 $cur = $kids[conflict.i]; 315 oldstruct = $oldkids[conflict.j]; 316 317 // attempt to determine if there was node rearrangement... won't gaurentee all matches 318 // also handles case where added/removed nodes cause nodes to be identified as conflicts 319 if (config.kids && counter && Math.abs(conflict.i - conflict.j) >= distance) { 320 mutations.push(MutationRecord({ 321 type: "childList", 322 target: node, 323 addedNodes: [$cur], 324 removedNodes: [$cur], 325 // haha don't rely on this please 326 nextSibling: $cur.nextSibling, 327 previousSibling: $cur.previousSibling 328 })); 329 counter--; // found conflict 330 } 331 332 // Alright we found the resorted nodes now check for other types of mutations 333 if (config.attr && oldstruct.attr) findAttributeMutations(mutations, $cur, oldstruct.attr, config.afilter); 334 if (config.charData && $cur.nodeType === 3 && $cur.nodeValue !== oldstruct.charData) { 335 mutations.push(MutationRecord({ 336 type: "characterData", 337 target: $cur 338 })); 339 } 340 // now look @ subtree 341 if (config.descendents) findMutations($cur, oldstruct); 342 } 343 } 344 345 /** 346 * Main worker. Finds and adds mutations if there are any 347 * @param {Node} node 348 * @param {!Object} old : A cloned data structure using internal clone 349 */ 350 function findMutations(node, old) { 351 var $kids = node.childNodes; 352 var $oldkids = old.kids; 353 var klen = $kids.length; 354 // $oldkids will be undefined for text and comment nodes 355 var olen = $oldkids ? $oldkids.length : 0; 356 // if (!olen && !klen) return; // both empty; clearly no changes 357 358 // we delay the intialization of these for marginal performance in the expected case (actually quite signficant on large subtrees when these would be otherwise unused) 359 // map of checked element of ids to prevent registering the same conflict twice 360 var map; 361 // array of potential conflicts (ie nodes that may have been re arranged) 362 var conflicts; 363 var id; // element id from getElementId helper 364 var idx; // index of a moved or inserted element 365 366 var oldstruct; 367 // current and old nodes 368 var $cur; 369 var $old; 370 // track the number of added nodes so we can resolve conflicts more accurately 371 var numAddedNodes = 0; 372 373 // iterate over both old and current child nodes at the same time 374 var i = 0, j = 0; 375 // while there is still anything left in $kids or $oldkids (same as i < $kids.length || j < $oldkids.length;) 376 while( i < klen || j < olen ) { 377 // current and old nodes at the indexs 378 $cur = $kids[i]; 379 oldstruct = $oldkids[j]; 380 $old = oldstruct && oldstruct.node; 381 382 if ($cur === $old) { // expected case - optimized for this case 383 // check attributes as specified by config 384 if (config.attr && oldstruct.attr) /* oldstruct.attr instead of textnode check */findAttributeMutations(mutations, $cur, oldstruct.attr, config.afilter); 385 // check character data if node is a comment or textNode and it's being observed 386 if (config.charData && oldstruct.charData !== undefined && $cur.nodeValue !== oldstruct.charData) { 387 mutations.push(MutationRecord({ 388 type: "characterData", 389 target: $cur 390 })); 391 } 392 393 // resolve conflicts; it will be undefined if there are no conflicts - otherwise an array 394 if (conflicts) resolveConflicts(conflicts, node, $kids, $oldkids, numAddedNodes); 395 396 // recurse on next level of children. Avoids the recursive call when there are no children left to iterate 397 if (config.descendents && ($cur.childNodes.length || oldstruct.kids && oldstruct.kids.length)) findMutations($cur, oldstruct); 398 399 i++; 400 j++; 401 } else { // (uncommon case) lookahead until they are the same again or the end of children 402 dirty = true; 403 if (!map) { // delayed initalization (big perf benefit) 404 map = {}; 405 conflicts = []; 406 } 407 if ($cur) { 408 // check id is in the location map otherwise do a indexOf search 409 if (!(map[id = getElementId($cur)])) { // to prevent double checking 410 // mark id as found 411 map[id] = true; 412 // custom indexOf using comparitor checking oldkids[i].node === $cur 413 if ((idx = indexOfCustomNode($oldkids, $cur, j)) === -1) { 414 if (config.kids) { 415 mutations.push(MutationRecord({ 416 type: "childList", 417 target: node, 418 addedNodes: [$cur], // $cur is a new node 419 nextSibling: $cur.nextSibling, 420 previousSibling: $cur.previousSibling 421 })); 422 numAddedNodes++; 423 } 424 } else { 425 conflicts.push({ // add conflict 426 i: i, 427 j: idx 428 }); 429 } 430 } 431 i++; 432 } 433 434 if ($old && 435 // special case: the changes may have been resolved: i and j appear congurent so we can continue using the expected case 436 $old !== $kids[i] 437 ) { 438 if (!(map[id = getElementId($old)])) { 439 map[id] = true; 440 if ((idx = indexOf($kids, $old, i)) === -1) { 441 if (config.kids) { 442 mutations.push(MutationRecord({ 443 type: "childList", 444 target: old.node, 445 removedNodes: [$old], 446 nextSibling: $oldkids[j + 1], // praise no indexoutofbounds exception 447 previousSibling: $oldkids[j - 1] 448 })); 449 numAddedNodes--; 450 } 451 } else { 452 conflicts.push({ 453 i: idx, 454 j: j 455 }); 456 } 457 } 458 j++; 459 } 460 }// end uncommon case 461 }// end loop 462 463 // resolve any remaining conflicts 464 if (conflicts) resolveConflicts(conflicts, node, $kids, $oldkids, numAddedNodes); 465 } 466 findMutations($target, $oldstate); 467 return dirty; 468 } 469 470 /** 471 * Utility 472 * Cones a element into a custom data structure designed for comparision. https://gist.github.com/megawac/8201012 473 * 474 * @param {Node} $target 475 * @param {!Object} config : A custom mutation config 476 * @return {!Object} : Cloned data structure 477 */ 478 function clone($target, config) { 479 var recurse = true; // set true so childList we'll always check the first level 480 return (function copy($target) { 481 var elestruct = { 482 /** @type {Node} */ 483 node: $target 484 }; 485 486 // Store current character data of target text or comment node if the config requests 487 // those properties to be observed. 488 if (config.charData && ($target.nodeType === 3 || $target.nodeType === 8)) { 489 elestruct.charData = $target.nodeValue; 490 } 491 // its either a element, comment, doc frag or document node 492 else { 493 // Add attr only if subtree is specified or top level and avoid if 494 // attributes is a document object (#13). 495 if (config.attr && recurse && $target.nodeType === 1) { 496 /** 497 * clone live attribute list to an object structure {name: val} 498 * @type {Object.<string, string>} 499 */ 500 elestruct.attr = reduce($target.attributes, function(memo, attr) { 501 if (!config.afilter || config.afilter[attr.name]) { 502 memo[attr.name] = getAttributeValue($target, attr); 503 } 504 return memo; 505 }, {}); 506 } 507 508 // whether we should iterate the children of $target node 509 if (recurse && ((config.kids || config.charData) || (config.attr && config.descendents)) ) { 510 /** @type {Array.<!Object>} : Array of custom clone */ 511 elestruct.kids = map($target.childNodes, copy); 512 } 513 514 recurse = config.descendents; 515 } 516 return elestruct; 517 })($target); 518 } 519 520 /** 521 * indexOf an element in a collection of custom nodes 522 * 523 * @param {NodeList} set 524 * @param {!Object} $node : A custom cloned node 525 * @param {number} idx : index to start the loop 526 * @return {number} 527 */ 528 function indexOfCustomNode(set, $node, idx) { 529 return indexOf(set, $node, idx, JSCompiler_renameProperty("node")); 530 } 531 532 // using a non id (eg outerHTML or nodeValue) is extremely naive and will run into issues with nodes that may appear the same like <li></li> 533 var counter = 1; // don't use 0 as id (falsy) 534 /** @const */ 535 var expando = "mo_id"; 536 537 /** 538 * Attempt to uniquely id an element for hashing. We could optimize this for legacy browsers but it hopefully wont be called enough to be a concern 539 * 540 * @param {Node} $ele 541 * @return {(string|number)} 542 */ 543 function getElementId($ele) { 544 try { 545 return $ele.id || ($ele[expando] = $ele[expando] || counter++); 546 } catch (o_O) { // ie <8 will throw if you set an unknown property on a text node 547 try { 548 return $ele.nodeValue; // naive 549 } catch (shitie) { // when text node is removed: https://gist.github.com/megawac/8355978 :( 550 return counter++; 551 } 552 } 553 } 554 555 /** 556 * **map** Apply a mapping function to each item of a set 557 * @param {Array|NodeList} set 558 * @param {Function} iterator 559 */ 560 function map(set, iterator) { 561 var results = []; 562 for (var index = 0; index < set.length; index++) { 563 results[index] = iterator(set[index], index, set); 564 } 565 return results; 566 } 567 568 /** 569 * **Reduce** builds up a single result from a list of values 570 * @param {Array|NodeList|NamedNodeMap} set 571 * @param {Function} iterator 572 * @param {*} [memo] Initial value of the memo. 573 */ 574 function reduce(set, iterator, memo) { 575 for (var index = 0; index < set.length; index++) { 576 memo = iterator(memo, set[index], index, set); 577 } 578 return memo; 579 } 580 581 /** 582 * **indexOf** find index of item in collection. 583 * @param {Array|NodeList} set 584 * @param {Object} item 585 * @param {number} idx 586 * @param {string} [prop] Property on set item to compare to item 587 */ 588 function indexOf(set, item, idx, prop) { 589 for (/*idx = ~~idx*/; idx < set.length; idx++) {// start idx is always given as this is internal 590 if ((prop ? set[idx][prop] : set[idx]) === item) return idx; 591 } 592 return -1; 593 } 594 595 /** 596 * @param {Object} obj 597 * @param {(string|number)} prop 598 * @return {boolean} 599 */ 600 function has(obj, prop) { 601 return obj[prop] !== undefined; // will be nicely inlined by gcc 602 } 603 604 // GCC hack see http:// stackoverflow.com/a/23202438/1517919 605 function JSCompiler_renameProperty(a) { 606 return a; 607 } 608 609 return MutationObserver; 610})(void 0);
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.