diff options
| author | Antony Lesuisse <al@openerp.com> | 2014-01-31 01:51:57 +0100 |
|---|---|---|
| committer | Antony Lesuisse <al@openerp.com> | 2014-01-31 01:51:57 +0100 |
| commit | 4b30f1fcbd8701f82dbbe4e5c8386ee432b063ac (patch) | |
| tree | 93b17a03b45927c20fbb045cda8c8aa09d853409 /addons/website/static/lib/MutationObservers | |
| parent | ba2d4c90307661a79d78e6ce3b274b76b4c62717 (diff) | |
| parent | 1c283ddf242f0681c1d060f3b8658efa3fcaa0a8 (diff) | |
[MERGE] trunk-website-al
bzr revid: al@openerp.com-20140131005157-jkf8hz6fh2buml2c
Diffstat (limited to 'addons/website/static/lib/MutationObservers')
15 files changed, 1902 insertions, 0 deletions
diff --git a/addons/website/static/lib/MutationObservers/AUTHORS b/addons/website/static/lib/MutationObservers/AUTHORS new file mode 100644 index 00000000000..06177657564 --- /dev/null +++ b/addons/website/static/lib/MutationObservers/AUTHORS @@ -0,0 +1,9 @@ +# Names should be added to this file with this pattern: +# +# For individuals: +# Name <email address> +# +# For organizations: +# Organization <fnmatch pattern> +# +Google Inc. <*@google.com> diff --git a/addons/website/static/lib/MutationObservers/LICENSE b/addons/website/static/lib/MutationObservers/LICENSE new file mode 100644 index 00000000000..92d60b019fd --- /dev/null +++ b/addons/website/static/lib/MutationObservers/LICENSE @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/addons/website/static/lib/MutationObservers/MutationObserver.js b/addons/website/static/lib/MutationObservers/MutationObserver.js new file mode 100644 index 00000000000..104d4cdc6fa --- /dev/null +++ b/addons/website/static/lib/MutationObservers/MutationObserver.js @@ -0,0 +1,542 @@ +/* + * Copyright 2012 The Polymer Authors. All rights reserved. + * Use of this source code is goverened by a BSD-style + * license that can be found in the LICENSE file. + */ + +(function(global) { + + var registrationsTable = new SideTable(); + + // We use setImmediate or postMessage for our future callback. + var setImmediate = window.msSetImmediate; + + // Use post message to emulate setImmediate. + if (!setImmediate) { + var setImmediateQueue = []; + var sentinel = String(Math.random()); + window.addEventListener('message', function(e) { + if (e.data === sentinel) { + var queue = setImmediateQueue; + setImmediateQueue = []; + queue.forEach(function(func) { + func(); + }); + } + }); + setImmediate = function(func) { + setImmediateQueue.push(func); + window.postMessage(sentinel, '*'); + }; + } + + // This is used to ensure that we never schedule 2 callas to setImmediate + var isScheduled = false; + + // Keep track of observers that needs to be notified next time. + var scheduledObservers = []; + + /** + * Schedules |dispatchCallback| to be called in the future. + * @param {MutationObserver} observer + */ + function scheduleCallback(observer) { + scheduledObservers.push(observer); + if (!isScheduled) { + isScheduled = true; + setImmediate(dispatchCallbacks); + } + } + + function wrapIfNeeded(node) { + return window.ShadowDOMPolyfill && + window.ShadowDOMPolyfill.wrapIfNeeded(node) || + node; + } + + function dispatchCallbacks() { + // http://dom.spec.whatwg.org/#mutation-observers + + isScheduled = false; // Used to allow a new setImmediate call above. + + var observers = scheduledObservers; + scheduledObservers = []; + // Sort observers based on their creation UID (incremental). + observers.sort(function(o1, o2) { + return o1.uid_ - o2.uid_; + }); + + var anyNonEmpty = false; + observers.forEach(function(observer) { + + // 2.1, 2.2 + var queue = observer.takeRecords(); + // 2.3. Remove all transient registered observers whose observer is mo. + removeTransientObserversFor(observer); + + // 2.4 + if (queue.length) { + observer.callback_(queue, observer); + anyNonEmpty = true; + } + }); + + // 3. + if (anyNonEmpty) + dispatchCallbacks(); + } + + function removeTransientObserversFor(observer) { + observer.nodes_.forEach(function(node) { + var registrations = registrationsTable.get(node); + if (!registrations) + return; + registrations.forEach(function(registration) { + if (registration.observer === observer) + registration.removeTransientObservers(); + }); + }); + } + + /** + * This function is used for the "For each registered observer observer (with + * observer's options as options) in target's list of registered observers, + * run these substeps:" and the "For each ancestor ancestor of target, and for + * each registered observer observer (with options options) in ancestor's list + * of registered observers, run these substeps:" part of the algorithms. The + * |options.subtree| is checked to ensure that the callback is called + * correctly. + * + * @param {Node} target + * @param {function(MutationObserverInit):MutationRecord} callback + */ + function forEachAncestorAndObserverEnqueueRecord(target, callback) { + for (var node = target; node; node = node.parentNode) { + var registrations = registrationsTable.get(node); + + if (registrations) { + for (var j = 0; j < registrations.length; j++) { + var registration = registrations[j]; + var options = registration.options; + + // Only target ignores subtree. + if (node !== target && !options.subtree) + continue; + + var record = callback(options); + if (record) + registration.enqueue(record); + } + } + } + } + + var uidCounter = 0; + + /** + * The class that maps to the DOM MutationObserver interface. + * @param {Function} callback. + * @constructor + */ + function JsMutationObserver(callback) { + this.callback_ = callback; + this.nodes_ = []; + this.records_ = []; + this.uid_ = ++uidCounter; + } + + JsMutationObserver.prototype = { + observe: function(target, options) { + target = wrapIfNeeded(target); + + // 1.1 + if (!options.childList && !options.attributes && !options.characterData || + + // 1.2 + options.attributeOldValue && !options.attributes || + + // 1.3 + options.attributeFilter && options.attributeFilter.length && + !options.attributes || + + // 1.4 + options.characterDataOldValue && !options.characterData) { + + throw new SyntaxError(); + } + + var registrations = registrationsTable.get(target); + if (!registrations) + registrationsTable.set(target, registrations = []); + + // 2 + // If target's list of registered observers already includes a registered + // observer associated with the context object, replace that registered + // observer's options with options. + var registration; + for (var i = 0; i < registrations.length; i++) { + if (registrations[i].observer === this) { + registration = registrations[i]; + registration.removeListeners(); + registration.options = options; + break; + } + } + + // 3. + // Otherwise, add a new registered observer to target's list of registered + // observers with the context object as the observer and options as the + // options, and add target to context object's list of nodes on which it + // is registered. + if (!registration) { + registration = new Registration(this, target, options); + registrations.push(registration); + this.nodes_.push(target); + } + + registration.addListeners(); + }, + + disconnect: function() { + this.nodes_.forEach(function(node) { + var registrations = registrationsTable.get(node); + for (var i = 0; i < registrations.length; i++) { + var registration = registrations[i]; + if (registration.observer === this) { + registration.removeListeners(); + registrations.splice(i, 1); + // Each node can only have one registered observer associated with + // this observer. + break; + } + } + }, this); + this.records_ = []; + }, + + takeRecords: function() { + var copyOfRecords = this.records_; + this.records_ = []; + return copyOfRecords; + } + }; + + /** + * @param {string} type + * @param {Node} target + * @constructor + */ + function MutationRecord(type, target) { + this.type = type; + this.target = target; + this.addedNodes = []; + this.removedNodes = []; + this.previousSibling = null; + this.nextSibling = null; + this.attributeName = null; + this.attributeNamespace = null; + this.oldValue = null; + } + + function copyMutationRecord(original) { + var record = new MutationRecord(original.type, original.target); + record.addedNodes = original.addedNodes.slice(); + record.removedNodes = original.removedNodes.slice(); + record.previousSibling = original.previousSibling; + record.nextSibling = original.nextSibling; + record.attributeName = original.attributeName; + record.attributeNamespace = original.attributeNamespace; + record.oldValue = original.oldValue; + return record; + }; + + // We keep track of the two (possibly one) records used in a single mutation. + var currentRecord, recordWithOldValue; + + /** + * Creates a record without |oldValue| and caches it as |currentRecord| for + * later use. + * @param {string} oldValue + * @return {MutationRecord} + */ + function getRecord(type, target) { + return currentRecord = new MutationRecord(type, target); + } + + /** + * Gets or creates a record with |oldValue| based in the |currentRecord| + * @param {string} oldValue + * @return {MutationRecord} + */ + function getRecordWithOldValue(oldValue) { + if (recordWithOldValue) + return recordWithOldValue; + recordWithOldValue = copyMutationRecord(currentRecord); + recordWithOldValue.oldValue = oldValue; + return recordWithOldValue; + } + + function clearRecords() { + currentRecord = recordWithOldValue = undefined; + } + + /** + * @param {MutationRecord} record + * @return {boolean} Whether the record represents a record from the current + * mutation event. + */ + function recordRepresentsCurrentMutation(record) { + return record === recordWithOldValue || record === currentRecord; + } + + /** + * Selects which record, if any, to replace the last record in the queue. + * This returns |null| if no record should be replaced. + * + * @param {MutationRecord} lastRecord + * @param {MutationRecord} newRecord + * @param {MutationRecord} + */ + function selectRecord(lastRecord, newRecord) { + if (lastRecord === newRecord) + return lastRecord; + + // Check if the the record we are adding represents the same record. If + // so, we keep the one with the oldValue in it. + if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) + return recordWithOldValue; + + return null; + } + + /** + * Class used to represent a registered observer. + * @param {MutationObserver} observer + * @param {Node} target + * @param {MutationObserverInit} options + * @constructor + */ + function Registration(observer, target, options) { + this.observer = observer; + this.target = target; + this.options = options; + this.transientObservedNodes = []; + } + + Registration.prototype = { + enqueue: function(record) { + var records = this.observer.records_; + var length = records.length; + + // There are cases where we replace the last record with the new record. + // For example if the record represents the same mutation we need to use + // the one with the oldValue. If we get same record (this can happen as we + // walk up the tree) we ignore the new record. + if (records.length > 0) { + var lastRecord = records[length - 1]; + var recordToReplaceLast = selectRecord(lastRecord, record); + if (recordToReplaceLast) { + records[length - 1] = recordToReplaceLast; + return; + } + } else { + scheduleCallback(this.observer); + } + + records[length] = record; + }, + + addListeners: function() { + this.addListeners_(this.target); + }, + + addListeners_: function(node) { + var options = this.options; + if (options.attributes) + node.addEventListener('DOMAttrModified', this, true); + + if (options.characterData) + node.addEventListener('DOMCharacterDataModified', this, true); + + if (options.childList) + node.addEventListener('DOMNodeInserted', this, true); + + if (options.childList || options.subtree) + node.addEventListener('DOMNodeRemoved', this, true); + }, + + removeListeners: function() { + this.removeListeners_(this.target); + }, + + removeListeners_: function(node) { + var options = this.options; + if (options.attributes) + node.removeEventListener('DOMAttrModified', this, true); + + if (options.characterData) + node.removeEventListener('DOMCharacterDataModified', this, true); + + if (options.childList) + node.removeEventListener('DOMNodeInserted', this, true); + + if (options.childList || options.subtree) + node.removeEventListener('DOMNodeRemoved', this, true); + }, + + /** + * Adds a transient observer on node. The transient observer gets removed + * next time we deliver the change records. + * @param {Node} node + */ + addTransientObserver: function(node) { + // Don't add transient observers on the target itself. We already have all + // the required listeners set up on the target. + if (node === this.target) + return; + + this.addListeners_(node); + this.transientObservedNodes.push(node); + var registrations = registrationsTable.get(node); + if (!registrations) + registrationsTable.set(node, registrations = []); + + // We know that registrations does not contain this because we already + // checked if node === this.target. + registrations.push(this); + }, + + removeTransientObservers: function() { + var transientObservedNodes = this.transientObservedNodes; + this.transientObservedNodes = []; + + transientObservedNodes.forEach(function(node) { + // Transient observers are never added to the target. + this.removeListeners_(node); + + var registrations = registrationsTable.get(node); + for (var i = 0; i < registrations.length; i++) { + if (registrations[i] === this) { + registrations.splice(i, 1); + // Each node can only have one registered observer associated with + // this observer. + break; + } + } + }, this); + }, + + handleEvent: function(e) { + // Stop propagation since we are managing the propagation manually. + // This means that other mutation events on the page will not work + // correctly but that is by design. + e.stopImmediatePropagation(); + + switch (e.type) { + case 'DOMAttrModified': + // http://dom.spec.whatwg.org/#concept-mo-queue-attributes + + var name = e.attrName; + var namespace = e.relatedNode.namespaceURI; + var target = e.target; + + // 1. + var record = new getRecord('attributes', target); + record.attributeName = name; + record.attributeNamespace = namespace; + + // 2. + var oldValue = + e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; + + forEachAncestorAndObserverEnqueueRecord(target, function(options) { + // 3.1, 4.2 + if (!options.attributes) + return; + + // 3.2, 4.3 + if (options.attributeFilter && options.attributeFilter.length && + options.attributeFilter.indexOf(name) === -1 && + options.attributeFilter.indexOf(namespace) === -1) { + return; + } + // 3.3, 4.4 + if (options.attributeOldValue) + return getRecordWithOldValue(oldValue); + + // 3.4, 4.5 + return record; + }); + + break; + + case 'DOMCharacterDataModified': + // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata + var target = e.target; + + // 1. + var record = getRecord('characterData', target); + + // 2. + var oldValue = e.prevValue; + + + forEachAncestorAndObserverEnqueueRecord(target, function(options) { + // 3.1, 4.2 + if (!options.characterData) + return; + + // 3.2, 4.3 + if (options.characterDataOldValue) + return getRecordWithOldValue(oldValue); + + // 3.3, 4.4 + return record; + }); + + break; + + case 'DOMNodeRemoved': + this.addTransientObserver(e.target); + // Fall through. + case 'DOMNodeInserted': + // http://dom.spec.whatwg.org/#concept-mo-queue-childlist + var target = e.relatedNode; + var changedNode = e.target; + var addedNodes, removedNodes; + if (e.type === 'DOMNodeInserted') { + addedNodes = [changedNode]; + removedNodes = []; + } else { + + addedNodes = []; + removedNodes = [changedNode]; + } + var previousSibling = changedNode.previousSibling; + var nextSibling = changedNode.nextSibling; + + // 1. + var record = getRecord('childList', target); + record.addedNodes = addedNodes; + record.removedNodes = removedNodes; + record.previousSibling = previousSibling; + record.nextSibling = nextSibling; + + forEachAncestorAndObserverEnqueueRecord(target, function(options) { + // 2.1, 3.2 + if (!options.childList) + return; + + // 2.2, 3.3 + return record; + }); + + } + + clearRecords(); + } + }; + + global.JsMutationObserver = JsMutationObserver; + +})(this); diff --git a/addons/website/static/lib/MutationObservers/PATENTS b/addons/website/static/lib/MutationObservers/PATENTS new file mode 100644 index 00000000000..e120963325b --- /dev/null +++ b/addons/website/static/lib/MutationObservers/PATENTS @@ -0,0 +1,23 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Polymer project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Polymer, where such license applies only to those +patent claims, both currently owned or controlled by Google and acquired +in the future, licensable by Google that are necessarily infringed by +this implementation of Polymer. This grant does not include claims +that would be infringed only as a consequence of further modification of +this implementation. If you or your agent or exclusive licensee +institute or order or agree to the institution of patent litigation +against any entity (including a cross-claim or counterclaim in a +lawsuit) alleging that this implementation of Polymer or any code +incorporated within this implementation of Polymer constitutes +direct or contributory patent infringement, or inducement of patent +infringement, then any patent rights granted to you under this License +for this implementation of Polymer shall terminate as of the date +such litigation is filed. diff --git a/addons/website/static/lib/MutationObservers/README.md b/addons/website/static/lib/MutationObservers/README.md new file mode 100644 index 00000000000..ada6b16abad --- /dev/null +++ b/addons/website/static/lib/MutationObservers/README.md @@ -0,0 +1,4 @@ +MutationObservers +================= + +Mutation Observers Polyfill diff --git a/addons/website/static/lib/MutationObservers/last-ref.txt b/addons/website/static/lib/MutationObservers/last-ref.txt new file mode 100644 index 00000000000..691ca46cd13 --- /dev/null +++ b/addons/website/static/lib/MutationObservers/last-ref.txt @@ -0,0 +1,8 @@ +commit 5bed2236ab958adab07e227b22dd4b9a1c2d054d +Merge: 11438c9 acf52bc +Author: Daniel Freedman <dfreedm2@gmail.com> +Date: Wed Jun 5 11:46:06 2013 -0700 + + Merge pull request #8 from Polymer/master + + 6/5 master -> stable diff --git a/addons/website/static/lib/MutationObservers/package.json b/addons/website/static/lib/MutationObservers/package.json new file mode 100644 index 00000000000..17ff89ac548 --- /dev/null +++ b/addons/website/static/lib/MutationObservers/package.json @@ -0,0 +1,23 @@ +{ + "name": "MutationObservers", + "version": "0.0.0", + "description": "ERROR: No README.md file found!", + "main": "MutationObserver.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/Polymer/MutationObservers.git" + }, + "author": "", + "license": "BSD", + "gitHead": "fffb15e43b267034671cb01521df35b7fa063b7a", + "devDependencies": { + "chai": "*", + "mocha": "*" + } +} diff --git a/addons/website/static/lib/MutationObservers/test/attributes.js b/addons/website/static/lib/MutationObservers/test/attributes.js new file mode 100644 index 00000000000..73d697beede --- /dev/null +++ b/addons/website/static/lib/MutationObservers/test/attributes.js @@ -0,0 +1,276 @@ +/* + * Copyright 2012 The Polymer Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +suite('JsMutationObserver attributes', function() { + + test('attr', function() { + var div = document.createElement('div'); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + attributes: true + }); + div.setAttribute('a', 'A'); + div.setAttribute('a', 'B'); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'attributes', + target: div, + attributeName: 'a', + attributeNamespace: null + }); + expectRecord(records[1], { + type: 'attributes', + target: div, + attributeName: 'a', + attributeNamespace: null + }); + }); + + test('attr with oldValue', function() { + var div = document.createElement('div'); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + attributes: true, + attributeOldValue: true + }); + div.setAttribute('a', 'A'); + div.setAttribute('a', 'B'); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'attributes', + target: div, + attributeName: 'a', + attributeNamespace: null, + oldValue: null + }); + expectRecord(records[1], { + type: 'attributes', + target: div, + attributeName: 'a', + attributeNamespace: null, + oldValue: 'A' + }); + }); + + test('attr change in subtree should not genereate a record', function() { + var div = document.createElement('div'); + var child = div.appendChild(document.createElement('div')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + attributes: true + }); + child.setAttribute('a', 'A'); + child.setAttribute('a', 'B'); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 0); + }); + + test('attr change, subtree', function() { + var div = document.createElement('div'); + var child = div.appendChild(document.createElement('div')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + attributes: true, + subtree: true + }); + child.setAttribute('a', 'A'); + child.setAttribute('a', 'B'); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'attributes', + target: child, + attributeName: 'a' + }); + expectRecord(records[1], { + type: 'attributes', + target: child, + attributeName: 'a' + }); + }); + + + test('multiple observers on same target', function() { + var div = document.createElement('div'); + var observer1 = new JsMutationObserver(function() {}); + observer1.observe(div, { + attributes: true, + attributeOldValue: true + }); + var observer2 = new JsMutationObserver(function() {}); + observer2.observe(div, { + attributes: true, + attributeFilter: ['b'] + }); + + div.setAttribute('a', 'A'); + div.setAttribute('a', 'A2'); + div.setAttribute('b', 'B'); + + var records = observer1.takeRecords(); + assert.strictEqual(records.length, 3); + + expectRecord(records[0], { + type: 'attributes', + target: div, + attributeName: 'a' + }); + expectRecord(records[1], { + type: 'attributes', + target: div, + attributeName: 'a', + oldValue: 'A' + }); + expectRecord(records[2], { + type: 'attributes', + target: div, + attributeName: 'b' + }); + + records = observer2.takeRecords(); + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'attributes', + target: div, + attributeName: 'b' + }); + }); + + test('observer observes on different target', function() { + var div = document.createElement('div'); + var child = div.appendChild(document.createElement('div')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(child, { + attributes: true + }); + observer.observe(div, { + attributes: true, + subtree: true, + attributeOldValue: true + }); + + child.setAttribute('a', 'A'); + child.setAttribute('a', 'A2'); + child.setAttribute('b', 'B'); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 3); + + expectRecord(records[0], { + type: 'attributes', + target: child, + attributeName: 'a' + }); + expectRecord(records[1], { + type: 'attributes', + target: child, + attributeName: 'a', + oldValue: 'A' + }); + expectRecord(records[2], { + type: 'attributes', + target: child, + attributeName: 'b' + }); + }); + + test('observing on the same node should update the options', function() { + var div = document.createElement('div'); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + attributes: true, + attributeFilter: ['a'] + }); + observer.observe(div, { + attributes: true, + attributeFilter: ['b'] + }); + + div.setAttribute('a', 'A'); + div.setAttribute('b', 'B'); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'attributes', + target: div, + attributeName: 'b' + }); + }); + + test('disconnect should stop all events and empty the records', function() { + var div = document.createElement('div'); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + attributes: true, + }); + + div.setAttribute('a', 'A'); + + observer.disconnect(); + var records = observer.takeRecords(); + assert.strictEqual(records.length, 0); + + div.setAttribute('b', 'B'); + + records = observer.takeRecords(); + assert.strictEqual(records.length, 0); + }); + + test('disconnect should not affect other observers', function() { + var div = document.createElement('div'); + var observer1 = new JsMutationObserver(function() {}); + observer1.observe(div, { + attributes: true, + }); + var observer2 = new JsMutationObserver(function() {}); + observer2.observe(div, { + attributes: true, + }); + + div.setAttribute('a', 'A'); + + observer1.disconnect(); + var records1 = observer1.takeRecords(); + assert.strictEqual(records1.length, 0); + + var records2 = observer2.takeRecords(); + assert.strictEqual(records2.length, 1); + expectRecord(records2[0], { + type: 'attributes', + target: div, + attributeName: 'a' + }); + + div.setAttribute('b', 'B'); + + records1 = observer1.takeRecords(); + assert.strictEqual(records1.length, 0); + + records2 = observer2.takeRecords(); + assert.strictEqual(records2.length, 1); + expectRecord(records2[0], { + type: 'attributes', + target: div, + attributeName: 'b' + }); + }); + +});
\ No newline at end of file diff --git a/addons/website/static/lib/MutationObservers/test/callback.js b/addons/website/static/lib/MutationObservers/test/callback.js new file mode 100644 index 00000000000..b3083c7bec3 --- /dev/null +++ b/addons/website/static/lib/MutationObservers/test/callback.js @@ -0,0 +1,72 @@ +/* + * Copyright 2012 The Polymer Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +suite('JsMutationObserver callback', function() { + + test('One observer, two attribute changes', function(cont) { + var div = document.createElement('div'); + var observer = new JsMutationObserver(function(records) { + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'attributes', + target: div, + attributeName: 'a', + attributeNamespace: null + }); + expectRecord(records[1], { + type: 'attributes', + target: div, + attributeName: 'a', + attributeNamespace: null + }); + + cont(); + }); + + observer.observe(div, { + attributes: true + }); + + div.setAttribute('a', 'A'); + div.setAttribute('a', 'B'); + }); + + test('nested changes', function(cont) { + var div = document.createElement('div'); + var i = 0; + var observer = new JsMutationObserver(function(records) { + assert.strictEqual(records.length, 1); + + if (i === 0) { + expectRecord(records[0], { + type: 'attributes', + target: div, + attributeName: 'a', + attributeNamespace: null + }); + div.setAttribute('b', 'B'); + i++; + } else { + expectRecord(records[0], { + type: 'attributes', + target: div, + attributeName: 'b', + attributeNamespace: null + }); + + cont(); + } + }); + + observer.observe(div, { + attributes: true + }); + + div.setAttribute('a', 'A'); + }); + +});
\ No newline at end of file diff --git a/addons/website/static/lib/MutationObservers/test/characterData.js b/addons/website/static/lib/MutationObservers/test/characterData.js new file mode 100644 index 00000000000..498dc053e6e --- /dev/null +++ b/addons/website/static/lib/MutationObservers/test/characterData.js @@ -0,0 +1,106 @@ +/* + * Copyright 2012 The Polymer Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +suite('JsMutationObserver characterData', function() { + + var testDiv; + + setup(function() { + testDiv = document.body.appendChild(document.createElement('div')); + }); + + teardown(function() { + document.body.removeChild(testDiv); + }); + + test('characterData', function() { + var text = document.createTextNode('abc'); + var observer = new JsMutationObserver(function() {}); + observer.observe(text, { + characterData: true + }); + text.data = 'def'; + text.data = 'ghi'; + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'characterData', + target: text + }); + expectRecord(records[1], { + type: 'characterData', + target: text + }); + }); + + test('characterData with old value', function() { + var text = testDiv.appendChild(document.createTextNode('abc')); + var observer = new JsMutationObserver(function() {}); + observer.observe(text, { + characterData: true, + characterDataOldValue: true + }); + text.data = 'def'; + text.data = 'ghi'; + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'characterData', + target: text, + oldValue: 'abc' + }); + expectRecord(records[1], { + type: 'characterData', + target: text, + oldValue: 'def' + }); + }); + + test('characterData change in subtree should not generate a record', + function() { + var div = document.createElement('div'); + var text = div.appendChild(document.createTextNode('abc')); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + characterData: true + }); + text.data = 'def'; + text.data = 'ghi'; + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 0); + }); + + test('characterData change in subtree', + function() { + var div = document.createElement('div'); + var text = div.appendChild(document.createTextNode('abc')); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + characterData: true, + subtree: true + }); + text.data = 'def'; + text.data = 'ghi'; + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'characterData', + target: text + }); + expectRecord(records[1], { + type: 'characterData', + target: text + }); + }); + +});
\ No newline at end of file diff --git a/addons/website/static/lib/MutationObservers/test/childList.js b/addons/website/static/lib/MutationObservers/test/childList.js new file mode 100644 index 00000000000..8ff4e4f2718 --- /dev/null +++ b/addons/website/static/lib/MutationObservers/test/childList.js @@ -0,0 +1,377 @@ +/* + * Copyright 2012 The Polymer Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +suite('JsMutationObserver childList', function() { + + var testDiv; + + teardown(function() { + document.body.removeChild(testDiv); + }); + + var addedNodes, removedNodes; + + setup(function() { + testDiv = document.body.appendChild(document.createElement('div')); + addedNodes = []; + removedNodes = []; + }); + + function mergeRecords(records) { + records.forEach(function(record) { + if (record.addedNodes) + addedNodes.push.apply(addedNodes, record.addedNodes); + if (record.removedNodes) + removedNodes.push.apply(removedNodes, record.removedNodes); + }); + } + + function assertAll(records, expectedProperties) { + records.forEach(function(record) { + for (var propertyName in expectedProperties) { + assert.strictEqual(record[propertyName], expectedProperties[propertyName]); + } + }); + } + + test('appendChild', function() { + var div = document.createElement('div'); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true + }); + var a = document.createElement('a'); + var b = document.createElement('b'); + + div.appendChild(a); + div.appendChild(b); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'childList', + target: div, + addedNodes: [a] + }); + + expectRecord(records[1], { + type: 'childList', + target: div, + addedNodes: [b], + previousSibling: a + }); + }); + + test('insertBefore', function() { + var div = document.createElement('div'); + var a = document.createElement('a'); + var b = document.createElement('b'); + var c = document.createElement('c'); + div.appendChild(a); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true + }); + + div.insertBefore(b, a); + div.insertBefore(c, a); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'childList', + target: div, + addedNodes: [b], + nextSibling: a + }); + + expectRecord(records[1], { + type: 'childList', + target: div, + addedNodes: [c], + nextSibling: a, + previousSibling: b + }); + }); + + + test('removeChild', function() { + var div = testDiv.appendChild(document.createElement('div')); + var a = div.appendChild(document.createElement('a')); + var b = div.appendChild(document.createElement('b')); + var c = div.appendChild(document.createElement('c')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true + }); + + div.removeChild(b); + div.removeChild(a); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'childList', + target: div, + removedNodes: [b], + nextSibling: c, + previousSibling: a + }); + + expectRecord(records[1], { + type: 'childList', + target: div, + removedNodes: [a], + nextSibling: c + }); + }); + + test('Direct children', function() { + var div = testDiv.appendChild(document.createElement('div')); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true + }); + var a = document.createElement('a'); + var b = document.createElement('b'); + + div.appendChild(a); + div.insertBefore(b, a); + div.removeChild(b); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 3); + + expectRecord(records[0], { + type: 'childList', + target: div, + addedNodes: [a] + }); + + expectRecord(records[1], { + type: 'childList', + target: div, + nextSibling: a, + addedNodes: [b] + }); + + expectRecord(records[2], { + type: 'childList', + target: div, + nextSibling: a, + removedNodes: [b] + }); + }); + + test('subtree', function() { + var div = document.createElement('div'); + var child = div.appendChild(document.createElement('div')); + var observer = new JsMutationObserver(function() {}); + observer.observe(child, { + childList: true + }); + var a = document.createTextNode('a'); + var b = document.createTextNode('b'); + + child.appendChild(a); + child.insertBefore(b, a); + child.removeChild(b); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 3); + + expectRecord(records[0], { + type: 'childList', + target: child, + addedNodes: [a] + }); + + expectRecord(records[1], { + type: 'childList', + target: child, + nextSibling: a, + addedNodes: [b] + }); + + expectRecord(records[2], { + type: 'childList', + target: child, + nextSibling: a, + removedNodes: [b] + }); + }); + + test('both direct and subtree', function() { + var div = document.createElement('div'); + var child = div.appendChild(document.createElement('div')); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true, + subtree: true + }); + observer.observe(child, { + childList: true + }); + + var a = document.createTextNode('a'); + var b = document.createTextNode('b'); + + child.appendChild(a); + div.appendChild(b); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'childList', + target: child, + addedNodes: [a] + }); + + expectRecord(records[1], { + type: 'childList', + target: div, + addedNodes: [b], + previousSibling: child + }); + }); + + test('Append multiple at once at the end', function() { + var div = testDiv.appendChild(document.createElement('div')); + var a = div.appendChild(document.createTextNode('a')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true + }); + + var df = document.createDocumentFragment(); + var b = df.appendChild(document.createTextNode('b')); + var c = df.appendChild(document.createTextNode('c')); + var d = df.appendChild(document.createTextNode('d')); + + div.appendChild(df); + + var records = observer.takeRecords(); + mergeRecords(records); + + assertArrayEqual(addedNodes, [b, c, d]); + assertArrayEqual(removedNodes, []); + assertAll(records, { + type: 'childList', + target: div + }); + }); + + test('Append multiple at once at the front', function() { + var div = testDiv.appendChild(document.createElement('div')); + var a = div.appendChild(document.createTextNode('a')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true + }); + + var df = document.createDocumentFragment(); + var b = df.appendChild(document.createTextNode('b')); + var c = df.appendChild(document.createTextNode('c')); + var d = df.appendChild(document.createTextNode('d')); + + div.insertBefore(df, a); + + var records = observer.takeRecords(); + mergeRecords(records); + + assertArrayEqual(addedNodes, [b, c, d]); + assertArrayEqual(removedNodes, []); + assertAll(records, { + type: 'childList', + target: div + }); + }); + + test('Append multiple at once in the middle', function() { + var div = testDiv.appendChild(document.createElement('div')); + var a = div.appendChild(document.createTextNode('a')); + var b = div.appendChild(document.createTextNode('b')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true + }); + + var df = document.createDocumentFragment(); + var c = df.appendChild(document.createTextNode('c')); + var d = df.appendChild(document.createTextNode('d')); + + div.insertBefore(df, b); + + var records = observer.takeRecords(); + mergeRecords(records); + + assertArrayEqual(addedNodes, [c, d]); + assertArrayEqual(removedNodes, []); + assertAll(records, { + type: 'childList', + target: div + }); + }); + + test('Remove all children', function() { + var div = testDiv.appendChild(document.createElement('div')); + var a = div.appendChild(document.createTextNode('a')); + var b = div.appendChild(document.createTextNode('b')); + var c = div.appendChild(document.createTextNode('c')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true + }); + + div.innerHTML = ''; + + var records = observer.takeRecords(); + mergeRecords(records); + + assertArrayEqual(addedNodes, []); + assertArrayEqual(removedNodes, [a, b, c]); + assertAll(records, { + type: 'childList', + target: div + }); + }); + + test('Replace all children using innerHTML', function() { + var div = testDiv.appendChild(document.createElement('div')); + var a = div.appendChild(document.createTextNode('a')); + var b = div.appendChild(document.createTextNode('b')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true + }); + + div.innerHTML = '<c></c><d></d>'; + var c = div.firstChild; + var d = div.lastChild; + + var records = observer.takeRecords(); + mergeRecords(records); + + assertArrayEqual(addedNodes, [c, d]); + assertArrayEqual(removedNodes, [a, b]); + assertAll(records, { + type: 'childList', + target: div + }); + }); + +});
\ No newline at end of file diff --git a/addons/website/static/lib/MutationObservers/test/index.html b/addons/website/static/lib/MutationObservers/test/index.html new file mode 100644 index 00000000000..fcedaf4e3fe --- /dev/null +++ b/addons/website/static/lib/MutationObservers/test/index.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<!-- +Copyright 2012 The Polymer Authors. All rights reserved. +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file. +--> +<meta charset="utf-8"> +<link rel="stylesheet" href="../node_modules/mocha/mocha.css"> +<script src="../node_modules/chai/chai.js"></script> +<script src="../node_modules/mocha/mocha.js"></script> +<script src="sidetable.js"></script> +<script src="../MutationObserver.js"></script> +<script> + +mocha.setup({ + ui: 'tdd', + ignoreLeaks: true +}); + +var assert = chai.assert; + +function assertArrayEqual(a, b, msg) { + assert.equal(a.length, b.length, msg); + for (var i = 0; i < a.length; i++) { + assert.equal(a[i], b[i], msg); + } +} + +var expectRecord = (function() { + var useNative = /native/.test(location.search); + var isWebKit = /WebKit/.test(navigator.userAgent); + var slice = Array.prototype.slice.call.bind(Array.prototype.slice); + + if (useNative) { + JsMutationObserver = + window.MutationObserver || window.WebKitMutationObserver; + } + + // addedNodes/removedNodes are broken in WebKit. + // https://bugs.webkit.org/show_bug.cgi?id=98921 + function fixWebKitNodes(nodes) { + if (nodes === null && useNative && isWebKit) + return []; + return nodes; + } + + return function expectRecord(record, expected) { + assert.strictEqual(record.type, + expected.type === undefined ? null : expected.type); + assert.strictEqual(record.target, + expected.target === undefined ? null : expected.target); + assertArrayEqual(fixWebKitNodes(record.addedNodes), + expected.addedNodes === undefined ? [] : expected.addedNodes); + assertArrayEqual(fixWebKitNodes(record.removedNodes), + expected.removedNodes === undefined ? [] : expected.removedNodes); + assert.strictEqual(record.previousSibling, + expected.previousSibling === undefined ? + null : expected.previousSibling); + assert.strictEqual(record.nextSibling, + expected.nextSibling === undefined ? null : expected.nextSibling); + assert.strictEqual(record.attributeName, + expected.attributeName === undefined ? null : expected.attributeName); + assert.strictEqual(record.attributeNamespace, + expected.attributeNamespace === undefined ? + null : expected.attributeNamespace); + assert.strictEqual(record.oldValue, + expected.oldValue === undefined ? null : expected.oldValue); + }; +})(); + +</script> +<script src="attributes.js"></script> +<script src="characterData.js"></script> +<script src="childList.js"></script> +<script src="mixed.js"></script> +<script src="callback.js"></script> +<script src="transient.js"></script> +<div id="mocha"></div> +<script> +mocha.run(); +</script> diff --git a/addons/website/static/lib/MutationObservers/test/mixed.js b/addons/website/static/lib/MutationObservers/test/mixed.js new file mode 100644 index 00000000000..eae6a60604c --- /dev/null +++ b/addons/website/static/lib/MutationObservers/test/mixed.js @@ -0,0 +1,36 @@ +/* + * Copyright 2012 The Polymer Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +suite('JsMutationObserver mixed types', function() { + + test('attr and characterData', function() { + var div = document.createElement('div'); + var text = div.appendChild(document.createTextNode('text')); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + attributes: true, + characterData: true, + subtree: true + }); + div.setAttribute('a', 'A'); + div.firstChild.data = 'changed'; + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'attributes', + target: div, + attributeName: 'a', + attributeNamespace: null + }); + expectRecord(records[1], { + type: 'characterData', + target: div.firstChild + }); + }); + +});
\ No newline at end of file diff --git a/addons/website/static/lib/MutationObservers/test/sidetable.js b/addons/website/static/lib/MutationObservers/test/sidetable.js new file mode 100644 index 00000000000..cae31e6c012 --- /dev/null +++ b/addons/website/static/lib/MutationObservers/test/sidetable.js @@ -0,0 +1,35 @@ +/* + * Copyright 2012 The Polymer Authors. All rights reserved. + * Use of this source code is goverened by a BSD-style + * license that can be found in the LICENSE file. + */ + +// SideTable is a weak map where possible. If WeakMap is not available the +// association is stored as an expando property. +var SideTable; +// TODO(arv): WeakMap does not allow for Node etc to be keys in Firefox +if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') < 0) { + SideTable = WeakMap; +} else { + (function() { + var defineProperty = Object.defineProperty; + var hasOwnProperty = Object.hasOwnProperty; + var counter = new Date().getTime() % 1e9; + + SideTable = function() { + this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__'); + }; + + SideTable.prototype = { + set: function(key, value) { + defineProperty(key, this.name, {value: value, writable: true}); + }, + get: function(key) { + return hasOwnProperty.call(key, this.name) ? key[this.name] : undefined; + }, + delete: function(key) { + this.set(key, undefined); + } + } + })(); +} diff --git a/addons/website/static/lib/MutationObservers/test/transient.js b/addons/website/static/lib/MutationObservers/test/transient.js new file mode 100644 index 00000000000..7dda83e27d9 --- /dev/null +++ b/addons/website/static/lib/MutationObservers/test/transient.js @@ -0,0 +1,283 @@ +/* + * Copyright 2012 The Polymer Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +suite('JsMutationObserver transient', function() { + + var testDiv; + + setup(function() { + testDiv = document.body.appendChild(document.createElement('div')); + }); + + teardown(function() { + document.body.removeChild(testDiv); + }); + + test('attr', function() { + var div = testDiv.appendChild(document.createElement('div')); + var child = div.appendChild(document.createElement('div')); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + attributes: true, + subtree: true + }); + div.removeChild(child); + child.setAttribute('a', 'A'); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'attributes', + target: child, + attributeName: 'a', + attributeNamespace: null + }); + + child.setAttribute('b', 'B'); + + records = observer.takeRecords(); + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'attributes', + target: child, + attributeName: 'b', + attributeNamespace: null + }); + }); + + test('attr callback', function(cont) { + var div = testDiv.appendChild(document.createElement('div')); + var child = div.appendChild(document.createElement('div')); + var i = 0; + var observer = new JsMutationObserver(function(records) { + i++; + if (i > 1) + expect().fail(); + + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'attributes', + target: child, + attributeName: 'a', + attributeNamespace: null + }); + + // The transient observers are removed before the callback is called. + child.setAttribute('b', 'B'); + records = observer.takeRecords(); + assert.strictEqual(records.length, 0); + + cont(); + }); + + observer.observe(div, { + attributes: true, + subtree: true + }); + + div.removeChild(child); + child.setAttribute('a', 'A'); + }); + + test('attr, make sure transient gets removed', function(cont) { + var div = testDiv.appendChild(document.createElement('div')); + var child = div.appendChild(document.createElement('div')); + var i = 0; + var observer = new JsMutationObserver(function(records) { + i++; + if (i > 1) + expect().fail(); + + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'attributes', + target: child, + attributeName: 'a', + attributeNamespace: null + }); + + step2(); + }); + + observer.observe(div, { + attributes: true, + subtree: true + }); + + div.removeChild(child); + child.setAttribute('a', 'A'); + + function step2() { + var div2 = document.createElement('div'); + var observer2 = new JsMutationObserver(function(records) { + i++; + if (i > 2) + expect().fail(); + + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'attributes', + target: child, + attributeName: 'b', + attributeNamespace: null + }); + + cont(); + }); + + observer2.observe(div2, { + attributes: true, + subtree: true, + }); + + div2.appendChild(child); + child.setAttribute('b', 'B'); + } + }); + + test('characterData', function() { + var div = document.createElement('div'); + var child = div.appendChild(document.createTextNode('text')); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + characterData: true, + subtree: true + }); + div.removeChild(child); + child.data = 'changed'; + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'characterData', + target: child + }); + + child.data += ' again'; + + records = observer.takeRecords(); + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'characterData', + target: child + }); + }); + + test('characterData callback', function(cont) { + var div = document.createElement('div'); + var child = div.appendChild(document.createTextNode('text')); + var i = 0; + var observer = new JsMutationObserver(function(records) { + i++; + if (i > 1) + expect().fail(); + + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'characterData', + target: child + }); + + // The transient observers are removed before the callback is called. + child.data += ' again'; + records = observer.takeRecords(); + assert.strictEqual(records.length, 0); + + cont(); + }); + observer.observe(div, { + characterData: true, + subtree: true + }); + div.removeChild(child); + child.data = 'changed'; + }); + + test('childList', function() { + var div = testDiv.appendChild(document.createElement('div')); + var child = div.appendChild(document.createElement('div')); + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true, + subtree: true + }); + div.removeChild(child); + var grandChild = child.appendChild(document.createElement('span')); + + var records = observer.takeRecords(); + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'childList', + target: div, + removedNodes: [child] + }); + + expectRecord(records[1], { + type: 'childList', + target: child, + addedNodes: [grandChild] + }); + + child.removeChild(grandChild); + + records = observer.takeRecords(); + assert.strictEqual(records.length, 1); + + expectRecord(records[0], { + type: 'childList', + target: child, + removedNodes: [grandChild] + }); + }); + + test('childList callback', function(cont) { + var div = testDiv.appendChild(document.createElement('div')); + var child = div.appendChild(document.createElement('div')); + var i = 0; + var observer = new JsMutationObserver(function(records) { + i++; + if (i > 1) + expect().fail(); + + assert.strictEqual(records.length, 2); + + expectRecord(records[0], { + type: 'childList', + target: div, + removedNodes: [child] + }); + + expectRecord(records[1], { + type: 'childList', + target: child, + addedNodes: [grandChild] + }); + + // The transient observers are removed before the callback is called. + child.removeChild(grandChild); + + records = observer.takeRecords(); + assert.strictEqual(records.length, 0); + + cont(); + }); + observer.observe(div, { + childList: true, + subtree: true + }); + div.removeChild(child); + var grandChild = child.appendChild(document.createElement('span')); + }); +}); |
