delete MutationObserver polyfill

This commit is contained in:
Monica Dinculescu
2016-12-13 15:52:04 -08:00
parent 52fe0c3247
commit ab21a42c01
18 changed files with 4 additions and 1868 deletions

View File

@@ -11,9 +11,6 @@ A suite of polyfills supporting the [Web Components](http://webcomponents.org) s
**Shadow DOM**: provides encapsulation by hiding DOM subtrees under shadow roots ([spec](https://w3c.github.io/webcomponents/spec/shadow/)).
This also folds in polyfills for `MutationObserver` and `WeakMap`.
## Releases
Pre-built (concatenated & minified) versions of the polyfills are maintained in the [tagged versions](https://github.com/webcomponents/webcomponentsjs/releases) of this repo. There are two variants:
@@ -75,9 +72,9 @@ Copyright (c) 2015 The Polymer Authors. All rights reserved.
### `WebComponentsReady`
Under native HTML Imports, `<script>` tags in the main document block the loading of such imports. This is to ensure the imports have loaded and any registered elements in them have been upgraded.
Under native HTML Imports, `<script>` tags in the main document block the loading of such imports. This is to ensure the imports have loaded and any registered elements in them have been upgraded.
The webcomponents.js and webcomponents-lite.js polyfills parse element definitions and handle their upgrade asynchronously. If prematurely fetching the element from the DOM before it has an opportunity to upgrade, you'll be working with an `HTMLUnknownElement`.
The webcomponents.js and webcomponents-lite.js polyfills parse element definitions and handle their upgrade asynchronously. If prematurely fetching the element from the DOM before it has an opportunity to upgrade, you'll be working with an `HTMLUnknownElement`.
For these situations (or when you need an approximate replacement for the Polymer 0.5 `polymer-ready` behavior), you can use the `WebComponentsReady` event as a signal before interacting with the element. The criteria for this event to fire is all Custom Elements with definitions registered by the time HTML Imports available at load time have loaded have upgraded.
@@ -144,7 +141,7 @@ polyfill-next-selector { content: '.foo :host.bar, :host.foo.bar'; }
```
### ShadowCSS: :host(.zot:not(.bar:nth-child(2))) doesn't work <a id="nestedparens"></a>
ShadowCSS `:host()` rules can only have (at most) 1-level of nested parentheses in its argument selector under ShadowCSS. For example, `:host(.zot)` and `:host(.zot:not(.bar))` both work, but `:host(.zot:not(.bar:nth-child(2)))` does not.
ShadowCSS `:host()` rules can only have (at most) 1-level of nested parentheses in its argument selector under ShadowCSS. For example, `:host(.zot)` and `:host(.zot:not(.bar))` both work, but `:host(.zot:not(.bar:nth-child(2)))` does not.
### HTML imports: document.currentScript doesn't work as expected <a id="currentscript"></a>
In native HTML Imports, document.currentScript.ownerDocument references the import document itself. In the polyfill use document._currentScript.ownerDocument (note the underscore).

View File

@@ -1 +0,0 @@
MutationObserver.prototype.takeRecords = function() {};

View File

@@ -125,10 +125,8 @@ defineBuildTask('webcomponents', './src/WebComponents/build.json');
defineBuildTask('webcomponents-lite', './src/WebComponents/build-lite.json');
defineBuildTask('HTMLImports');
defineBuildTask('ShadowDOM');
defineBuildTask('MutationObserver');
gulp.task('build', ['webcomponents', 'webcomponents-lite', 'HTMLImports', 'ShadowDOM', 'copy-bower',
'MutationObserver']);
gulp.task('build', ['webcomponents', 'webcomponents-lite', 'HTMLImports', 'ShadowDOM', 'copy-bower']);
gulp.task('release', function(cb) {
isRelease = true;

View File

@@ -30,7 +30,6 @@ var file = 'HTMLImports.js';
var modules = [
'../WeakMap/WeakMap.js',
'../MutationObserver/MutationObserver.js',
'../WebComponents/dom.js',
'base.js',
'module.js',

View File

@@ -1,6 +1,5 @@
[
"../WeakMap/WeakMap.js",
"../MutationObserver/MutationObserver.js",
"../WebComponents/dom.js",
"base.js",
"module.js",

View File

@@ -1,575 +0,0 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function(global) {
// Don't allow this object to be redefined.
if (global.JsMutationObserver) {
return;
}
var registrationsTable = new WeakMap();
var setImmediate;
// As much as we would like to use the native implementation, IE
// (all versions) suffers a rather annoying bug where it will drop or defer
// callbacks when heavy DOM operations are being performed concurrently.
//
// For a thorough discussion on this, see:
// http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
if (/Trident|Edge/.test(navigator.userAgent)) {
// Sadly, this bug also affects postMessage and MessageQueues.
//
// We would like to use the onreadystatechange hack for IE <= 10, but it is
// dangerous in the polyfilled environment due to requiring that the
// observed script element be in the document.
setImmediate = setTimeout;
// If some other browser ever implements it, let's prefer their native
// implementation:
} else if (window.setImmediate) {
setImmediate = window.setImmediate;
// Otherwise, we fall back to postMessage as a means of emulating the next
// task semantics of setImmediate.
} else {
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 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', e.target.parentNode);
record.addedNodes = addedNodes;
record.removedNodes = removedNodes;
record.previousSibling = previousSibling;
record.nextSibling = nextSibling;
forEachAncestorAndObserverEnqueueRecord(e.relatedNode, function(options) {
// 2.1, 3.2
if (!options.childList)
return;
// 2.2, 3.3
return record;
});
}
clearRecords();
}
};
global.JsMutationObserver = JsMutationObserver;
if (!global.MutationObserver) {
global.MutationObserver = JsMutationObserver;
// Explicltly mark MO as polyfilled for user reference.
JsMutationObserver._isPolyfilled = true;
}
})(self);

View File

@@ -1,4 +0,0 @@
[
"../WeakMap/WeakMap.js",
"MutationObserver.js"
]

View File

@@ -26,7 +26,6 @@ Array.prototype.forEach.call(document.querySelectorAll('script[src]'), function(
'wrappers.js',
'ArraySplice.js',
'microtask.js',
'MutationObserver.js',
'TreeScope.js',
'wrappers/events.js',
'wrappers/TouchEvent.js',

View File

@@ -3,7 +3,6 @@
"wrappers.js",
"ArraySplice.js",
"microtask.js",
"MutationObserver.js",
"TreeScope.js",
"wrappers/events.js",
"wrappers/TouchEvent.js",

View File

@@ -2,7 +2,6 @@
"build/boot-lite.js",
"../URL/URL.js",
"../WeakMap/WeakMap.js",
"../MutationObserver/MutationObserver.js",
"../Template/Template.js",
"../HTMLImports/build.json",
"../../es6-promise/es6-promise.auto.min.js",

View File

@@ -1,280 +0,0 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
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'
});
});
});

View File

@@ -1,76 +0,0 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
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');
});
});

View File

@@ -1,110 +0,0 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
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
});
});
});

View File

@@ -1,402 +0,0 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
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
});
});
test('Append child in child', function() {
var div = testDiv.appendChild(document.createElement('div'));
var observer = new JsMutationObserver(function() {});
observer.observe(div, {
childList: true,
subtree: true
});
var div2 = document.createElement('div')
var div3 = div2.appendChild(document.createElement('div'));
div.appendChild(div2);
var records = observer.takeRecords();
if(records.length == 1) {
assert.strictEqual(records[0].target, div);
assert.strictEqual(records[0].addedNodes[0].firstChild, div3);
} else {
assert.strictEqual(records[0].target, div);
assert.strictEqual(records[1].target, div2);
}
});
});

View File

@@ -1,40 +0,0 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
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
});
});
});

View File

@@ -1,78 +0,0 @@
<!doctype html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<meta charset="utf-8">
<!-- MutationObserver -->
<script src="../../src/WeakMap/WeakMap.js"></script>
<script src="../../src/MutationObserver/MutationObserver.js"></script>
<script src="../../../web-component-tester/browser.js"></script>
<script>
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 = Boolean(window.MutationObserver ||
window.WebKitMutationObserver);
var isWebKit = /WebKit/.test(navigator.userAgent);
var slice = Array.prototype.slice.call.bind(Array.prototype.slice);
if (useNative) {
window.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);
};
})();
WCT.loadSuites([
'attributes.js',
'characterData.js',
'childList.js',
'mixed.js',
'callback.js',
'transient.js'
]);
</script>

View File

@@ -1,287 +0,0 @@
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
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'));
});
});

View File

@@ -94,7 +94,6 @@ function expectMutationRecord(record, expected) {
<script src="../js/HTMLTableSectionElement.js"></script>
<script src="../js/HTMLTemplateElement.js"></script>
<script src="../js/HTMLTextAreaElement.js"></script>
<script src="../js/MutationObserver.js"></script>
<script src="../js/MutationObserver/attributes.js"></script>
<script src="../js/MutationObserver/callback.js"></script>
<script src="../js/MutationObserver/characterData.js"></script>