├── .gitignore ├── Readme.md ├── package.json ├── History.md ├── LICENSE └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | build 3 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # mutation-observer 3 | 4 | Exposes the native `MutationObserver` API provided by the browser, or a polyfill based on mutation events. (For compatibility with IE9-10.) 5 | 6 | MutationObserver polyfill by the [Polymer Project](https://www.polymer-project.org/). 7 | 8 | ## Installation 9 | 10 | ```bash 11 | $ npm install mutation-observer 12 | ``` 13 | 14 | ## API 15 | 16 | ```javascript 17 | var MutationObserver = require('mutation-observer'); 18 | ``` 19 | 20 | ## License 21 | 22 | BSD (See LICENSE file) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mutation-observer", 3 | "description": "Exposes the `MutationObserver` API, or a polyfill based on mutation events for IE 9-10.", 4 | "tags": [ 5 | "polyfill", 6 | "webmodule", 7 | "mutation", 8 | "observer", 9 | "browser" 10 | ], 11 | "version": "1.0.3", 12 | "dependencies": {}, 13 | "component": { 14 | "scripts": { 15 | "mutation-observer/index.js": "index.js" 16 | } 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/webmodules/mutation-observer.git" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.0.3 / 2017-07-25 3 | ================== 4 | 5 | * With the posibility of the MutationEvent being deprecated, check if exists prior to usage (#8) 6 | 7 | 1.0.2 / 2015-07-07 8 | ================== 9 | 10 | * fix delete as a reserved word issue on IE8 (#4, @kaesonho) 11 | 12 | 1.0.1 / 2015-03-10 13 | ================== 14 | 15 | * package: re-add the "component" section 16 | * package: use public git URL 17 | 18 | 1.0.0 / 2014-12-09 19 | ================== 20 | 21 | * Move away from `component.json`; into webmodules organization. 22 | * Add a MutationObserver polyfill for IE9-10. (written by the Polymer contributors) 23 | * Change license from MIT to BSD to match the Polymer polyfill license. 24 | 25 | 0.0.1 / 2013-02-09 26 | ================== 27 | 28 | * Initial release 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Automattic Inc. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of Automattic Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | // Copyright (c) 2012 The Polymer Authors. All rights reserved. 30 | // 31 | // Redistribution and use in source and binary forms, with or without 32 | // modification, are permitted provided that the following conditions are 33 | // met: 34 | // 35 | // * Redistributions of source code must retain the above copyright 36 | // notice, this list of conditions and the following disclaimer. 37 | // * Redistributions in binary form must reproduce the above 38 | // copyright notice, this list of conditions and the following disclaimer 39 | // in the documentation and/or other materials provided with the 40 | // distribution. 41 | // * Neither the name of Google Inc. nor the names of its 42 | // contributors may be used to endorse or promote products derived from 43 | // this software without specific prior written permission. 44 | // 45 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 46 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 47 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 48 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 49 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 50 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 51 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 52 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 53 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 54 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 55 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var MutationObserver = window.MutationObserver 2 | || window.WebKitMutationObserver 3 | || window.MozMutationObserver; 4 | 5 | /* 6 | * Copyright 2012 The Polymer Authors. All rights reserved. 7 | * Use of this source code is goverened by a BSD-style 8 | * license that can be found in the LICENSE file. 9 | */ 10 | 11 | var WeakMap = window.WeakMap; 12 | 13 | if (typeof WeakMap === 'undefined') { 14 | var defineProperty = Object.defineProperty; 15 | var counter = Date.now() % 1e9; 16 | 17 | WeakMap = function() { 18 | this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__'); 19 | }; 20 | 21 | WeakMap.prototype = { 22 | set: function(key, value) { 23 | var entry = key[this.name]; 24 | if (entry && entry[0] === key) 25 | entry[1] = value; 26 | else 27 | defineProperty(key, this.name, {value: [key, value], writable: true}); 28 | return this; 29 | }, 30 | get: function(key) { 31 | var entry; 32 | return (entry = key[this.name]) && entry[0] === key ? 33 | entry[1] : undefined; 34 | }, 35 | 'delete': function(key) { 36 | var entry = key[this.name]; 37 | if (!entry) return false; 38 | var hasValue = entry[0] === key; 39 | entry[0] = entry[1] = undefined; 40 | return hasValue; 41 | }, 42 | has: function(key) { 43 | var entry = key[this.name]; 44 | if (!entry) return false; 45 | return entry[0] === key; 46 | } 47 | }; 48 | } 49 | 50 | var registrationsTable = new WeakMap(); 51 | 52 | // We use setImmediate or postMessage for our future callback. 53 | var setImmediate = window.msSetImmediate; 54 | 55 | // Use post message to emulate setImmediate. 56 | if (!setImmediate) { 57 | var setImmediateQueue = []; 58 | var sentinel = String(Math.random()); 59 | window.addEventListener('message', function(e) { 60 | if (e.data === sentinel) { 61 | var queue = setImmediateQueue; 62 | setImmediateQueue = []; 63 | queue.forEach(function(func) { 64 | func(); 65 | }); 66 | } 67 | }); 68 | setImmediate = function(func) { 69 | setImmediateQueue.push(func); 70 | window.postMessage(sentinel, '*'); 71 | }; 72 | } 73 | 74 | // This is used to ensure that we never schedule 2 callas to setImmediate 75 | var isScheduled = false; 76 | 77 | // Keep track of observers that needs to be notified next time. 78 | var scheduledObservers = []; 79 | 80 | /** 81 | * Schedules |dispatchCallback| to be called in the future. 82 | * @param {MutationObserver} observer 83 | */ 84 | function scheduleCallback(observer) { 85 | scheduledObservers.push(observer); 86 | if (!isScheduled) { 87 | isScheduled = true; 88 | setImmediate(dispatchCallbacks); 89 | } 90 | } 91 | 92 | function wrapIfNeeded(node) { 93 | return window.ShadowDOMPolyfill && 94 | window.ShadowDOMPolyfill.wrapIfNeeded(node) || 95 | node; 96 | } 97 | 98 | function dispatchCallbacks() { 99 | // http://dom.spec.whatwg.org/#mutation-observers 100 | 101 | isScheduled = false; // Used to allow a new setImmediate call above. 102 | 103 | var observers = scheduledObservers; 104 | scheduledObservers = []; 105 | // Sort observers based on their creation UID (incremental). 106 | observers.sort(function(o1, o2) { 107 | return o1.uid_ - o2.uid_; 108 | }); 109 | 110 | var anyNonEmpty = false; 111 | observers.forEach(function(observer) { 112 | 113 | // 2.1, 2.2 114 | var queue = observer.takeRecords(); 115 | // 2.3. Remove all transient registered observers whose observer is mo. 116 | removeTransientObserversFor(observer); 117 | 118 | // 2.4 119 | if (queue.length) { 120 | observer.callback_(queue, observer); 121 | anyNonEmpty = true; 122 | } 123 | }); 124 | 125 | // 3. 126 | if (anyNonEmpty) 127 | dispatchCallbacks(); 128 | } 129 | 130 | function removeTransientObserversFor(observer) { 131 | observer.nodes_.forEach(function(node) { 132 | var registrations = registrationsTable.get(node); 133 | if (!registrations) 134 | return; 135 | registrations.forEach(function(registration) { 136 | if (registration.observer === observer) 137 | registration.removeTransientObservers(); 138 | }); 139 | }); 140 | } 141 | 142 | /** 143 | * This function is used for the "For each registered observer observer (with 144 | * observer's options as options) in target's list of registered observers, 145 | * run these substeps:" and the "For each ancestor ancestor of target, and for 146 | * each registered observer observer (with options options) in ancestor's list 147 | * of registered observers, run these substeps:" part of the algorithms. The 148 | * |options.subtree| is checked to ensure that the callback is called 149 | * correctly. 150 | * 151 | * @param {Node} target 152 | * @param {function(MutationObserverInit):MutationRecord} callback 153 | */ 154 | function forEachAncestorAndObserverEnqueueRecord(target, callback) { 155 | for (var node = target; node; node = node.parentNode) { 156 | var registrations = registrationsTable.get(node); 157 | 158 | if (registrations) { 159 | for (var j = 0; j < registrations.length; j++) { 160 | var registration = registrations[j]; 161 | var options = registration.options; 162 | 163 | // Only target ignores subtree. 164 | if (node !== target && !options.subtree) 165 | continue; 166 | 167 | var record = callback(options); 168 | if (record) 169 | registration.enqueue(record); 170 | } 171 | } 172 | } 173 | } 174 | 175 | var uidCounter = 0; 176 | 177 | /** 178 | * The class that maps to the DOM MutationObserver interface. 179 | * @param {Function} callback. 180 | * @constructor 181 | */ 182 | function JsMutationObserver(callback) { 183 | this.callback_ = callback; 184 | this.nodes_ = []; 185 | this.records_ = []; 186 | this.uid_ = ++uidCounter; 187 | } 188 | 189 | JsMutationObserver.prototype = { 190 | observe: function(target, options) { 191 | target = wrapIfNeeded(target); 192 | 193 | // 1.1 194 | if (!options.childList && !options.attributes && !options.characterData || 195 | 196 | // 1.2 197 | options.attributeOldValue && !options.attributes || 198 | 199 | // 1.3 200 | options.attributeFilter && options.attributeFilter.length && 201 | !options.attributes || 202 | 203 | // 1.4 204 | options.characterDataOldValue && !options.characterData) { 205 | 206 | throw new SyntaxError(); 207 | } 208 | 209 | var registrations = registrationsTable.get(target); 210 | if (!registrations) 211 | registrationsTable.set(target, registrations = []); 212 | 213 | // 2 214 | // If target's list of registered observers already includes a registered 215 | // observer associated with the context object, replace that registered 216 | // observer's options with options. 217 | var registration; 218 | for (var i = 0; i < registrations.length; i++) { 219 | if (registrations[i].observer === this) { 220 | registration = registrations[i]; 221 | registration.removeListeners(); 222 | registration.options = options; 223 | break; 224 | } 225 | } 226 | 227 | // 3. 228 | // Otherwise, add a new registered observer to target's list of registered 229 | // observers with the context object as the observer and options as the 230 | // options, and add target to context object's list of nodes on which it 231 | // is registered. 232 | if (!registration) { 233 | registration = new Registration(this, target, options); 234 | registrations.push(registration); 235 | this.nodes_.push(target); 236 | } 237 | 238 | registration.addListeners(); 239 | }, 240 | 241 | disconnect: function() { 242 | this.nodes_.forEach(function(node) { 243 | var registrations = registrationsTable.get(node); 244 | for (var i = 0; i < registrations.length; i++) { 245 | var registration = registrations[i]; 246 | if (registration.observer === this) { 247 | registration.removeListeners(); 248 | registrations.splice(i, 1); 249 | // Each node can only have one registered observer associated with 250 | // this observer. 251 | break; 252 | } 253 | } 254 | }, this); 255 | this.records_ = []; 256 | }, 257 | 258 | takeRecords: function() { 259 | var copyOfRecords = this.records_; 260 | this.records_ = []; 261 | return copyOfRecords; 262 | } 263 | }; 264 | 265 | /** 266 | * @param {string} type 267 | * @param {Node} target 268 | * @constructor 269 | */ 270 | function MutationRecord(type, target) { 271 | this.type = type; 272 | this.target = target; 273 | this.addedNodes = []; 274 | this.removedNodes = []; 275 | this.previousSibling = null; 276 | this.nextSibling = null; 277 | this.attributeName = null; 278 | this.attributeNamespace = null; 279 | this.oldValue = null; 280 | } 281 | 282 | function copyMutationRecord(original) { 283 | var record = new MutationRecord(original.type, original.target); 284 | record.addedNodes = original.addedNodes.slice(); 285 | record.removedNodes = original.removedNodes.slice(); 286 | record.previousSibling = original.previousSibling; 287 | record.nextSibling = original.nextSibling; 288 | record.attributeName = original.attributeName; 289 | record.attributeNamespace = original.attributeNamespace; 290 | record.oldValue = original.oldValue; 291 | return record; 292 | }; 293 | 294 | // We keep track of the two (possibly one) records used in a single mutation. 295 | var currentRecord, recordWithOldValue; 296 | 297 | /** 298 | * Creates a record without |oldValue| and caches it as |currentRecord| for 299 | * later use. 300 | * @param {string} oldValue 301 | * @return {MutationRecord} 302 | */ 303 | function getRecord(type, target) { 304 | return currentRecord = new MutationRecord(type, target); 305 | } 306 | 307 | /** 308 | * Gets or creates a record with |oldValue| based in the |currentRecord| 309 | * @param {string} oldValue 310 | * @return {MutationRecord} 311 | */ 312 | function getRecordWithOldValue(oldValue) { 313 | if (recordWithOldValue) 314 | return recordWithOldValue; 315 | recordWithOldValue = copyMutationRecord(currentRecord); 316 | recordWithOldValue.oldValue = oldValue; 317 | return recordWithOldValue; 318 | } 319 | 320 | function clearRecords() { 321 | currentRecord = recordWithOldValue = undefined; 322 | } 323 | 324 | /** 325 | * @param {MutationRecord} record 326 | * @return {boolean} Whether the record represents a record from the current 327 | * mutation event. 328 | */ 329 | function recordRepresentsCurrentMutation(record) { 330 | return record === recordWithOldValue || record === currentRecord; 331 | } 332 | 333 | /** 334 | * Selects which record, if any, to replace the last record in the queue. 335 | * This returns |null| if no record should be replaced. 336 | * 337 | * @param {MutationRecord} lastRecord 338 | * @param {MutationRecord} newRecord 339 | * @param {MutationRecord} 340 | */ 341 | function selectRecord(lastRecord, newRecord) { 342 | if (lastRecord === newRecord) 343 | return lastRecord; 344 | 345 | // Check if the the record we are adding represents the same record. If 346 | // so, we keep the one with the oldValue in it. 347 | if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) 348 | return recordWithOldValue; 349 | 350 | return null; 351 | } 352 | 353 | /** 354 | * Class used to represent a registered observer. 355 | * @param {MutationObserver} observer 356 | * @param {Node} target 357 | * @param {MutationObserverInit} options 358 | * @constructor 359 | */ 360 | function Registration(observer, target, options) { 361 | this.observer = observer; 362 | this.target = target; 363 | this.options = options; 364 | this.transientObservedNodes = []; 365 | } 366 | 367 | Registration.prototype = { 368 | enqueue: function(record) { 369 | var records = this.observer.records_; 370 | var length = records.length; 371 | 372 | // There are cases where we replace the last record with the new record. 373 | // For example if the record represents the same mutation we need to use 374 | // the one with the oldValue. If we get same record (this can happen as we 375 | // walk up the tree) we ignore the new record. 376 | if (records.length > 0) { 377 | var lastRecord = records[length - 1]; 378 | var recordToReplaceLast = selectRecord(lastRecord, record); 379 | if (recordToReplaceLast) { 380 | records[length - 1] = recordToReplaceLast; 381 | return; 382 | } 383 | } else { 384 | scheduleCallback(this.observer); 385 | } 386 | 387 | records[length] = record; 388 | }, 389 | 390 | addListeners: function() { 391 | this.addListeners_(this.target); 392 | }, 393 | 394 | addListeners_: function(node) { 395 | var options = this.options; 396 | if (options.attributes) 397 | node.addEventListener('DOMAttrModified', this, true); 398 | 399 | if (options.characterData) 400 | node.addEventListener('DOMCharacterDataModified', this, true); 401 | 402 | if (options.childList) 403 | node.addEventListener('DOMNodeInserted', this, true); 404 | 405 | if (options.childList || options.subtree) 406 | node.addEventListener('DOMNodeRemoved', this, true); 407 | }, 408 | 409 | removeListeners: function() { 410 | this.removeListeners_(this.target); 411 | }, 412 | 413 | removeListeners_: function(node) { 414 | var options = this.options; 415 | if (options.attributes) 416 | node.removeEventListener('DOMAttrModified', this, true); 417 | 418 | if (options.characterData) 419 | node.removeEventListener('DOMCharacterDataModified', this, true); 420 | 421 | if (options.childList) 422 | node.removeEventListener('DOMNodeInserted', this, true); 423 | 424 | if (options.childList || options.subtree) 425 | node.removeEventListener('DOMNodeRemoved', this, true); 426 | }, 427 | 428 | /** 429 | * Adds a transient observer on node. The transient observer gets removed 430 | * next time we deliver the change records. 431 | * @param {Node} node 432 | */ 433 | addTransientObserver: function(node) { 434 | // Don't add transient observers on the target itself. We already have all 435 | // the required listeners set up on the target. 436 | if (node === this.target) 437 | return; 438 | 439 | this.addListeners_(node); 440 | this.transientObservedNodes.push(node); 441 | var registrations = registrationsTable.get(node); 442 | if (!registrations) 443 | registrationsTable.set(node, registrations = []); 444 | 445 | // We know that registrations does not contain this because we already 446 | // checked if node === this.target. 447 | registrations.push(this); 448 | }, 449 | 450 | removeTransientObservers: function() { 451 | var transientObservedNodes = this.transientObservedNodes; 452 | this.transientObservedNodes = []; 453 | 454 | transientObservedNodes.forEach(function(node) { 455 | // Transient observers are never added to the target. 456 | this.removeListeners_(node); 457 | 458 | var registrations = registrationsTable.get(node); 459 | for (var i = 0; i < registrations.length; i++) { 460 | if (registrations[i] === this) { 461 | registrations.splice(i, 1); 462 | // Each node can only have one registered observer associated with 463 | // this observer. 464 | break; 465 | } 466 | } 467 | }, this); 468 | }, 469 | 470 | handleEvent: function(e) { 471 | // Stop propagation since we are managing the propagation manually. 472 | // This means that other mutation events on the page will not work 473 | // correctly but that is by design. 474 | e.stopImmediatePropagation(); 475 | 476 | switch (e.type) { 477 | case 'DOMAttrModified': 478 | // http://dom.spec.whatwg.org/#concept-mo-queue-attributes 479 | 480 | var name = e.attrName; 481 | var namespace = e.relatedNode.namespaceURI; 482 | var target = e.target; 483 | 484 | // 1. 485 | var record = new getRecord('attributes', target); 486 | record.attributeName = name; 487 | record.attributeNamespace = namespace; 488 | 489 | // 2. 490 | var oldValue = null; 491 | if (!(typeof MutationEvent !== 'undefined' && e.attrChange === MutationEvent.ADDITION)) 492 | oldValue = e.prevValue; 493 | 494 | forEachAncestorAndObserverEnqueueRecord(target, function(options) { 495 | // 3.1, 4.2 496 | if (!options.attributes) 497 | return; 498 | 499 | // 3.2, 4.3 500 | if (options.attributeFilter && options.attributeFilter.length && 501 | options.attributeFilter.indexOf(name) === -1 && 502 | options.attributeFilter.indexOf(namespace) === -1) { 503 | return; 504 | } 505 | // 3.3, 4.4 506 | if (options.attributeOldValue) 507 | return getRecordWithOldValue(oldValue); 508 | 509 | // 3.4, 4.5 510 | return record; 511 | }); 512 | 513 | break; 514 | 515 | case 'DOMCharacterDataModified': 516 | // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata 517 | var target = e.target; 518 | 519 | // 1. 520 | var record = getRecord('characterData', target); 521 | 522 | // 2. 523 | var oldValue = e.prevValue; 524 | 525 | 526 | forEachAncestorAndObserverEnqueueRecord(target, function(options) { 527 | // 3.1, 4.2 528 | if (!options.characterData) 529 | return; 530 | 531 | // 3.2, 4.3 532 | if (options.characterDataOldValue) 533 | return getRecordWithOldValue(oldValue); 534 | 535 | // 3.3, 4.4 536 | return record; 537 | }); 538 | 539 | break; 540 | 541 | case 'DOMNodeRemoved': 542 | this.addTransientObserver(e.target); 543 | // Fall through. 544 | case 'DOMNodeInserted': 545 | // http://dom.spec.whatwg.org/#concept-mo-queue-childlist 546 | var target = e.relatedNode; 547 | var changedNode = e.target; 548 | var addedNodes, removedNodes; 549 | if (e.type === 'DOMNodeInserted') { 550 | addedNodes = [changedNode]; 551 | removedNodes = []; 552 | } else { 553 | 554 | addedNodes = []; 555 | removedNodes = [changedNode]; 556 | } 557 | var previousSibling = changedNode.previousSibling; 558 | var nextSibling = changedNode.nextSibling; 559 | 560 | // 1. 561 | var record = getRecord('childList', target); 562 | record.addedNodes = addedNodes; 563 | record.removedNodes = removedNodes; 564 | record.previousSibling = previousSibling; 565 | record.nextSibling = nextSibling; 566 | 567 | forEachAncestorAndObserverEnqueueRecord(target, function(options) { 568 | // 2.1, 3.2 569 | if (!options.childList) 570 | return; 571 | 572 | // 2.2, 3.3 573 | return record; 574 | }); 575 | 576 | } 577 | 578 | clearRecords(); 579 | } 580 | }; 581 | 582 | if (!MutationObserver) { 583 | MutationObserver = JsMutationObserver; 584 | } 585 | 586 | module.exports = MutationObserver; 587 | --------------------------------------------------------------------------------