summaryrefslogtreecommitdiff
path: root/addons/website/static/lib/MutationObservers
diff options
context:
space:
mode:
Diffstat (limited to 'addons/website/static/lib/MutationObservers')
-rw-r--r--addons/website/static/lib/MutationObservers/AUTHORS9
-rw-r--r--addons/website/static/lib/MutationObservers/LICENSE27
-rw-r--r--addons/website/static/lib/MutationObservers/MutationObserver.js542
-rw-r--r--addons/website/static/lib/MutationObservers/PATENTS23
-rw-r--r--addons/website/static/lib/MutationObservers/README.md4
-rw-r--r--addons/website/static/lib/MutationObservers/last-ref.txt8
-rw-r--r--addons/website/static/lib/MutationObservers/package.json23
-rw-r--r--addons/website/static/lib/MutationObservers/test/attributes.js276
-rw-r--r--addons/website/static/lib/MutationObservers/test/callback.js72
-rw-r--r--addons/website/static/lib/MutationObservers/test/characterData.js106
-rw-r--r--addons/website/static/lib/MutationObservers/test/childList.js377
-rw-r--r--addons/website/static/lib/MutationObservers/test/index.html81
-rw-r--r--addons/website/static/lib/MutationObservers/test/mixed.js36
-rw-r--r--addons/website/static/lib/MutationObservers/test/sidetable.js35
-rw-r--r--addons/website/static/lib/MutationObservers/test/transient.js283
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'));
+ });
+});