Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(44)

Side by Side Diff: runtime/bin/vmservice/observatory/deployed/web/packages/mutation_observer/mutation_observer.js

Issue 839543002: Revert "Build Observatory with runtime" (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 /*
2 * Copyright 2013 The Polymer Authors. All rights reserved.
3 * Use of this source code is goverened by a BSD-style
4 * license that can be found in the LICENSE file.
5 */
6
7 // TODO(jmesserly): polyfill does not have feature testing or the definition of
8 // SideTable. The extra code is from:
9 // https://github.com/Polymer/CustomElements/blob/master/src/MutationObserver.js
10 // https://github.com/Polymer/CustomElements/blob/master/src/sidetable.js
11 // I also renamed JsMutationObserver -> MutationObserver to correctly interact
12 // with dart2js interceptors.
13
14 if (!window.MutationObserver && !window.WebKitMutationObserver) {
15
16 (function(global) {
17 // SideTable is a weak map where possible. If WeakMap is not available the
18 // association is stored as an expando property.
19 var SideTable;
20 // TODO(arv): WeakMap does not allow for Node etc to be keys in Firefox
21 if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') < 0) {
22 SideTable = WeakMap;
23 } else {
24 (function() {
25 var defineProperty = Object.defineProperty;
26 var hasOwnProperty = Object.hasOwnProperty;
27 var counter = new Date().getTime() % 1e9;
28
29 SideTable = function() {
30 this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
31 };
32
33 SideTable.prototype = {
34 set: function(key, value) {
35 defineProperty(key, this.name, {value: value, writable: true});
36 },
37 get: function(key) {
38 return hasOwnProperty.call(key, this.name) ? key[this.name] : undefine d;
39 },
40 delete: function(key) {
41 this.set(key, undefined);
42 }
43 }
44 })();
45 }
46
47 var registrationsTable = new SideTable();
48
49 // We use setImmediate or postMessage for our future callback.
50 var setImmediate = window.msSetImmediate;
51
52 // Use post message to emulate setImmediate.
53 if (!setImmediate) {
54 var setImmediateQueue = [];
55 var sentinel = String(Math.random());
56 window.addEventListener('message', function(e) {
57 if (e.data === sentinel) {
58 var queue = setImmediateQueue;
59 setImmediateQueue = [];
60 queue.forEach(function(func) {
61 func();
62 });
63 }
64 });
65 setImmediate = function(func) {
66 setImmediateQueue.push(func);
67 window.postMessage(sentinel, '*');
68 };
69 }
70
71 // This is used to ensure that we never schedule 2 callas to setImmediate
72 var isScheduled = false;
73
74 // Keep track of observers that needs to be notified next time.
75 var scheduledObservers = [];
76
77 /**
78 * Schedules |dispatchCallback| to be called in the future.
79 * @param {MutationObserver} observer
80 */
81 function scheduleCallback(observer) {
82 scheduledObservers.push(observer);
83 if (!isScheduled) {
84 isScheduled = true;
85 setImmediate(dispatchCallbacks);
86 }
87 }
88
89 function wrapIfNeeded(node) {
90 return window.ShadowDOMPolyfill &&
91 window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
92 node;
93 }
94
95 function dispatchCallbacks() {
96 // http://dom.spec.whatwg.org/#mutation-observers
97
98 isScheduled = false; // Used to allow a new setImmediate call above.
99
100 var observers = scheduledObservers;
101 scheduledObservers = [];
102 // Sort observers based on their creation UID (incremental).
103 observers.sort(function(o1, o2) {
104 return o1.uid_ - o2.uid_;
105 });
106
107 var anyNonEmpty = false;
108 observers.forEach(function(observer) {
109
110 // 2.1, 2.2
111 var queue = observer.takeRecords();
112 // 2.3. Remove all transient registered observers whose observer is mo.
113 removeTransientObserversFor(observer);
114
115 // 2.4
116 if (queue.length) {
117 observer.callback_(queue, observer);
118 anyNonEmpty = true;
119 }
120 });
121
122 // 3.
123 if (anyNonEmpty)
124 dispatchCallbacks();
125 }
126
127 function removeTransientObserversFor(observer) {
128 observer.nodes_.forEach(function(node) {
129 var registrations = registrationsTable.get(node);
130 if (!registrations)
131 return;
132 registrations.forEach(function(registration) {
133 if (registration.observer === observer)
134 registration.removeTransientObservers();
135 });
136 });
137 }
138
139 /**
140 * This function is used for the "For each registered observer observer (with
141 * observer's options as options) in target's list of registered observers,
142 * run these substeps:" and the "For each ancestor ancestor of target, and for
143 * each registered observer observer (with options options) in ancestor's list
144 * of registered observers, run these substeps:" part of the algorithms. The
145 * |options.subtree| is checked to ensure that the callback is called
146 * correctly.
147 *
148 * @param {Node} target
149 * @param {function(MutationObserverInit):MutationRecord} callback
150 */
151 function forEachAncestorAndObserverEnqueueRecord(target, callback) {
152 for (var node = target; node; node = node.parentNode) {
153 var registrations = registrationsTable.get(node);
154
155 if (registrations) {
156 for (var j = 0; j < registrations.length; j++) {
157 var registration = registrations[j];
158 var options = registration.options;
159
160 // Only target ignores subtree.
161 if (node !== target && !options.subtree)
162 continue;
163
164 var record = callback(options);
165 if (record)
166 registration.enqueue(record);
167 }
168 }
169 }
170 }
171
172 var uidCounter = 0;
173
174 /**
175 * The class that maps to the DOM MutationObserver interface.
176 * @param {Function} callback.
177 * @constructor
178 */
179 function MutationObserver(callback) {
180 this.callback_ = callback;
181 this.nodes_ = [];
182 this.records_ = [];
183 this.uid_ = ++uidCounter;
184 }
185
186 MutationObserver.prototype = {
187 observe: function(target, options) {
188 target = wrapIfNeeded(target);
189
190 // 1.1
191 if (!options.childList && !options.attributes && !options.characterData ||
192
193 // 1.2
194 options.attributeOldValue && !options.attributes ||
195
196 // 1.3
197 options.attributeFilter && options.attributeFilter.length &&
198 !options.attributes ||
199
200 // 1.4
201 options.characterDataOldValue && !options.characterData) {
202
203 throw new SyntaxError();
204 }
205
206 var registrations = registrationsTable.get(target);
207 if (!registrations)
208 registrationsTable.set(target, registrations = []);
209
210 // 2
211 // If target's list of registered observers already includes a registered
212 // observer associated with the context object, replace that registered
213 // observer's options with options.
214 var registration;
215 for (var i = 0; i < registrations.length; i++) {
216 if (registrations[i].observer === this) {
217 registration = registrations[i];
218 registration.removeListeners();
219 registration.options = options;
220 break;
221 }
222 }
223
224 // 3.
225 // Otherwise, add a new registered observer to target's list of registered
226 // observers with the context object as the observer and options as the
227 // options, and add target to context object's list of nodes on which it
228 // is registered.
229 if (!registration) {
230 registration = new Registration(this, target, options);
231 registrations.push(registration);
232 this.nodes_.push(target);
233 }
234
235 registration.addListeners();
236 },
237
238 disconnect: function() {
239 this.nodes_.forEach(function(node) {
240 var registrations = registrationsTable.get(node);
241 for (var i = 0; i < registrations.length; i++) {
242 var registration = registrations[i];
243 if (registration.observer === this) {
244 registration.removeListeners();
245 registrations.splice(i, 1);
246 // Each node can only have one registered observer associated with
247 // this observer.
248 break;
249 }
250 }
251 }, this);
252 this.records_ = [];
253 },
254
255 takeRecords: function() {
256 var copyOfRecords = this.records_;
257 this.records_ = [];
258 return copyOfRecords;
259 }
260 };
261
262 /**
263 * @param {string} type
264 * @param {Node} target
265 * @constructor
266 */
267 function MutationRecord(type, target) {
268 this.type = type;
269 this.target = target;
270 this.addedNodes = [];
271 this.removedNodes = [];
272 this.previousSibling = null;
273 this.nextSibling = null;
274 this.attributeName = null;
275 this.attributeNamespace = null;
276 this.oldValue = null;
277 }
278
279 // TODO(jmesserly): this fixes the interceptor dispatch on IE.
280 // Not sure why this is necessary.
281 MutationObserver.prototype.constructor = MutationObserver;
282 MutationObserver.name = 'MutationObserver';
283 MutationRecord.prototype.constructor = MutationRecord;
284 MutationRecord.name = 'MutationRecord';
285
286 function copyMutationRecord(original) {
287 var record = new MutationRecord(original.type, original.target);
288 record.addedNodes = original.addedNodes.slice();
289 record.removedNodes = original.removedNodes.slice();
290 record.previousSibling = original.previousSibling;
291 record.nextSibling = original.nextSibling;
292 record.attributeName = original.attributeName;
293 record.attributeNamespace = original.attributeNamespace;
294 record.oldValue = original.oldValue;
295 return record;
296 };
297
298 // We keep track of the two (possibly one) records used in a single mutation.
299 var currentRecord, recordWithOldValue;
300
301 /**
302 * Creates a record without |oldValue| and caches it as |currentRecord| for
303 * later use.
304 * @param {string} oldValue
305 * @return {MutationRecord}
306 */
307 function getRecord(type, target) {
308 return currentRecord = new MutationRecord(type, target);
309 }
310
311 /**
312 * Gets or creates a record with |oldValue| based in the |currentRecord|
313 * @param {string} oldValue
314 * @return {MutationRecord}
315 */
316 function getRecordWithOldValue(oldValue) {
317 if (recordWithOldValue)
318 return recordWithOldValue;
319 recordWithOldValue = copyMutationRecord(currentRecord);
320 recordWithOldValue.oldValue = oldValue;
321 return recordWithOldValue;
322 }
323
324 function clearRecords() {
325 currentRecord = recordWithOldValue = undefined;
326 }
327
328 /**
329 * @param {MutationRecord} record
330 * @return {boolean} Whether the record represents a record from the current
331 * mutation event.
332 */
333 function recordRepresentsCurrentMutation(record) {
334 return record === recordWithOldValue || record === currentRecord;
335 }
336
337 /**
338 * Selects which record, if any, to replace the last record in the queue.
339 * This returns |null| if no record should be replaced.
340 *
341 * @param {MutationRecord} lastRecord
342 * @param {MutationRecord} newRecord
343 * @param {MutationRecord}
344 */
345 function selectRecord(lastRecord, newRecord) {
346 if (lastRecord === newRecord)
347 return lastRecord;
348
349 // Check if the the record we are adding represents the same record. If
350 // so, we keep the one with the oldValue in it.
351 if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
352 return recordWithOldValue;
353
354 return null;
355 }
356
357 /**
358 * Class used to represent a registered observer.
359 * @param {MutationObserver} observer
360 * @param {Node} target
361 * @param {MutationObserverInit} options
362 * @constructor
363 */
364 function Registration(observer, target, options) {
365 this.observer = observer;
366 this.target = target;
367 this.options = options;
368 this.transientObservedNodes = [];
369 }
370
371 Registration.prototype = {
372 enqueue: function(record) {
373 var records = this.observer.records_;
374 var length = records.length;
375
376 // There are cases where we replace the last record with the new record.
377 // For example if the record represents the same mutation we need to use
378 // the one with the oldValue. If we get same record (this can happen as we
379 // walk up the tree) we ignore the new record.
380 if (records.length > 0) {
381 var lastRecord = records[length - 1];
382 var recordToReplaceLast = selectRecord(lastRecord, record);
383 if (recordToReplaceLast) {
384 records[length - 1] = recordToReplaceLast;
385 return;
386 }
387 } else {
388 scheduleCallback(this.observer);
389 }
390
391 records[length] = record;
392 },
393
394 addListeners: function() {
395 this.addListeners_(this.target);
396 },
397
398 addListeners_: function(node) {
399 var options = this.options;
400 if (options.attributes)
401 node.addEventListener('DOMAttrModified', this, true);
402
403 if (options.characterData)
404 node.addEventListener('DOMCharacterDataModified', this, true);
405
406 if (options.childList)
407 node.addEventListener('DOMNodeInserted', this, true);
408
409 if (options.childList || options.subtree)
410 node.addEventListener('DOMNodeRemoved', this, true);
411 },
412
413 removeListeners: function() {
414 this.removeListeners_(this.target);
415 },
416
417 removeListeners_: function(node) {
418 var options = this.options;
419 if (options.attributes)
420 node.removeEventListener('DOMAttrModified', this, true);
421
422 if (options.characterData)
423 node.removeEventListener('DOMCharacterDataModified', this, true);
424
425 if (options.childList)
426 node.removeEventListener('DOMNodeInserted', this, true);
427
428 if (options.childList || options.subtree)
429 node.removeEventListener('DOMNodeRemoved', this, true);
430 },
431
432 /**
433 * Adds a transient observer on node. The transient observer gets removed
434 * next time we deliver the change records.
435 * @param {Node} node
436 */
437 addTransientObserver: function(node) {
438 // Don't add transient observers on the target itself. We already have all
439 // the required listeners set up on the target.
440 if (node === this.target)
441 return;
442
443 this.addListeners_(node);
444 this.transientObservedNodes.push(node);
445 var registrations = registrationsTable.get(node);
446 if (!registrations)
447 registrationsTable.set(node, registrations = []);
448
449 // We know that registrations does not contain this because we already
450 // checked if node === this.target.
451 registrations.push(this);
452 },
453
454 removeTransientObservers: function() {
455 var transientObservedNodes = this.transientObservedNodes;
456 this.transientObservedNodes = [];
457
458 transientObservedNodes.forEach(function(node) {
459 // Transient observers are never added to the target.
460 this.removeListeners_(node);
461
462 var registrations = registrationsTable.get(node);
463 for (var i = 0; i < registrations.length; i++) {
464 if (registrations[i] === this) {
465 registrations.splice(i, 1);
466 // Each node can only have one registered observer associated with
467 // this observer.
468 break;
469 }
470 }
471 }, this);
472 },
473
474 handleEvent: function(e) {
475 // Stop propagation since we are managing the propagation manually.
476 // This means that other mutation events on the page will not work
477 // correctly but that is by design.
478 e.stopImmediatePropagation();
479
480 switch (e.type) {
481 case 'DOMAttrModified':
482 // http://dom.spec.whatwg.org/#concept-mo-queue-attributes
483
484 var name = e.attrName;
485 var namespace = e.relatedNode.namespaceURI;
486 var target = e.target;
487
488 // 1.
489 var record = new getRecord('attributes', target);
490 record.attributeName = name;
491 record.attributeNamespace = namespace;
492
493 // 2.
494 var oldValue =
495 e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
496
497 forEachAncestorAndObserverEnqueueRecord(target, function(options) {
498 // 3.1, 4.2
499 if (!options.attributes)
500 return;
501
502 // 3.2, 4.3
503 if (options.attributeFilter && options.attributeFilter.length &&
504 options.attributeFilter.indexOf(name) === -1 &&
505 options.attributeFilter.indexOf(namespace) === -1) {
506 return;
507 }
508 // 3.3, 4.4
509 if (options.attributeOldValue)
510 return getRecordWithOldValue(oldValue);
511
512 // 3.4, 4.5
513 return record;
514 });
515
516 break;
517
518 case 'DOMCharacterDataModified':
519 // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
520 var target = e.target;
521
522 // 1.
523 var record = getRecord('characterData', target);
524
525 // 2.
526 var oldValue = e.prevValue;
527
528
529 forEachAncestorAndObserverEnqueueRecord(target, function(options) {
530 // 3.1, 4.2
531 if (!options.characterData)
532 return;
533
534 // 3.2, 4.3
535 if (options.characterDataOldValue)
536 return getRecordWithOldValue(oldValue);
537
538 // 3.3, 4.4
539 return record;
540 });
541
542 break;
543
544 case 'DOMNodeRemoved':
545 this.addTransientObserver(e.target);
546 // Fall through.
547 case 'DOMNodeInserted':
548 // http://dom.spec.whatwg.org/#concept-mo-queue-childlist
549 var target = e.relatedNode;
550 var changedNode = e.target;
551 var addedNodes, removedNodes;
552 if (e.type === 'DOMNodeInserted') {
553 addedNodes = [changedNode];
554 removedNodes = [];
555 } else {
556
557 addedNodes = [];
558 removedNodes = [changedNode];
559 }
560 var previousSibling = changedNode.previousSibling;
561 var nextSibling = changedNode.nextSibling;
562
563 // 1.
564 var record = getRecord('childList', target);
565 record.addedNodes = addedNodes;
566 record.removedNodes = removedNodes;
567 record.previousSibling = previousSibling;
568 record.nextSibling = nextSibling;
569
570 forEachAncestorAndObserverEnqueueRecord(target, function(options) {
571 // 2.1, 3.2
572 if (!options.childList)
573 return;
574
575 // 2.2, 3.3
576 return record;
577 });
578
579 }
580
581 clearRecords();
582 }
583 };
584
585 global.MutationObserver = MutationObserver;
586 })(window);
587
588 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698