├── .bowerrc ├── .gitignore ├── AUTHORS ├── LICENSE ├── MutationObserver.js ├── PATENTS ├── README.md ├── bower.json ├── build.json ├── mutation-observers.js ├── package.json └── test ├── attributes.js ├── callback.js ├── characterData.js ├── childList.js ├── index.html ├── mixed.js └── transient.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "../" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Names should be added to this file with this pattern: 2 | # 3 | # For individuals: 4 | # Name 5 | # 6 | # For organizations: 7 | # Organization 8 | # 9 | Google Inc. <*@google.com> 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Polymer Authors. 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 Google 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 | -------------------------------------------------------------------------------- /MutationObserver.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Polymer Authors. All rights reserved. 3 | * Use of this source code is goverened by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | (function(global) { 8 | 9 | var registrationsTable = new WeakMap(); 10 | 11 | // We use setImmediate or postMessage for our future callback. 12 | var setImmediate = window.msSetImmediate; 13 | 14 | // Use post message to emulate setImmediate. 15 | if (!setImmediate) { 16 | var setImmediateQueue = []; 17 | var sentinel = String(Math.random()); 18 | window.addEventListener('message', function(e) { 19 | if (e.data === sentinel) { 20 | var queue = setImmediateQueue; 21 | setImmediateQueue = []; 22 | queue.forEach(function(func) { 23 | func(); 24 | }); 25 | } 26 | }); 27 | setImmediate = function(func) { 28 | setImmediateQueue.push(func); 29 | window.postMessage(sentinel, '*'); 30 | }; 31 | } 32 | 33 | // This is used to ensure that we never schedule 2 callas to setImmediate 34 | var isScheduled = false; 35 | 36 | // Keep track of observers that needs to be notified next time. 37 | var scheduledObservers = []; 38 | 39 | /** 40 | * Schedules |dispatchCallback| to be called in the future. 41 | * @param {MutationObserver} observer 42 | */ 43 | function scheduleCallback(observer) { 44 | scheduledObservers.push(observer); 45 | if (!isScheduled) { 46 | isScheduled = true; 47 | setImmediate(dispatchCallbacks); 48 | } 49 | } 50 | 51 | function wrapIfNeeded(node) { 52 | return window.ShadowDOMPolyfill && 53 | window.ShadowDOMPolyfill.wrapIfNeeded(node) || 54 | node; 55 | } 56 | 57 | function dispatchCallbacks() { 58 | // http://dom.spec.whatwg.org/#mutation-observers 59 | 60 | isScheduled = false; // Used to allow a new setImmediate call above. 61 | 62 | var observers = scheduledObservers; 63 | scheduledObservers = []; 64 | // Sort observers based on their creation UID (incremental). 65 | observers.sort(function(o1, o2) { 66 | return o1.uid_ - o2.uid_; 67 | }); 68 | 69 | var anyNonEmpty = false; 70 | observers.forEach(function(observer) { 71 | 72 | // 2.1, 2.2 73 | var queue = observer.takeRecords(); 74 | // 2.3. Remove all transient registered observers whose observer is mo. 75 | removeTransientObserversFor(observer); 76 | 77 | // 2.4 78 | if (queue.length) { 79 | observer.callback_(queue, observer); 80 | anyNonEmpty = true; 81 | } 82 | }); 83 | 84 | // 3. 85 | if (anyNonEmpty) 86 | dispatchCallbacks(); 87 | } 88 | 89 | function removeTransientObserversFor(observer) { 90 | observer.nodes_.forEach(function(node) { 91 | var registrations = registrationsTable.get(node); 92 | if (!registrations) 93 | return; 94 | registrations.forEach(function(registration) { 95 | if (registration.observer === observer) 96 | registration.removeTransientObservers(); 97 | }); 98 | }); 99 | } 100 | 101 | /** 102 | * This function is used for the "For each registered observer observer (with 103 | * observer's options as options) in target's list of registered observers, 104 | * run these substeps:" and the "For each ancestor ancestor of target, and for 105 | * each registered observer observer (with options options) in ancestor's list 106 | * of registered observers, run these substeps:" part of the algorithms. The 107 | * |options.subtree| is checked to ensure that the callback is called 108 | * correctly. 109 | * 110 | * @param {Node} target 111 | * @param {function(MutationObserverInit):MutationRecord} callback 112 | */ 113 | function forEachAncestorAndObserverEnqueueRecord(target, callback) { 114 | for (var node = target; node; node = node.parentNode) { 115 | var registrations = registrationsTable.get(node); 116 | 117 | if (registrations) { 118 | for (var j = 0; j < registrations.length; j++) { 119 | var registration = registrations[j]; 120 | var options = registration.options; 121 | 122 | // Only target ignores subtree. 123 | if (node !== target && !options.subtree) 124 | continue; 125 | 126 | var record = callback(options); 127 | if (record) 128 | registration.enqueue(record); 129 | } 130 | } 131 | } 132 | } 133 | 134 | var uidCounter = 0; 135 | 136 | /** 137 | * The class that maps to the DOM MutationObserver interface. 138 | * @param {Function} callback. 139 | * @constructor 140 | */ 141 | function JsMutationObserver(callback) { 142 | this.callback_ = callback; 143 | this.nodes_ = []; 144 | this.records_ = []; 145 | this.uid_ = ++uidCounter; 146 | } 147 | 148 | JsMutationObserver.prototype = { 149 | observe: function(target, options) { 150 | target = wrapIfNeeded(target); 151 | 152 | // 1.1 153 | if (!options.childList && !options.attributes && !options.characterData || 154 | 155 | // 1.2 156 | options.attributeOldValue && !options.attributes || 157 | 158 | // 1.3 159 | options.attributeFilter && options.attributeFilter.length && 160 | !options.attributes || 161 | 162 | // 1.4 163 | options.characterDataOldValue && !options.characterData) { 164 | 165 | throw new SyntaxError(); 166 | } 167 | 168 | var registrations = registrationsTable.get(target); 169 | if (!registrations) 170 | registrationsTable.set(target, registrations = []); 171 | 172 | // 2 173 | // If target's list of registered observers already includes a registered 174 | // observer associated with the context object, replace that registered 175 | // observer's options with options. 176 | var registration; 177 | for (var i = 0; i < registrations.length; i++) { 178 | if (registrations[i].observer === this) { 179 | registration = registrations[i]; 180 | registration.removeListeners(); 181 | registration.options = options; 182 | break; 183 | } 184 | } 185 | 186 | // 3. 187 | // Otherwise, add a new registered observer to target's list of registered 188 | // observers with the context object as the observer and options as the 189 | // options, and add target to context object's list of nodes on which it 190 | // is registered. 191 | if (!registration) { 192 | registration = new Registration(this, target, options); 193 | registrations.push(registration); 194 | this.nodes_.push(target); 195 | } 196 | 197 | registration.addListeners(); 198 | }, 199 | 200 | disconnect: function() { 201 | this.nodes_.forEach(function(node) { 202 | var registrations = registrationsTable.get(node); 203 | for (var i = 0; i < registrations.length; i++) { 204 | var registration = registrations[i]; 205 | if (registration.observer === this) { 206 | registration.removeListeners(); 207 | registrations.splice(i, 1); 208 | // Each node can only have one registered observer associated with 209 | // this observer. 210 | break; 211 | } 212 | } 213 | }, this); 214 | this.records_ = []; 215 | }, 216 | 217 | takeRecords: function() { 218 | var copyOfRecords = this.records_; 219 | this.records_ = []; 220 | return copyOfRecords; 221 | } 222 | }; 223 | 224 | /** 225 | * @param {string} type 226 | * @param {Node} target 227 | * @constructor 228 | */ 229 | function MutationRecord(type, target) { 230 | this.type = type; 231 | this.target = target; 232 | this.addedNodes = []; 233 | this.removedNodes = []; 234 | this.previousSibling = null; 235 | this.nextSibling = null; 236 | this.attributeName = null; 237 | this.attributeNamespace = null; 238 | this.oldValue = null; 239 | } 240 | 241 | function copyMutationRecord(original) { 242 | var record = new MutationRecord(original.type, original.target); 243 | record.addedNodes = original.addedNodes.slice(); 244 | record.removedNodes = original.removedNodes.slice(); 245 | record.previousSibling = original.previousSibling; 246 | record.nextSibling = original.nextSibling; 247 | record.attributeName = original.attributeName; 248 | record.attributeNamespace = original.attributeNamespace; 249 | record.oldValue = original.oldValue; 250 | return record; 251 | }; 252 | 253 | // We keep track of the two (possibly one) records used in a single mutation. 254 | var currentRecord, recordWithOldValue; 255 | 256 | /** 257 | * Creates a record without |oldValue| and caches it as |currentRecord| for 258 | * later use. 259 | * @param {string} oldValue 260 | * @return {MutationRecord} 261 | */ 262 | function getRecord(type, target) { 263 | return currentRecord = new MutationRecord(type, target); 264 | } 265 | 266 | /** 267 | * Gets or creates a record with |oldValue| based in the |currentRecord| 268 | * @param {string} oldValue 269 | * @return {MutationRecord} 270 | */ 271 | function getRecordWithOldValue(oldValue) { 272 | if (recordWithOldValue) 273 | return recordWithOldValue; 274 | recordWithOldValue = copyMutationRecord(currentRecord); 275 | recordWithOldValue.oldValue = oldValue; 276 | return recordWithOldValue; 277 | } 278 | 279 | function clearRecords() { 280 | currentRecord = recordWithOldValue = undefined; 281 | } 282 | 283 | /** 284 | * @param {MutationRecord} record 285 | * @return {boolean} Whether the record represents a record from the current 286 | * mutation event. 287 | */ 288 | function recordRepresentsCurrentMutation(record) { 289 | return record === recordWithOldValue || record === currentRecord; 290 | } 291 | 292 | /** 293 | * Selects which record, if any, to replace the last record in the queue. 294 | * This returns |null| if no record should be replaced. 295 | * 296 | * @param {MutationRecord} lastRecord 297 | * @param {MutationRecord} newRecord 298 | * @param {MutationRecord} 299 | */ 300 | function selectRecord(lastRecord, newRecord) { 301 | if (lastRecord === newRecord) 302 | return lastRecord; 303 | 304 | // Check if the the record we are adding represents the same record. If 305 | // so, we keep the one with the oldValue in it. 306 | if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) 307 | return recordWithOldValue; 308 | 309 | return null; 310 | } 311 | 312 | /** 313 | * Class used to represent a registered observer. 314 | * @param {MutationObserver} observer 315 | * @param {Node} target 316 | * @param {MutationObserverInit} options 317 | * @constructor 318 | */ 319 | function Registration(observer, target, options) { 320 | this.observer = observer; 321 | this.target = target; 322 | this.options = options; 323 | this.transientObservedNodes = []; 324 | } 325 | 326 | Registration.prototype = { 327 | enqueue: function(record) { 328 | var records = this.observer.records_; 329 | var length = records.length; 330 | 331 | // There are cases where we replace the last record with the new record. 332 | // For example if the record represents the same mutation we need to use 333 | // the one with the oldValue. If we get same record (this can happen as we 334 | // walk up the tree) we ignore the new record. 335 | if (records.length > 0) { 336 | var lastRecord = records[length - 1]; 337 | var recordToReplaceLast = selectRecord(lastRecord, record); 338 | if (recordToReplaceLast) { 339 | records[length - 1] = recordToReplaceLast; 340 | return; 341 | } 342 | } else { 343 | scheduleCallback(this.observer); 344 | } 345 | 346 | records[length] = record; 347 | }, 348 | 349 | addListeners: function() { 350 | this.addListeners_(this.target); 351 | }, 352 | 353 | addListeners_: function(node) { 354 | var options = this.options; 355 | if (options.attributes) 356 | node.addEventListener('DOMAttrModified', this, true); 357 | 358 | if (options.characterData) 359 | node.addEventListener('DOMCharacterDataModified', this, true); 360 | 361 | if (options.childList) 362 | node.addEventListener('DOMNodeInserted', this, true); 363 | 364 | if (options.childList || options.subtree) 365 | node.addEventListener('DOMNodeRemoved', this, true); 366 | }, 367 | 368 | removeListeners: function() { 369 | this.removeListeners_(this.target); 370 | }, 371 | 372 | removeListeners_: function(node) { 373 | var options = this.options; 374 | if (options.attributes) 375 | node.removeEventListener('DOMAttrModified', this, true); 376 | 377 | if (options.characterData) 378 | node.removeEventListener('DOMCharacterDataModified', this, true); 379 | 380 | if (options.childList) 381 | node.removeEventListener('DOMNodeInserted', this, true); 382 | 383 | if (options.childList || options.subtree) 384 | node.removeEventListener('DOMNodeRemoved', this, true); 385 | }, 386 | 387 | /** 388 | * Adds a transient observer on node. The transient observer gets removed 389 | * next time we deliver the change records. 390 | * @param {Node} node 391 | */ 392 | addTransientObserver: function(node) { 393 | // Don't add transient observers on the target itself. We already have all 394 | // the required listeners set up on the target. 395 | if (node === this.target) 396 | return; 397 | 398 | this.addListeners_(node); 399 | this.transientObservedNodes.push(node); 400 | var registrations = registrationsTable.get(node); 401 | if (!registrations) 402 | registrationsTable.set(node, registrations = []); 403 | 404 | // We know that registrations does not contain this because we already 405 | // checked if node === this.target. 406 | registrations.push(this); 407 | }, 408 | 409 | removeTransientObservers: function() { 410 | var transientObservedNodes = this.transientObservedNodes; 411 | this.transientObservedNodes = []; 412 | 413 | transientObservedNodes.forEach(function(node) { 414 | // Transient observers are never added to the target. 415 | this.removeListeners_(node); 416 | 417 | var registrations = registrationsTable.get(node); 418 | for (var i = 0; i < registrations.length; i++) { 419 | if (registrations[i] === this) { 420 | registrations.splice(i, 1); 421 | // Each node can only have one registered observer associated with 422 | // this observer. 423 | break; 424 | } 425 | } 426 | }, this); 427 | }, 428 | 429 | handleEvent: function(e) { 430 | // Stop propagation since we are managing the propagation manually. 431 | // This means that other mutation events on the page will not work 432 | // correctly but that is by design. 433 | e.stopImmediatePropagation(); 434 | 435 | switch (e.type) { 436 | case 'DOMAttrModified': 437 | // http://dom.spec.whatwg.org/#concept-mo-queue-attributes 438 | 439 | var name = e.attrName; 440 | var namespace = e.relatedNode.namespaceURI; 441 | var target = e.target; 442 | 443 | // 1. 444 | var record = new getRecord('attributes', target); 445 | record.attributeName = name; 446 | record.attributeNamespace = namespace; 447 | 448 | // 2. 449 | var oldValue = 450 | e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; 451 | 452 | forEachAncestorAndObserverEnqueueRecord(target, function(options) { 453 | // 3.1, 4.2 454 | if (!options.attributes) 455 | return; 456 | 457 | // 3.2, 4.3 458 | if (options.attributeFilter && options.attributeFilter.length && 459 | options.attributeFilter.indexOf(name) === -1 && 460 | options.attributeFilter.indexOf(namespace) === -1) { 461 | return; 462 | } 463 | // 3.3, 4.4 464 | if (options.attributeOldValue) 465 | return getRecordWithOldValue(oldValue); 466 | 467 | // 3.4, 4.5 468 | return record; 469 | }); 470 | 471 | break; 472 | 473 | case 'DOMCharacterDataModified': 474 | // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata 475 | var target = e.target; 476 | 477 | // 1. 478 | var record = getRecord('characterData', target); 479 | 480 | // 2. 481 | var oldValue = e.prevValue; 482 | 483 | 484 | forEachAncestorAndObserverEnqueueRecord(target, function(options) { 485 | // 3.1, 4.2 486 | if (!options.characterData) 487 | return; 488 | 489 | // 3.2, 4.3 490 | if (options.characterDataOldValue) 491 | return getRecordWithOldValue(oldValue); 492 | 493 | // 3.3, 4.4 494 | return record; 495 | }); 496 | 497 | break; 498 | 499 | case 'DOMNodeRemoved': 500 | this.addTransientObserver(e.target); 501 | // Fall through. 502 | case 'DOMNodeInserted': 503 | // http://dom.spec.whatwg.org/#concept-mo-queue-childlist 504 | var target = e.relatedNode; 505 | var changedNode = e.target; 506 | var addedNodes, removedNodes; 507 | if (e.type === 'DOMNodeInserted') { 508 | addedNodes = [changedNode]; 509 | removedNodes = []; 510 | } else { 511 | 512 | addedNodes = []; 513 | removedNodes = [changedNode]; 514 | } 515 | var previousSibling = changedNode.previousSibling; 516 | var nextSibling = changedNode.nextSibling; 517 | 518 | // 1. 519 | var record = getRecord('childList', target); 520 | record.addedNodes = addedNodes; 521 | record.removedNodes = removedNodes; 522 | record.previousSibling = previousSibling; 523 | record.nextSibling = nextSibling; 524 | 525 | forEachAncestorAndObserverEnqueueRecord(target, function(options) { 526 | // 2.1, 3.2 527 | if (!options.childList) 528 | return; 529 | 530 | // 2.2, 3.3 531 | return record; 532 | }); 533 | 534 | } 535 | 536 | clearRecords(); 537 | } 538 | }; 539 | 540 | global.JsMutationObserver = JsMutationObserver; 541 | 542 | if (!global.MutationObserver) 543 | global.MutationObserver = JsMutationObserver; 544 | 545 | 546 | })(this); 547 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Polymer project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Polymer, where such license applies only to those 11 | patent claims, both currently owned or controlled by Google and acquired 12 | in the future, licensable by Google that are necessarily infringed by 13 | this implementation of Polymer. This grant does not include claims 14 | that would be infringed only as a consequence of further modification of 15 | this implementation. If you or your agent or exclusive licensee 16 | institute or order or agree to the institution of patent litigation 17 | against any entity (including a cross-claim or counterclaim in a 18 | lawsuit) alleging that this implementation of Polymer or any code 19 | incorporated within this implementation of Polymer constitutes 20 | direct or contributory patent infringement, or inducement of patent 21 | infringement, then any patent rights granted to you under this License 22 | for this implementation of Polymer shall terminate as of the date 23 | such litigation is filed. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DEPRECATED 2 | This repo has moved to https://github.com/Polymer/webcomponentsjs 3 | 4 | 5 | MutationObservers 6 | ================= 7 | 8 | Mutation Observers Polyfill 9 | 10 | This uses MutationEvents to implement MutationObserves so you need at least IE9. 11 | 12 | IE11, Firefox, Chrome and Opera all support MutationObservers natively. 13 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MutationObservers", 3 | "homepage": "https://github.com/Polymer/MutationObservers", 4 | "authors": [ 5 | "The Polymer Authors" 6 | ], 7 | "description": "Mutation Observers Polyfill", 8 | "main": "MutationObserver.js", 9 | "keywords": [ 10 | "mutationobservers" 11 | ], 12 | "dependencies": { 13 | "WeakMap": "Polymer/WeakMap#master" 14 | }, 15 | "license": "BSD", 16 | "private": true, 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /build.json: -------------------------------------------------------------------------------- 1 | [ 2 | "../WeakMap/weakmap.js", 3 | "MutationObserver.js" 4 | ] 5 | -------------------------------------------------------------------------------- /mutation-observers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | (function() { 8 | 9 | var thisFile = 'mutation-observers.js'; 10 | var scopeName = 'MutationObservers'; 11 | var modules = [ 12 | '../WeakMap/weakmap.js', 13 | 'MutationObserver.js', 14 | ]; 15 | 16 | // export 17 | 18 | window[scopeName] = { 19 | entryPointName: thisFile, 20 | modules: modules 21 | }; 22 | 23 | // bootstrap 24 | 25 | var script = document.querySelector('script[src*="' + thisFile + '"]'); 26 | var src = script.attributes.src.value; 27 | var basePath = src.slice(0, src.indexOf(thisFile)); 28 | 29 | if (!window.PolymerLoader) { 30 | var path = basePath + '../tools/loader/loader.js'; 31 | document.write(''); 32 | } 33 | document.write(''); 34 | 35 | })(); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MutationObservers", 3 | "version": "0.0.0", 4 | "description": "ERROR: No README.md file found!", 5 | "main": "MutationObserver.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/Polymer/MutationObservers.git" 15 | }, 16 | "author": "", 17 | "license": "BSD", 18 | "gitHead": "fffb15e43b267034671cb01521df35b7fa063b7a", 19 | "devDependencies": { 20 | "chai": "*", 21 | "mocha": "*" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/attributes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | suite('JsMutationObserver attributes', function() { 8 | 9 | test('attr', function() { 10 | var div = document.createElement('div'); 11 | var observer = new JsMutationObserver(function() {}); 12 | observer.observe(div, { 13 | attributes: true 14 | }); 15 | div.setAttribute('a', 'A'); 16 | div.setAttribute('a', 'B'); 17 | 18 | var records = observer.takeRecords(); 19 | assert.strictEqual(records.length, 2); 20 | 21 | expectRecord(records[0], { 22 | type: 'attributes', 23 | target: div, 24 | attributeName: 'a', 25 | attributeNamespace: null 26 | }); 27 | expectRecord(records[1], { 28 | type: 'attributes', 29 | target: div, 30 | attributeName: 'a', 31 | attributeNamespace: null 32 | }); 33 | }); 34 | 35 | test('attr with oldValue', function() { 36 | var div = document.createElement('div'); 37 | var observer = new JsMutationObserver(function() {}); 38 | observer.observe(div, { 39 | attributes: true, 40 | attributeOldValue: true 41 | }); 42 | div.setAttribute('a', 'A'); 43 | div.setAttribute('a', 'B'); 44 | 45 | var records = observer.takeRecords(); 46 | assert.strictEqual(records.length, 2); 47 | 48 | expectRecord(records[0], { 49 | type: 'attributes', 50 | target: div, 51 | attributeName: 'a', 52 | attributeNamespace: null, 53 | oldValue: null 54 | }); 55 | expectRecord(records[1], { 56 | type: 'attributes', 57 | target: div, 58 | attributeName: 'a', 59 | attributeNamespace: null, 60 | oldValue: 'A' 61 | }); 62 | }); 63 | 64 | test('attr change in subtree should not genereate a record', function() { 65 | var div = document.createElement('div'); 66 | var child = div.appendChild(document.createElement('div')); 67 | 68 | var observer = new JsMutationObserver(function() {}); 69 | observer.observe(div, { 70 | attributes: true 71 | }); 72 | child.setAttribute('a', 'A'); 73 | child.setAttribute('a', 'B'); 74 | 75 | var records = observer.takeRecords(); 76 | assert.strictEqual(records.length, 0); 77 | }); 78 | 79 | test('attr change, subtree', function() { 80 | var div = document.createElement('div'); 81 | var child = div.appendChild(document.createElement('div')); 82 | 83 | var observer = new JsMutationObserver(function() {}); 84 | observer.observe(div, { 85 | attributes: true, 86 | subtree: true 87 | }); 88 | child.setAttribute('a', 'A'); 89 | child.setAttribute('a', 'B'); 90 | 91 | var records = observer.takeRecords(); 92 | assert.strictEqual(records.length, 2); 93 | 94 | expectRecord(records[0], { 95 | type: 'attributes', 96 | target: child, 97 | attributeName: 'a' 98 | }); 99 | expectRecord(records[1], { 100 | type: 'attributes', 101 | target: child, 102 | attributeName: 'a' 103 | }); 104 | }); 105 | 106 | 107 | test('multiple observers on same target', function() { 108 | var div = document.createElement('div'); 109 | var observer1 = new JsMutationObserver(function() {}); 110 | observer1.observe(div, { 111 | attributes: true, 112 | attributeOldValue: true 113 | }); 114 | var observer2 = new JsMutationObserver(function() {}); 115 | observer2.observe(div, { 116 | attributes: true, 117 | attributeFilter: ['b'] 118 | }); 119 | 120 | div.setAttribute('a', 'A'); 121 | div.setAttribute('a', 'A2'); 122 | div.setAttribute('b', 'B'); 123 | 124 | var records = observer1.takeRecords(); 125 | assert.strictEqual(records.length, 3); 126 | 127 | expectRecord(records[0], { 128 | type: 'attributes', 129 | target: div, 130 | attributeName: 'a' 131 | }); 132 | expectRecord(records[1], { 133 | type: 'attributes', 134 | target: div, 135 | attributeName: 'a', 136 | oldValue: 'A' 137 | }); 138 | expectRecord(records[2], { 139 | type: 'attributes', 140 | target: div, 141 | attributeName: 'b' 142 | }); 143 | 144 | records = observer2.takeRecords(); 145 | assert.strictEqual(records.length, 1); 146 | 147 | expectRecord(records[0], { 148 | type: 'attributes', 149 | target: div, 150 | attributeName: 'b' 151 | }); 152 | }); 153 | 154 | test('observer observes on different target', function() { 155 | var div = document.createElement('div'); 156 | var child = div.appendChild(document.createElement('div')); 157 | 158 | var observer = new JsMutationObserver(function() {}); 159 | observer.observe(child, { 160 | attributes: true 161 | }); 162 | observer.observe(div, { 163 | attributes: true, 164 | subtree: true, 165 | attributeOldValue: true 166 | }); 167 | 168 | child.setAttribute('a', 'A'); 169 | child.setAttribute('a', 'A2'); 170 | child.setAttribute('b', 'B'); 171 | 172 | var records = observer.takeRecords(); 173 | assert.strictEqual(records.length, 3); 174 | 175 | expectRecord(records[0], { 176 | type: 'attributes', 177 | target: child, 178 | attributeName: 'a' 179 | }); 180 | expectRecord(records[1], { 181 | type: 'attributes', 182 | target: child, 183 | attributeName: 'a', 184 | oldValue: 'A' 185 | }); 186 | expectRecord(records[2], { 187 | type: 'attributes', 188 | target: child, 189 | attributeName: 'b' 190 | }); 191 | }); 192 | 193 | test('observing on the same node should update the options', function() { 194 | var div = document.createElement('div'); 195 | var observer = new JsMutationObserver(function() {}); 196 | observer.observe(div, { 197 | attributes: true, 198 | attributeFilter: ['a'] 199 | }); 200 | observer.observe(div, { 201 | attributes: true, 202 | attributeFilter: ['b'] 203 | }); 204 | 205 | div.setAttribute('a', 'A'); 206 | div.setAttribute('b', 'B'); 207 | 208 | var records = observer.takeRecords(); 209 | assert.strictEqual(records.length, 1); 210 | 211 | expectRecord(records[0], { 212 | type: 'attributes', 213 | target: div, 214 | attributeName: 'b' 215 | }); 216 | }); 217 | 218 | test('disconnect should stop all events and empty the records', function() { 219 | var div = document.createElement('div'); 220 | var observer = new JsMutationObserver(function() {}); 221 | observer.observe(div, { 222 | attributes: true, 223 | }); 224 | 225 | div.setAttribute('a', 'A'); 226 | 227 | observer.disconnect(); 228 | var records = observer.takeRecords(); 229 | assert.strictEqual(records.length, 0); 230 | 231 | div.setAttribute('b', 'B'); 232 | 233 | records = observer.takeRecords(); 234 | assert.strictEqual(records.length, 0); 235 | }); 236 | 237 | test('disconnect should not affect other observers', function() { 238 | var div = document.createElement('div'); 239 | var observer1 = new JsMutationObserver(function() {}); 240 | observer1.observe(div, { 241 | attributes: true, 242 | }); 243 | var observer2 = new JsMutationObserver(function() {}); 244 | observer2.observe(div, { 245 | attributes: true, 246 | }); 247 | 248 | div.setAttribute('a', 'A'); 249 | 250 | observer1.disconnect(); 251 | var records1 = observer1.takeRecords(); 252 | assert.strictEqual(records1.length, 0); 253 | 254 | var records2 = observer2.takeRecords(); 255 | assert.strictEqual(records2.length, 1); 256 | expectRecord(records2[0], { 257 | type: 'attributes', 258 | target: div, 259 | attributeName: 'a' 260 | }); 261 | 262 | div.setAttribute('b', 'B'); 263 | 264 | records1 = observer1.takeRecords(); 265 | assert.strictEqual(records1.length, 0); 266 | 267 | records2 = observer2.takeRecords(); 268 | assert.strictEqual(records2.length, 1); 269 | expectRecord(records2[0], { 270 | type: 'attributes', 271 | target: div, 272 | attributeName: 'b' 273 | }); 274 | }); 275 | 276 | }); -------------------------------------------------------------------------------- /test/callback.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | suite('JsMutationObserver callback', function() { 8 | 9 | test('One observer, two attribute changes', function(cont) { 10 | var div = document.createElement('div'); 11 | var observer = new JsMutationObserver(function(records) { 12 | assert.strictEqual(records.length, 2); 13 | 14 | expectRecord(records[0], { 15 | type: 'attributes', 16 | target: div, 17 | attributeName: 'a', 18 | attributeNamespace: null 19 | }); 20 | expectRecord(records[1], { 21 | type: 'attributes', 22 | target: div, 23 | attributeName: 'a', 24 | attributeNamespace: null 25 | }); 26 | 27 | cont(); 28 | }); 29 | 30 | observer.observe(div, { 31 | attributes: true 32 | }); 33 | 34 | div.setAttribute('a', 'A'); 35 | div.setAttribute('a', 'B'); 36 | }); 37 | 38 | test('nested changes', function(cont) { 39 | var div = document.createElement('div'); 40 | var i = 0; 41 | var observer = new JsMutationObserver(function(records) { 42 | assert.strictEqual(records.length, 1); 43 | 44 | if (i === 0) { 45 | expectRecord(records[0], { 46 | type: 'attributes', 47 | target: div, 48 | attributeName: 'a', 49 | attributeNamespace: null 50 | }); 51 | div.setAttribute('b', 'B'); 52 | i++; 53 | } else { 54 | expectRecord(records[0], { 55 | type: 'attributes', 56 | target: div, 57 | attributeName: 'b', 58 | attributeNamespace: null 59 | }); 60 | 61 | cont(); 62 | } 63 | }); 64 | 65 | observer.observe(div, { 66 | attributes: true 67 | }); 68 | 69 | div.setAttribute('a', 'A'); 70 | }); 71 | 72 | }); -------------------------------------------------------------------------------- /test/characterData.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | suite('JsMutationObserver characterData', function() { 8 | 9 | var testDiv; 10 | 11 | setup(function() { 12 | testDiv = document.body.appendChild(document.createElement('div')); 13 | }); 14 | 15 | teardown(function() { 16 | document.body.removeChild(testDiv); 17 | }); 18 | 19 | test('characterData', function() { 20 | var text = document.createTextNode('abc'); 21 | var observer = new JsMutationObserver(function() {}); 22 | observer.observe(text, { 23 | characterData: true 24 | }); 25 | text.data = 'def'; 26 | text.data = 'ghi'; 27 | 28 | var records = observer.takeRecords(); 29 | assert.strictEqual(records.length, 2); 30 | 31 | expectRecord(records[0], { 32 | type: 'characterData', 33 | target: text 34 | }); 35 | expectRecord(records[1], { 36 | type: 'characterData', 37 | target: text 38 | }); 39 | }); 40 | 41 | test('characterData with old value', function() { 42 | var text = testDiv.appendChild(document.createTextNode('abc')); 43 | var observer = new JsMutationObserver(function() {}); 44 | observer.observe(text, { 45 | characterData: true, 46 | characterDataOldValue: true 47 | }); 48 | text.data = 'def'; 49 | text.data = 'ghi'; 50 | 51 | var records = observer.takeRecords(); 52 | assert.strictEqual(records.length, 2); 53 | 54 | expectRecord(records[0], { 55 | type: 'characterData', 56 | target: text, 57 | oldValue: 'abc' 58 | }); 59 | expectRecord(records[1], { 60 | type: 'characterData', 61 | target: text, 62 | oldValue: 'def' 63 | }); 64 | }); 65 | 66 | test('characterData change in subtree should not generate a record', 67 | function() { 68 | var div = document.createElement('div'); 69 | var text = div.appendChild(document.createTextNode('abc')); 70 | var observer = new JsMutationObserver(function() {}); 71 | observer.observe(div, { 72 | characterData: true 73 | }); 74 | text.data = 'def'; 75 | text.data = 'ghi'; 76 | 77 | var records = observer.takeRecords(); 78 | assert.strictEqual(records.length, 0); 79 | }); 80 | 81 | test('characterData change in subtree', 82 | function() { 83 | var div = document.createElement('div'); 84 | var text = div.appendChild(document.createTextNode('abc')); 85 | var observer = new JsMutationObserver(function() {}); 86 | observer.observe(div, { 87 | characterData: true, 88 | subtree: true 89 | }); 90 | text.data = 'def'; 91 | text.data = 'ghi'; 92 | 93 | var records = observer.takeRecords(); 94 | assert.strictEqual(records.length, 2); 95 | 96 | expectRecord(records[0], { 97 | type: 'characterData', 98 | target: text 99 | }); 100 | expectRecord(records[1], { 101 | type: 'characterData', 102 | target: text 103 | }); 104 | }); 105 | 106 | }); -------------------------------------------------------------------------------- /test/childList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | suite('JsMutationObserver childList', function() { 8 | 9 | var testDiv; 10 | 11 | teardown(function() { 12 | document.body.removeChild(testDiv); 13 | }); 14 | 15 | var addedNodes, removedNodes; 16 | 17 | setup(function() { 18 | testDiv = document.body.appendChild(document.createElement('div')); 19 | addedNodes = []; 20 | removedNodes = []; 21 | }); 22 | 23 | function mergeRecords(records) { 24 | records.forEach(function(record) { 25 | if (record.addedNodes) 26 | addedNodes.push.apply(addedNodes, record.addedNodes); 27 | if (record.removedNodes) 28 | removedNodes.push.apply(removedNodes, record.removedNodes); 29 | }); 30 | } 31 | 32 | function assertAll(records, expectedProperties) { 33 | records.forEach(function(record) { 34 | for (var propertyName in expectedProperties) { 35 | assert.strictEqual(record[propertyName], expectedProperties[propertyName]); 36 | } 37 | }); 38 | } 39 | 40 | test('appendChild', function() { 41 | var div = document.createElement('div'); 42 | var observer = new JsMutationObserver(function() {}); 43 | observer.observe(div, { 44 | childList: true 45 | }); 46 | var a = document.createElement('a'); 47 | var b = document.createElement('b'); 48 | 49 | div.appendChild(a); 50 | div.appendChild(b); 51 | 52 | var records = observer.takeRecords(); 53 | assert.strictEqual(records.length, 2); 54 | 55 | expectRecord(records[0], { 56 | type: 'childList', 57 | target: div, 58 | addedNodes: [a] 59 | }); 60 | 61 | expectRecord(records[1], { 62 | type: 'childList', 63 | target: div, 64 | addedNodes: [b], 65 | previousSibling: a 66 | }); 67 | }); 68 | 69 | test('insertBefore', function() { 70 | var div = document.createElement('div'); 71 | var a = document.createElement('a'); 72 | var b = document.createElement('b'); 73 | var c = document.createElement('c'); 74 | div.appendChild(a); 75 | 76 | var observer = new JsMutationObserver(function() {}); 77 | observer.observe(div, { 78 | childList: true 79 | }); 80 | 81 | div.insertBefore(b, a); 82 | div.insertBefore(c, a); 83 | 84 | var records = observer.takeRecords(); 85 | assert.strictEqual(records.length, 2); 86 | 87 | expectRecord(records[0], { 88 | type: 'childList', 89 | target: div, 90 | addedNodes: [b], 91 | nextSibling: a 92 | }); 93 | 94 | expectRecord(records[1], { 95 | type: 'childList', 96 | target: div, 97 | addedNodes: [c], 98 | nextSibling: a, 99 | previousSibling: b 100 | }); 101 | }); 102 | 103 | 104 | test('removeChild', function() { 105 | var div = testDiv.appendChild(document.createElement('div')); 106 | var a = div.appendChild(document.createElement('a')); 107 | var b = div.appendChild(document.createElement('b')); 108 | var c = div.appendChild(document.createElement('c')); 109 | 110 | var observer = new JsMutationObserver(function() {}); 111 | observer.observe(div, { 112 | childList: true 113 | }); 114 | 115 | div.removeChild(b); 116 | div.removeChild(a); 117 | 118 | var records = observer.takeRecords(); 119 | assert.strictEqual(records.length, 2); 120 | 121 | expectRecord(records[0], { 122 | type: 'childList', 123 | target: div, 124 | removedNodes: [b], 125 | nextSibling: c, 126 | previousSibling: a 127 | }); 128 | 129 | expectRecord(records[1], { 130 | type: 'childList', 131 | target: div, 132 | removedNodes: [a], 133 | nextSibling: c 134 | }); 135 | }); 136 | 137 | test('Direct children', function() { 138 | var div = testDiv.appendChild(document.createElement('div')); 139 | var observer = new JsMutationObserver(function() {}); 140 | observer.observe(div, { 141 | childList: true 142 | }); 143 | var a = document.createElement('a'); 144 | var b = document.createElement('b'); 145 | 146 | div.appendChild(a); 147 | div.insertBefore(b, a); 148 | div.removeChild(b); 149 | 150 | var records = observer.takeRecords(); 151 | assert.strictEqual(records.length, 3); 152 | 153 | expectRecord(records[0], { 154 | type: 'childList', 155 | target: div, 156 | addedNodes: [a] 157 | }); 158 | 159 | expectRecord(records[1], { 160 | type: 'childList', 161 | target: div, 162 | nextSibling: a, 163 | addedNodes: [b] 164 | }); 165 | 166 | expectRecord(records[2], { 167 | type: 'childList', 168 | target: div, 169 | nextSibling: a, 170 | removedNodes: [b] 171 | }); 172 | }); 173 | 174 | test('subtree', function() { 175 | var div = document.createElement('div'); 176 | var child = div.appendChild(document.createElement('div')); 177 | var observer = new JsMutationObserver(function() {}); 178 | observer.observe(child, { 179 | childList: true 180 | }); 181 | var a = document.createTextNode('a'); 182 | var b = document.createTextNode('b'); 183 | 184 | child.appendChild(a); 185 | child.insertBefore(b, a); 186 | child.removeChild(b); 187 | 188 | var records = observer.takeRecords(); 189 | assert.strictEqual(records.length, 3); 190 | 191 | expectRecord(records[0], { 192 | type: 'childList', 193 | target: child, 194 | addedNodes: [a] 195 | }); 196 | 197 | expectRecord(records[1], { 198 | type: 'childList', 199 | target: child, 200 | nextSibling: a, 201 | addedNodes: [b] 202 | }); 203 | 204 | expectRecord(records[2], { 205 | type: 'childList', 206 | target: child, 207 | nextSibling: a, 208 | removedNodes: [b] 209 | }); 210 | }); 211 | 212 | test('both direct and subtree', function() { 213 | var div = document.createElement('div'); 214 | var child = div.appendChild(document.createElement('div')); 215 | var observer = new JsMutationObserver(function() {}); 216 | observer.observe(div, { 217 | childList: true, 218 | subtree: true 219 | }); 220 | observer.observe(child, { 221 | childList: true 222 | }); 223 | 224 | var a = document.createTextNode('a'); 225 | var b = document.createTextNode('b'); 226 | 227 | child.appendChild(a); 228 | div.appendChild(b); 229 | 230 | var records = observer.takeRecords(); 231 | assert.strictEqual(records.length, 2); 232 | 233 | expectRecord(records[0], { 234 | type: 'childList', 235 | target: child, 236 | addedNodes: [a] 237 | }); 238 | 239 | expectRecord(records[1], { 240 | type: 'childList', 241 | target: div, 242 | addedNodes: [b], 243 | previousSibling: child 244 | }); 245 | }); 246 | 247 | test('Append multiple at once at the end', function() { 248 | var div = testDiv.appendChild(document.createElement('div')); 249 | var a = div.appendChild(document.createTextNode('a')); 250 | 251 | var observer = new JsMutationObserver(function() {}); 252 | observer.observe(div, { 253 | childList: true 254 | }); 255 | 256 | var df = document.createDocumentFragment(); 257 | var b = df.appendChild(document.createTextNode('b')); 258 | var c = df.appendChild(document.createTextNode('c')); 259 | var d = df.appendChild(document.createTextNode('d')); 260 | 261 | div.appendChild(df); 262 | 263 | var records = observer.takeRecords(); 264 | mergeRecords(records); 265 | 266 | assertArrayEqual(addedNodes, [b, c, d]); 267 | assertArrayEqual(removedNodes, []); 268 | assertAll(records, { 269 | type: 'childList', 270 | target: div 271 | }); 272 | }); 273 | 274 | test('Append multiple at once at the front', function() { 275 | var div = testDiv.appendChild(document.createElement('div')); 276 | var a = div.appendChild(document.createTextNode('a')); 277 | 278 | var observer = new JsMutationObserver(function() {}); 279 | observer.observe(div, { 280 | childList: true 281 | }); 282 | 283 | var df = document.createDocumentFragment(); 284 | var b = df.appendChild(document.createTextNode('b')); 285 | var c = df.appendChild(document.createTextNode('c')); 286 | var d = df.appendChild(document.createTextNode('d')); 287 | 288 | div.insertBefore(df, a); 289 | 290 | var records = observer.takeRecords(); 291 | mergeRecords(records); 292 | 293 | assertArrayEqual(addedNodes, [b, c, d]); 294 | assertArrayEqual(removedNodes, []); 295 | assertAll(records, { 296 | type: 'childList', 297 | target: div 298 | }); 299 | }); 300 | 301 | test('Append multiple at once in the middle', function() { 302 | var div = testDiv.appendChild(document.createElement('div')); 303 | var a = div.appendChild(document.createTextNode('a')); 304 | var b = div.appendChild(document.createTextNode('b')); 305 | 306 | var observer = new JsMutationObserver(function() {}); 307 | observer.observe(div, { 308 | childList: true 309 | }); 310 | 311 | var df = document.createDocumentFragment(); 312 | var c = df.appendChild(document.createTextNode('c')); 313 | var d = df.appendChild(document.createTextNode('d')); 314 | 315 | div.insertBefore(df, b); 316 | 317 | var records = observer.takeRecords(); 318 | mergeRecords(records); 319 | 320 | assertArrayEqual(addedNodes, [c, d]); 321 | assertArrayEqual(removedNodes, []); 322 | assertAll(records, { 323 | type: 'childList', 324 | target: div 325 | }); 326 | }); 327 | 328 | test('Remove all children', function() { 329 | var div = testDiv.appendChild(document.createElement('div')); 330 | var a = div.appendChild(document.createTextNode('a')); 331 | var b = div.appendChild(document.createTextNode('b')); 332 | var c = div.appendChild(document.createTextNode('c')); 333 | 334 | var observer = new JsMutationObserver(function() {}); 335 | observer.observe(div, { 336 | childList: true 337 | }); 338 | 339 | div.innerHTML = ''; 340 | 341 | var records = observer.takeRecords(); 342 | mergeRecords(records); 343 | 344 | assertArrayEqual(addedNodes, []); 345 | assertArrayEqual(removedNodes, [a, b, c]); 346 | assertAll(records, { 347 | type: 'childList', 348 | target: div 349 | }); 350 | }); 351 | 352 | test('Replace all children using innerHTML', function() { 353 | var div = testDiv.appendChild(document.createElement('div')); 354 | var a = div.appendChild(document.createTextNode('a')); 355 | var b = div.appendChild(document.createTextNode('b')); 356 | 357 | var observer = new JsMutationObserver(function() {}); 358 | observer.observe(div, { 359 | childList: true 360 | }); 361 | 362 | div.innerHTML = ''; 363 | var c = div.firstChild; 364 | var d = div.lastChild; 365 | 366 | var records = observer.takeRecords(); 367 | mergeRecords(records); 368 | 369 | assertArrayEqual(addedNodes, [c, d]); 370 | assertArrayEqual(removedNodes, [a, b]); 371 | assertAll(records, { 372 | type: 'childList', 373 | target: div 374 | }); 375 | }); 376 | 377 | }); -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
78 | 81 | -------------------------------------------------------------------------------- /test/mixed.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | suite('JsMutationObserver mixed types', function() { 8 | 9 | test('attr and characterData', function() { 10 | var div = document.createElement('div'); 11 | var text = div.appendChild(document.createTextNode('text')); 12 | var observer = new JsMutationObserver(function() {}); 13 | observer.observe(div, { 14 | attributes: true, 15 | characterData: true, 16 | subtree: true 17 | }); 18 | div.setAttribute('a', 'A'); 19 | div.firstChild.data = 'changed'; 20 | 21 | var records = observer.takeRecords(); 22 | assert.strictEqual(records.length, 2); 23 | 24 | expectRecord(records[0], { 25 | type: 'attributes', 26 | target: div, 27 | attributeName: 'a', 28 | attributeNamespace: null 29 | }); 30 | expectRecord(records[1], { 31 | type: 'characterData', 32 | target: div.firstChild 33 | }); 34 | }); 35 | 36 | }); -------------------------------------------------------------------------------- /test/transient.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | suite('JsMutationObserver transient', function() { 8 | 9 | var testDiv; 10 | 11 | setup(function() { 12 | testDiv = document.body.appendChild(document.createElement('div')); 13 | }); 14 | 15 | teardown(function() { 16 | document.body.removeChild(testDiv); 17 | }); 18 | 19 | test('attr', function() { 20 | var div = testDiv.appendChild(document.createElement('div')); 21 | var child = div.appendChild(document.createElement('div')); 22 | var observer = new JsMutationObserver(function() {}); 23 | observer.observe(div, { 24 | attributes: true, 25 | subtree: true 26 | }); 27 | div.removeChild(child); 28 | child.setAttribute('a', 'A'); 29 | 30 | var records = observer.takeRecords(); 31 | assert.strictEqual(records.length, 1); 32 | 33 | expectRecord(records[0], { 34 | type: 'attributes', 35 | target: child, 36 | attributeName: 'a', 37 | attributeNamespace: null 38 | }); 39 | 40 | child.setAttribute('b', 'B'); 41 | 42 | records = observer.takeRecords(); 43 | assert.strictEqual(records.length, 1); 44 | 45 | expectRecord(records[0], { 46 | type: 'attributes', 47 | target: child, 48 | attributeName: 'b', 49 | attributeNamespace: null 50 | }); 51 | }); 52 | 53 | test('attr callback', function(cont) { 54 | var div = testDiv.appendChild(document.createElement('div')); 55 | var child = div.appendChild(document.createElement('div')); 56 | var i = 0; 57 | var observer = new JsMutationObserver(function(records) { 58 | i++; 59 | if (i > 1) 60 | expect().fail(); 61 | 62 | assert.strictEqual(records.length, 1); 63 | 64 | expectRecord(records[0], { 65 | type: 'attributes', 66 | target: child, 67 | attributeName: 'a', 68 | attributeNamespace: null 69 | }); 70 | 71 | // The transient observers are removed before the callback is called. 72 | child.setAttribute('b', 'B'); 73 | records = observer.takeRecords(); 74 | assert.strictEqual(records.length, 0); 75 | 76 | cont(); 77 | }); 78 | 79 | observer.observe(div, { 80 | attributes: true, 81 | subtree: true 82 | }); 83 | 84 | div.removeChild(child); 85 | child.setAttribute('a', 'A'); 86 | }); 87 | 88 | test('attr, make sure transient gets removed', function(cont) { 89 | var div = testDiv.appendChild(document.createElement('div')); 90 | var child = div.appendChild(document.createElement('div')); 91 | var i = 0; 92 | var observer = new JsMutationObserver(function(records) { 93 | i++; 94 | if (i > 1) 95 | expect().fail(); 96 | 97 | assert.strictEqual(records.length, 1); 98 | 99 | expectRecord(records[0], { 100 | type: 'attributes', 101 | target: child, 102 | attributeName: 'a', 103 | attributeNamespace: null 104 | }); 105 | 106 | step2(); 107 | }); 108 | 109 | observer.observe(div, { 110 | attributes: true, 111 | subtree: true 112 | }); 113 | 114 | div.removeChild(child); 115 | child.setAttribute('a', 'A'); 116 | 117 | function step2() { 118 | var div2 = document.createElement('div'); 119 | var observer2 = new JsMutationObserver(function(records) { 120 | i++; 121 | if (i > 2) 122 | expect().fail(); 123 | 124 | assert.strictEqual(records.length, 1); 125 | 126 | expectRecord(records[0], { 127 | type: 'attributes', 128 | target: child, 129 | attributeName: 'b', 130 | attributeNamespace: null 131 | }); 132 | 133 | cont(); 134 | }); 135 | 136 | observer2.observe(div2, { 137 | attributes: true, 138 | subtree: true, 139 | }); 140 | 141 | div2.appendChild(child); 142 | child.setAttribute('b', 'B'); 143 | } 144 | }); 145 | 146 | test('characterData', function() { 147 | var div = document.createElement('div'); 148 | var child = div.appendChild(document.createTextNode('text')); 149 | var observer = new JsMutationObserver(function() {}); 150 | observer.observe(div, { 151 | characterData: true, 152 | subtree: true 153 | }); 154 | div.removeChild(child); 155 | child.data = 'changed'; 156 | 157 | var records = observer.takeRecords(); 158 | assert.strictEqual(records.length, 1); 159 | 160 | expectRecord(records[0], { 161 | type: 'characterData', 162 | target: child 163 | }); 164 | 165 | child.data += ' again'; 166 | 167 | records = observer.takeRecords(); 168 | assert.strictEqual(records.length, 1); 169 | 170 | expectRecord(records[0], { 171 | type: 'characterData', 172 | target: child 173 | }); 174 | }); 175 | 176 | test('characterData callback', function(cont) { 177 | var div = document.createElement('div'); 178 | var child = div.appendChild(document.createTextNode('text')); 179 | var i = 0; 180 | var observer = new JsMutationObserver(function(records) { 181 | i++; 182 | if (i > 1) 183 | expect().fail(); 184 | 185 | assert.strictEqual(records.length, 1); 186 | 187 | expectRecord(records[0], { 188 | type: 'characterData', 189 | target: child 190 | }); 191 | 192 | // The transient observers are removed before the callback is called. 193 | child.data += ' again'; 194 | records = observer.takeRecords(); 195 | assert.strictEqual(records.length, 0); 196 | 197 | cont(); 198 | }); 199 | observer.observe(div, { 200 | characterData: true, 201 | subtree: true 202 | }); 203 | div.removeChild(child); 204 | child.data = 'changed'; 205 | }); 206 | 207 | test('childList', function() { 208 | var div = testDiv.appendChild(document.createElement('div')); 209 | var child = div.appendChild(document.createElement('div')); 210 | var observer = new JsMutationObserver(function() {}); 211 | observer.observe(div, { 212 | childList: true, 213 | subtree: true 214 | }); 215 | div.removeChild(child); 216 | var grandChild = child.appendChild(document.createElement('span')); 217 | 218 | var records = observer.takeRecords(); 219 | assert.strictEqual(records.length, 2); 220 | 221 | expectRecord(records[0], { 222 | type: 'childList', 223 | target: div, 224 | removedNodes: [child] 225 | }); 226 | 227 | expectRecord(records[1], { 228 | type: 'childList', 229 | target: child, 230 | addedNodes: [grandChild] 231 | }); 232 | 233 | child.removeChild(grandChild); 234 | 235 | records = observer.takeRecords(); 236 | assert.strictEqual(records.length, 1); 237 | 238 | expectRecord(records[0], { 239 | type: 'childList', 240 | target: child, 241 | removedNodes: [grandChild] 242 | }); 243 | }); 244 | 245 | test('childList callback', function(cont) { 246 | var div = testDiv.appendChild(document.createElement('div')); 247 | var child = div.appendChild(document.createElement('div')); 248 | var i = 0; 249 | var observer = new JsMutationObserver(function(records) { 250 | i++; 251 | if (i > 1) 252 | expect().fail(); 253 | 254 | assert.strictEqual(records.length, 2); 255 | 256 | expectRecord(records[0], { 257 | type: 'childList', 258 | target: div, 259 | removedNodes: [child] 260 | }); 261 | 262 | expectRecord(records[1], { 263 | type: 'childList', 264 | target: child, 265 | addedNodes: [grandChild] 266 | }); 267 | 268 | // The transient observers are removed before the callback is called. 269 | child.removeChild(grandChild); 270 | 271 | records = observer.takeRecords(); 272 | assert.strictEqual(records.length, 0); 273 | 274 | cont(); 275 | }); 276 | observer.observe(div, { 277 | childList: true, 278 | subtree: true 279 | }); 280 | div.removeChild(child); 281 | var grandChild = child.appendChild(document.createElement('span')); 282 | }); 283 | }); 284 | --------------------------------------------------------------------------------