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

Side by Side Diff: pkg/custom_element/lib/custom-elements.debug.js

Issue 22967006: Adding polyfill support for custom elements. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Removing mutation observer polyfill from test. Created 7 years, 4 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 // Copyright (c) 2012 The Polymer Authors. All rights reserved.
Jennifer Messerly 2013/08/20 19:09:59 from the comment on earlier CL's, should this be n
blois 2013/08/20 19:36:56 My goal was that the file can be copied over from
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 // * Neither the name of Google Inc. nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 (function() {
29
30 var scope = window.PolymerLoader = {};
31 var flags = {};
32
33 // convert url arguments to flags
34
35 if (!flags.noOpts) {
36 location.search.slice(1).split('&').forEach(function(o) {
37 o = o.split('=');
38 o[0] && (flags[o[0]] = o[1] || true);
39 });
40 }
41
42 // process global logFlags
43
44 parseLogFlags(flags);
45
46 function load(scopeName) {
47 // imports
48
49 var scope = window[scopeName];
50 var entryPointName = scope.entryPointName;
51 var processFlags = scope.processFlags;
52
53 // acquire attributes and base path from entry point
54
55 var entryPoint = findScript(entryPointName);
56 var base = entryPoint.basePath;
57
58 // acquire common flags
59 var flags = scope.flags;
60
61 // convert attributes to flags
62 var flags = PolymerLoader.flags;
63 for (var i=0, a; (a=entryPoint.attributes[i]); i++) {
64 if (a.name !== 'src') {
65 flags[a.name] = a.value || true;
66 }
67 }
68
69 // parse log flags into global
70 parseLogFlags(flags);
71
72 // exports
73
74 scope.basePath = base;
75 scope.flags = flags;
76
77 // process flags for dynamic dependencies
78
79 if (processFlags) {
80 processFlags.call(scope, flags);
81 }
82
83 // post-process imports
84
85 var modules = scope.modules || [];
86 var sheets = scope.sheets || [];
87
88 // write script tags for dependencies
89
90 modules.forEach(function(src) {
91 document.write('<script src="' + base + src + '"></script>');
92 });
93
94 // write link tags for styles
95
96 sheets.forEach(function(src) {
97 document.write('<link rel="stylesheet" href="' + base + src + '">');
98 });
99 }
100
101 // utility method
102
103 function findScript(fileName) {
104 var script = document.querySelector('script[src*="' + fileName + '"]');
105 var src = script.attributes.src.value;
106 script.basePath = src.slice(0, src.indexOf(fileName));
107 return script;
108 }
109
110 function parseLogFlags(flags) {
111 var logFlags = window.logFlags = window.logFlags || {};
112 if (flags.log) {
113 flags.log.split(',').forEach(function(f) {
114 logFlags[f] = true;
115 });
116 }
117 }
118
119 scope.flags = flags;
120 scope.load = load;
121
122 })();
123
124 window.CustomElements = {flags:{}};
125 // SideTable is a weak map where possible. If WeakMap is not available the
126 // association is stored as an expando property.
127 var SideTable;
128 // TODO(arv): WeakMap does not allow for Node etc to be keys in Firefox
129 if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') < 0) {
130 SideTable = WeakMap;
131 } else {
132 (function() {
133 var defineProperty = Object.defineProperty;
134 var hasOwnProperty = Object.hasOwnProperty;
135 var counter = new Date().getTime() % 1e9;
136
137 SideTable = function() {
138 this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
139 };
140
141 SideTable.prototype = {
142 set: function(key, value) {
143 defineProperty(key, this.name, {value: value, writable: true});
144 },
145 get: function(key) {
146 return hasOwnProperty.call(key, this.name) ? key[this.name] : undefined;
147 },
148 delete: function(key) {
149 this.set(key, undefined);
150 }
151 }
152 })();
153 }
154
155 (function(global) {
156
157 var registrationsTable = new SideTable();
158
159 // We use setImmediate or postMessage for our future callback.
160 var setImmediate = window.msSetImmediate;
161
162 // Use post message to emulate setImmediate.
163 if (!setImmediate) {
164 var setImmediateQueue = [];
165 var sentinel = String(Math.random());
166 window.addEventListener('message', function(e) {
167 if (e.data === sentinel) {
168 var queue = setImmediateQueue;
169 setImmediateQueue = [];
170 queue.forEach(function(func) {
171 func();
172 });
173 }
174 });
175 setImmediate = function(func) {
176 setImmediateQueue.push(func);
177 window.postMessage(sentinel, '*');
178 };
179 }
180
181 // This is used to ensure that we never schedule 2 callas to setImmediate
182 var isScheduled = false;
183
184 // Keep track of observers that needs to be notified next time.
185 var scheduledObservers = [];
186
187 /**
188 * Schedules |dispatchCallback| to be called in the future.
189 * @param {MutationObserver} observer
190 */
191 function scheduleCallback(observer) {
192 scheduledObservers.push(observer);
193 if (!isScheduled) {
194 isScheduled = true;
195 setImmediate(dispatchCallbacks);
196 }
197 }
198
199 function wrapIfNeeded(node) {
200 return window.ShadowDOMPolyfill &&
201 window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
202 node;
203 }
204
205 function dispatchCallbacks() {
206 // http://dom.spec.whatwg.org/#mutation-observers
207
208 isScheduled = false; // Used to allow a new setImmediate call above.
209
210 var observers = scheduledObservers;
211 scheduledObservers = [];
212 // Sort observers based on their creation UID (incremental).
213 observers.sort(function(o1, o2) {
214 return o1.uid_ - o2.uid_;
215 });
216
217 var anyNonEmpty = false;
218 observers.forEach(function(observer) {
219
220 // 2.1, 2.2
221 var queue = observer.takeRecords();
222 // 2.3. Remove all transient registered observers whose observer is mo.
223 removeTransientObserversFor(observer);
224
225 // 2.4
226 if (queue.length) {
227 observer.callback_(queue, observer);
228 anyNonEmpty = true;
229 }
230 });
231
232 // 3.
233 if (anyNonEmpty)
234 dispatchCallbacks();
235 }
236
237 function removeTransientObserversFor(observer) {
238 observer.nodes_.forEach(function(node) {
239 var registrations = registrationsTable.get(node);
240 if (!registrations)
241 return;
242 registrations.forEach(function(registration) {
243 if (registration.observer === observer)
244 registration.removeTransientObservers();
245 });
246 });
247 }
248
249 /**
250 * This function is used for the "For each registered observer observer (with
251 * observer's options as options) in target's list of registered observers,
252 * run these substeps:" and the "For each ancestor ancestor of target, and for
253 * each registered observer observer (with options options) in ancestor's list
254 * of registered observers, run these substeps:" part of the algorithms. The
255 * |options.subtree| is checked to ensure that the callback is called
256 * correctly.
257 *
258 * @param {Node} target
259 * @param {function(MutationObserverInit):MutationRecord} callback
260 */
261 function forEachAncestorAndObserverEnqueueRecord(target, callback) {
262 for (var node = target; node; node = node.parentNode) {
263 var registrations = registrationsTable.get(node);
264
265 if (registrations) {
266 for (var j = 0; j < registrations.length; j++) {
267 var registration = registrations[j];
268 var options = registration.options;
269
270 // Only target ignores subtree.
271 if (node !== target && !options.subtree)
272 continue;
273
274 var record = callback(options);
275 if (record)
276 registration.enqueue(record);
277 }
278 }
279 }
280 }
281
282 var uidCounter = 0;
283
284 /**
285 * The class that maps to the DOM MutationObserver interface.
286 * @param {Function} callback.
287 * @constructor
288 */
289 function JsMutationObserver(callback) {
290 this.callback_ = callback;
291 this.nodes_ = [];
292 this.records_ = [];
293 this.uid_ = ++uidCounter;
294 }
295
296 JsMutationObserver.prototype = {
297 observe: function(target, options) {
298 target = wrapIfNeeded(target);
299
300 // 1.1
301 if (!options.childList && !options.attributes && !options.characterData ||
302
303 // 1.2
304 options.attributeOldValue && !options.attributes ||
305
306 // 1.3
307 options.attributeFilter && options.attributeFilter.length &&
308 !options.attributes ||
309
310 // 1.4
311 options.characterDataOldValue && !options.characterData) {
312
313 throw new SyntaxError();
314 }
315
316 var registrations = registrationsTable.get(target);
317 if (!registrations)
318 registrationsTable.set(target, registrations = []);
319
320 // 2
321 // If target's list of registered observers already includes a registered
322 // observer associated with the context object, replace that registered
323 // observer's options with options.
324 var registration;
325 for (var i = 0; i < registrations.length; i++) {
326 if (registrations[i].observer === this) {
327 registration = registrations[i];
328 registration.removeListeners();
329 registration.options = options;
330 break;
331 }
332 }
333
334 // 3.
335 // Otherwise, add a new registered observer to target's list of registered
336 // observers with the context object as the observer and options as the
337 // options, and add target to context object's list of nodes on which it
338 // is registered.
339 if (!registration) {
340 registration = new Registration(this, target, options);
341 registrations.push(registration);
342 this.nodes_.push(target);
343 }
344
345 registration.addListeners();
346 },
347
348 disconnect: function() {
349 this.nodes_.forEach(function(node) {
350 var registrations = registrationsTable.get(node);
351 for (var i = 0; i < registrations.length; i++) {
352 var registration = registrations[i];
353 if (registration.observer === this) {
354 registration.removeListeners();
355 registrations.splice(i, 1);
356 // Each node can only have one registered observer associated with
357 // this observer.
358 break;
359 }
360 }
361 }, this);
362 this.records_ = [];
363 },
364
365 takeRecords: function() {
366 var copyOfRecords = this.records_;
367 this.records_ = [];
368 return copyOfRecords;
369 }
370 };
371
372 /**
373 * @param {string} type
374 * @param {Node} target
375 * @constructor
376 */
377 function MutationRecord(type, target) {
378 this.type = type;
379 this.target = target;
380 this.addedNodes = [];
381 this.removedNodes = [];
382 this.previousSibling = null;
383 this.nextSibling = null;
384 this.attributeName = null;
385 this.attributeNamespace = null;
386 this.oldValue = null;
387 }
388
389 function copyMutationRecord(original) {
390 var record = new MutationRecord(original.type, original.target);
391 record.addedNodes = original.addedNodes.slice();
392 record.removedNodes = original.removedNodes.slice();
393 record.previousSibling = original.previousSibling;
394 record.nextSibling = original.nextSibling;
395 record.attributeName = original.attributeName;
396 record.attributeNamespace = original.attributeNamespace;
397 record.oldValue = original.oldValue;
398 return record;
399 };
400
401 // We keep track of the two (possibly one) records used in a single mutation.
402 var currentRecord, recordWithOldValue;
403
404 /**
405 * Creates a record without |oldValue| and caches it as |currentRecord| for
406 * later use.
407 * @param {string} oldValue
408 * @return {MutationRecord}
409 */
410 function getRecord(type, target) {
411 return currentRecord = new MutationRecord(type, target);
412 }
413
414 /**
415 * Gets or creates a record with |oldValue| based in the |currentRecord|
416 * @param {string} oldValue
417 * @return {MutationRecord}
418 */
419 function getRecordWithOldValue(oldValue) {
420 if (recordWithOldValue)
421 return recordWithOldValue;
422 recordWithOldValue = copyMutationRecord(currentRecord);
423 recordWithOldValue.oldValue = oldValue;
424 return recordWithOldValue;
425 }
426
427 function clearRecords() {
428 currentRecord = recordWithOldValue = undefined;
429 }
430
431 /**
432 * @param {MutationRecord} record
433 * @return {boolean} Whether the record represents a record from the current
434 * mutation event.
435 */
436 function recordRepresentsCurrentMutation(record) {
437 return record === recordWithOldValue || record === currentRecord;
438 }
439
440 /**
441 * Selects which record, if any, to replace the last record in the queue.
442 * This returns |null| if no record should be replaced.
443 *
444 * @param {MutationRecord} lastRecord
445 * @param {MutationRecord} newRecord
446 * @param {MutationRecord}
447 */
448 function selectRecord(lastRecord, newRecord) {
449 if (lastRecord === newRecord)
450 return lastRecord;
451
452 // Check if the the record we are adding represents the same record. If
453 // so, we keep the one with the oldValue in it.
454 if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
455 return recordWithOldValue;
456
457 return null;
458 }
459
460 /**
461 * Class used to represent a registered observer.
462 * @param {MutationObserver} observer
463 * @param {Node} target
464 * @param {MutationObserverInit} options
465 * @constructor
466 */
467 function Registration(observer, target, options) {
468 this.observer = observer;
469 this.target = target;
470 this.options = options;
471 this.transientObservedNodes = [];
472 }
473
474 Registration.prototype = {
475 enqueue: function(record) {
476 var records = this.observer.records_;
477 var length = records.length;
478
479 // There are cases where we replace the last record with the new record.
480 // For example if the record represents the same mutation we need to use
481 // the one with the oldValue. If we get same record (this can happen as we
482 // walk up the tree) we ignore the new record.
483 if (records.length > 0) {
484 var lastRecord = records[length - 1];
485 var recordToReplaceLast = selectRecord(lastRecord, record);
486 if (recordToReplaceLast) {
487 records[length - 1] = recordToReplaceLast;
488 return;
489 }
490 } else {
491 scheduleCallback(this.observer);
492 }
493
494 records[length] = record;
495 },
496
497 addListeners: function() {
498 this.addListeners_(this.target);
499 },
500
501 addListeners_: function(node) {
502 var options = this.options;
503 if (options.attributes)
504 node.addEventListener('DOMAttrModified', this, true);
505
506 if (options.characterData)
507 node.addEventListener('DOMCharacterDataModified', this, true);
508
509 if (options.childList)
510 node.addEventListener('DOMNodeInserted', this, true);
511
512 if (options.childList || options.subtree)
513 node.addEventListener('DOMNodeRemoved', this, true);
514 },
515
516 removeListeners: function() {
517 this.removeListeners_(this.target);
518 },
519
520 removeListeners_: function(node) {
521 var options = this.options;
522 if (options.attributes)
523 node.removeEventListener('DOMAttrModified', this, true);
524
525 if (options.characterData)
526 node.removeEventListener('DOMCharacterDataModified', this, true);
527
528 if (options.childList)
529 node.removeEventListener('DOMNodeInserted', this, true);
530
531 if (options.childList || options.subtree)
532 node.removeEventListener('DOMNodeRemoved', this, true);
533 },
534
535 /**
536 * Adds a transient observer on node. The transient observer gets removed
537 * next time we deliver the change records.
538 * @param {Node} node
539 */
540 addTransientObserver: function(node) {
541 // Don't add transient observers on the target itself. We already have all
542 // the required listeners set up on the target.
543 if (node === this.target)
544 return;
545
546 this.addListeners_(node);
547 this.transientObservedNodes.push(node);
548 var registrations = registrationsTable.get(node);
549 if (!registrations)
550 registrationsTable.set(node, registrations = []);
551
552 // We know that registrations does not contain this because we already
553 // checked if node === this.target.
554 registrations.push(this);
555 },
556
557 removeTransientObservers: function() {
558 var transientObservedNodes = this.transientObservedNodes;
559 this.transientObservedNodes = [];
560
561 transientObservedNodes.forEach(function(node) {
562 // Transient observers are never added to the target.
563 this.removeListeners_(node);
564
565 var registrations = registrationsTable.get(node);
566 for (var i = 0; i < registrations.length; i++) {
567 if (registrations[i] === this) {
568 registrations.splice(i, 1);
569 // Each node can only have one registered observer associated with
570 // this observer.
571 break;
572 }
573 }
574 }, this);
575 },
576
577 handleEvent: function(e) {
578 // Stop propagation since we are managing the propagation manually.
579 // This means that other mutation events on the page will not work
580 // correctly but that is by design.
581 e.stopImmediatePropagation();
582
583 switch (e.type) {
584 case 'DOMAttrModified':
585 // http://dom.spec.whatwg.org/#concept-mo-queue-attributes
586
587 var name = e.attrName;
588 var namespace = e.relatedNode.namespaceURI;
589 var target = e.target;
590
591 // 1.
592 var record = new getRecord('attributes', target);
593 record.attributeName = name;
594 record.attributeNamespace = namespace;
595
596 // 2.
597 var oldValue =
598 e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
599
600 forEachAncestorAndObserverEnqueueRecord(target, function(options) {
601 // 3.1, 4.2
602 if (!options.attributes)
603 return;
604
605 // 3.2, 4.3
606 if (options.attributeFilter && options.attributeFilter.length &&
607 options.attributeFilter.indexOf(name) === -1 &&
608 options.attributeFilter.indexOf(namespace) === -1) {
609 return;
610 }
611 // 3.3, 4.4
612 if (options.attributeOldValue)
613 return getRecordWithOldValue(oldValue);
614
615 // 3.4, 4.5
616 return record;
617 });
618
619 break;
620
621 case 'DOMCharacterDataModified':
622 // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
623 var target = e.target;
624
625 // 1.
626 var record = getRecord('characterData', target);
627
628 // 2.
629 var oldValue = e.prevValue;
630
631
632 forEachAncestorAndObserverEnqueueRecord(target, function(options) {
633 // 3.1, 4.2
634 if (!options.characterData)
635 return;
636
637 // 3.2, 4.3
638 if (options.characterDataOldValue)
639 return getRecordWithOldValue(oldValue);
640
641 // 3.3, 4.4
642 return record;
643 });
644
645 break;
646
647 case 'DOMNodeRemoved':
648 this.addTransientObserver(e.target);
649 // Fall through.
650 case 'DOMNodeInserted':
651 // http://dom.spec.whatwg.org/#concept-mo-queue-childlist
652 var target = e.relatedNode;
653 var changedNode = e.target;
654 var addedNodes, removedNodes;
655 if (e.type === 'DOMNodeInserted') {
656 addedNodes = [changedNode];
657 removedNodes = [];
658 } else {
659
660 addedNodes = [];
661 removedNodes = [changedNode];
662 }
663 var previousSibling = changedNode.previousSibling;
664 var nextSibling = changedNode.nextSibling;
665
666 // 1.
667 var record = getRecord('childList', target);
668 record.addedNodes = addedNodes;
669 record.removedNodes = removedNodes;
670 record.previousSibling = previousSibling;
671 record.nextSibling = nextSibling;
672
673 forEachAncestorAndObserverEnqueueRecord(target, function(options) {
674 // 2.1, 3.2
675 if (!options.childList)
676 return;
677
678 // 2.2, 3.3
679 return record;
680 });
681
682 }
683
684 clearRecords();
685 }
686 };
687
688 global.JsMutationObserver = JsMutationObserver;
689
690 })(this);
691
692 if (!window.MutationObserver) {
693 window.MutationObserver =
694 window.WebKitMutationObserver ||
695 window.JsMutationObserver;
696 if (!MutationObserver) {
697 throw new Error("no mutation observer support");
698 }
699 }
700
701 (function(scope){
702
703 /*
704 if (HTMLElement.prototype.webkitShadowRoot) {
705 Object.defineProperty(HTMLElement.prototype, 'shadowRoot', {
706 get: function() {
707 return this.webkitShadowRoot;
708 }
709 };
710 }
711 */
712
713 // walk the subtree rooted at node, applying 'find(element, data)' function
714 // to each element
715 // if 'find' returns true for 'element', do not search element's subtree
716 function findAll(node, find, data) {
717 var e = node.firstElementChild;
718 if (!e) {
719 e = node.firstChild;
720 while (e && e.nodeType !== Node.ELEMENT_NODE) {
721 e = e.nextSibling;
722 }
723 }
724 while (e) {
725 if (find(e, data) !== true) {
726 findAll(e, find, data);
727 }
728 e = e.nextElementSibling;
729 }
730 return null;
731 }
732
733 // walk all shadowRoots on a given node.
734 function forRoots(node, cb) {
735 var root = node.webkitShadowRoot;
736 while(root) {
737 forSubtree(root, cb);
738 root = root.olderShadowRoot;
739 }
740 }
741
742 // walk the subtree rooted at node, including descent into shadow-roots,
743 // applying 'cb' to each element
744 function forSubtree(node, cb) {
745 //logFlags.dom && node.childNodes && node.childNodes.length && console.group(' subTree: ', node);
746 findAll(node, function(e) {
747 if (cb(e)) {
748 return true;
749 }
750 forRoots(e, cb);
751 });
752 forRoots(node, cb);
753 //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEn d();
754 }
755
756 // manage lifecycle on added node
757 function added(node) {
758 if (upgrade(node)) {
759 insertedNode(node);
760 return true;
761 }
762 inserted(node);
763 }
764
765 // manage lifecycle on added node's subtree only
766 function addedSubtree(node) {
767 forSubtree(node, function(e) {
768 if (added(e)) {
769 return true;
770 }
771 });
772 }
773
774 // manage lifecycle on added node and it's subtree
775 function addedNode(node) {
776 return added(node) || addedSubtree(node);
777 }
778
779 // upgrade custom elements at node, if applicable
780 function upgrade(node) {
781 if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) {
782 var type = node.getAttribute('is') || node.localName;
783 var definition = scope.registry[type];
784 if (definition) {
785 logFlags.dom && console.group('upgrade:', node.localName);
786 scope.upgrade(node);
787 logFlags.dom && console.groupEnd();
788 return true;
789 }
790 }
791 }
792
793 function insertedNode(node) {
794 inserted(node);
795 if (inDocument(node)) {
796 forSubtree(node, function(e) {
797 inserted(e);
798 });
799 }
800 }
801
802 // TODO(sjmiles): if there are descents into trees that can never have inDocumen t(*) true, fix this
803
804 function inserted(element) {
805 // TODO(sjmiles): it's possible we were inserted and removed in the space
806 // of one microtask, in which case we won't be 'inDocument' here
807 // But there are other cases where we are testing for inserted without
808 // specific knowledge of mutations, and must test 'inDocument' to determine
809 // whether to call inserted
810 // If we can factor these cases into separate code paths we can have
811 // better diagnostics.
812 // TODO(sjmiles): when logging, do work on all custom elements so we can
813 // track behavior even when callbacks not defined
814 //console.log('inserted: ', element.localName);
815 if (element.enteredDocumentCallback || (element.__upgraded__ && logFlags.dom)) {
816 logFlags.dom && console.group('inserted:', element.localName);
817 if (inDocument(element)) {
818 element.__inserted = (element.__inserted || 0) + 1;
819 // if we are in a 'removed' state, bluntly adjust to an 'inserted' state
820 if (element.__inserted < 1) {
821 element.__inserted = 1;
822 }
823 // if we are 'over inserted', squelch the callback
824 if (element.__inserted > 1) {
825 logFlags.dom && console.warn('inserted:', element.localName,
826 'insert/remove count:', element.__inserted)
827 } else if (element.enteredDocumentCallback) {
828 logFlags.dom && console.log('inserted:', element.localName);
829 element.enteredDocumentCallback();
830 }
831 }
832 logFlags.dom && console.groupEnd();
833 }
834 }
835
836 function removedNode(node) {
837 removed(node);
838 forSubtree(node, function(e) {
839 removed(e);
840 });
841 }
842
843 function removed(element) {
844 // TODO(sjmiles): temporary: do work on all custom elements so we can track
845 // behavior even when callbacks not defined
846 if (element.leftDocumentCallback || (element.__upgraded__ && logFlags.dom)) {
847 logFlags.dom && console.log('removed:', element.localName);
848 if (!inDocument(element)) {
849 element.__inserted = (element.__inserted || 0) - 1;
850 // if we are in a 'inserted' state, bluntly adjust to an 'removed' state
851 if (element.__inserted > 0) {
852 element.__inserted = 0;
853 }
854 // if we are 'over removed', squelch the callback
855 if (element.__inserted < 0) {
856 logFlags.dom && console.warn('removed:', element.localName,
857 'insert/remove count:', element.__inserted)
858 } else if (element.leftDocumentCallback) {
859 element.leftDocumentCallback();
860 }
861 }
862 }
863 }
864
865 function inDocument(element) {
866 var p = element;
867 while (p) {
868 if (p == element.ownerDocument) {
869 return true;
870 }
871 p = p.parentNode || p.host;
872 }
873 }
874
875 function watchShadow(node) {
876 if (node.webkitShadowRoot && !node.webkitShadowRoot.__watched) {
877 logFlags.dom && console.log('watching shadow-root for: ', node.localName);
878 // watch all unwatched roots...
879 var root = node.webkitShadowRoot;
880 while (root) {
881 watchRoot(root);
882 root = root.olderShadowRoot;
883 }
884 }
885 }
886
887 function watchRoot(root) {
888 if (!root.__watched) {
889 observe(root);
890 root.__watched = true;
891 }
892 }
893
894 function watchAllShadows(node) {
895 watchShadow(node);
896 forSubtree(node, function(e) {
897 watchShadow(node);
898 });
899 }
900
901 function filter(inNode) {
902 switch (inNode.localName) {
903 case 'style':
904 case 'script':
905 case 'template':
906 case undefined:
907 return true;
908 }
909 }
910
911 function handler(mutations) {
912 //
913 if (logFlags.dom) {
914 var mx = mutations[0];
915 if (mx && mx.type === 'childList' && mx.addedNodes) {
916 if (mx.addedNodes) {
917 var d = mx.addedNodes[0];
918 while (d && d !== document && !d.host) {
919 d = d.parentNode;
920 }
921 var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || '';
922 u = u.split('/?').shift().split('/').pop();
923 }
924 }
925 console.group('mutations (%d) [%s]', mutations.length, u || '');
926 }
927 //
928 mutations.forEach(function(mx) {
929 //logFlags.dom && console.group('mutation');
930 if (mx.type === 'childList') {
931 forEach(mx.addedNodes, function(n) {
932 //logFlags.dom && console.log(n.localName);
933 if (filter(n)) {
934 return;
935 }
936 // watch shadow-roots on nodes that have had them attached manually
937 // TODO(sjmiles): remove if createShadowRoot is overridden
938 // TODO(sjmiles): removed as an optimization, manual shadow roots
939 // must be watched explicitly
940 //watchAllShadows(n);
941 // nodes added may need lifecycle management
942 addedNode(n);
943 });
944 // removed nodes may need lifecycle management
945 forEach(mx.removedNodes, function(n) {
946 //logFlags.dom && console.log(n.localName);
947 if (filter(n)) {
948 return;
949 }
950 removedNode(n);
951 });
952 }
953 //logFlags.dom && console.groupEnd();
954 });
955 logFlags.dom && console.groupEnd();
956 };
957
958 var observer = new MutationObserver(handler);
959
960 function takeRecords() {
961 // TODO(sjmiles): ask Raf why we have to call handler ourselves
962 handler(observer.takeRecords());
963 }
964
965 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
966
967 function observe(inRoot) {
968 observer.observe(inRoot, {childList: true, subtree: true});
969 }
970
971 function observeDocument(document) {
972 observe(document);
973 }
974
975 function upgradeDocument(document) {
976 logFlags.dom && console.group('upgradeDocument: ', (document.URL || document._ URL || '').split('/').pop());
977 addedNode(document);
978 logFlags.dom && console.groupEnd();
979 }
980
981 // exports
982
983 scope.watchShadow = watchShadow;
984 scope.watchAllShadows = watchAllShadows;
985
986 scope.upgradeAll = addedNode;
987 scope.upgradeSubtree = addedSubtree;
988
989 scope.observeDocument = observeDocument;
990 scope.upgradeDocument = upgradeDocument;
991
992 scope.takeRecords = takeRecords;
993
994 })(window.CustomElements);
995
996 /**
997 * Implements `document.register`
998 * @module CustomElements
999 */
1000
1001 /**
1002 * Polyfilled extensions to the `document` object.
1003 * @class Document
1004 */
1005
1006 (function(scope) {
1007
1008 // imports
1009
1010 if (!scope) {
1011 scope = window.CustomElements = {flags:{}};
1012 }
1013 var flags = scope.flags;
1014
1015 // native document.register?
1016
1017 var hasNative = Boolean(document.webkitRegister || document.register);
1018 var useNative = !flags.register && hasNative;
1019
1020 if (useNative) {
1021
1022 // normalize
1023 document.register = document.register || document.webkitRegister;
1024
1025 // stub
1026 var nop = function() {};
1027
1028 // exports
1029 scope.registry = {};
1030 scope.upgradeElement = nop;
1031
1032 scope.watchShadow = nop;
1033 scope.watchAllShadows = nop;
1034 scope.upgrade = nop;
1035 scope.upgradeAll = nop;
1036 scope.upgradeSubtree = nop;
1037 scope.observeDocument = nop;
1038 scope.upgradeDocument = nop;
1039 scope.takeRecords = nop;
1040
1041 } else {
1042
1043 /**
1044 * Registers a custom tag name with the document.
1045 *
1046 * When a registered element is created, a `readyCallback` method is called
1047 * in the scope of the element. The `readyCallback` method can be specified on
1048 * either `inOptions.prototype` or `inOptions.lifecycle` with the latter takin g
1049 * precedence.
1050 *
1051 * @method register
1052 * @param {String} inName The tag name to register. Must include a dash ('-'),
1053 * for example 'x-component'.
1054 * @param {Object} inOptions
1055 * @param {String} [inOptions.extends]
1056 * (_off spec_) Tag name of an element to extend (or blank for a new
1057 * element). This parameter is not part of the specification, but instead
1058 * is a hint for the polyfill because the extendee is difficult to infer.
1059 * Remember that the input prototype must chain to the extended element's
1060 * prototype (or HTMLElement.prototype) regardless of the value of
1061 * `extends`.
1062 * @param {Object} inOptions.prototype The prototype to use for the new
1063 * element. The prototype must inherit from HTMLElement.
1064 * @param {Object} [inOptions.lifecycle]
1065 * Callbacks that fire at important phases in the life of the custom
1066 * element.
1067 *
1068 * @example
1069 * FancyButton = document.register("fancy-button", {
1070 * extends: 'button',
1071 * prototype: Object.create(HTMLButtonElement.prototype, {
1072 * readyCallback: {
1073 * value: function() {
1074 * console.log("a fancy-button was created",
1075 * }
1076 * }
1077 * })
1078 * });
1079 * @return {Function} Constructor for the newly registered type.
1080 */
1081 function register(inName, inOptions) {
1082 //console.warn('document.register("' + inName + '", ', inOptions, ')');
1083 // construct a defintion out of options
1084 // TODO(sjmiles): probably should clone inOptions instead of mutating it
1085 var definition = inOptions || {};
1086 if (!inName) {
1087 // TODO(sjmiles): replace with more appropriate error (EricB can probably
1088 // offer guidance)
1089 throw new Error('Name argument must not be empty');
1090 }
1091 // record name
1092 definition.name = inName;
1093 // must have a prototype, default to an extension of HTMLElement
1094 // TODO(sjmiles): probably should throw if no prototype, check spec
1095 if (!definition.prototype) {
1096 // TODO(sjmiles): replace with more appropriate error (EricB can probably
1097 // offer guidance)
1098 throw new Error('Options missing required prototype property');
1099 }
1100 // ensure a lifecycle object so we don't have to null test it
1101 definition.lifecycle = definition.lifecycle || {};
1102 // build a list of ancestral custom elements (for native base detection)
1103 // TODO(sjmiles): we used to need to store this, but current code only
1104 // uses it in 'resolveTagName': it should probably be inlined
1105 definition.ancestry = ancestry(definition.extends);
1106 // extensions of native specializations of HTMLElement require localName
1107 // to remain native, and use secondary 'is' specifier for extension type
1108 resolveTagName(definition);
1109 // some platforms require modifications to the user-supplied prototype
1110 // chain
1111 resolvePrototypeChain(definition);
1112 // overrides to implement attributeChanged callback
1113 overrideAttributeApi(definition.prototype);
1114 // 7.1.5: Register the DEFINITION with DOCUMENT
1115 registerDefinition(inName, definition);
1116 // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
1117 // 7.1.8. Return the output of the previous step.
1118 definition.ctor = generateConstructor(definition);
1119 definition.ctor.prototype = definition.prototype;
1120 // force our .constructor to be our actual constructor
1121 definition.prototype.constructor = definition.ctor;
1122 // if initial parsing is complete
1123 if (scope.ready) {
1124 // upgrade any pre-existing nodes of this type
1125 scope.upgradeAll(document);
1126 }
1127 return definition.ctor;
1128 }
1129
1130 function ancestry(inExtends) {
1131 var extendee = registry[inExtends];
1132 if (extendee) {
1133 return ancestry(extendee.extends).concat([extendee]);
1134 }
1135 return [];
1136 }
1137
1138 function resolveTagName(inDefinition) {
1139 // if we are explicitly extending something, that thing is our
1140 // baseTag, unless it represents a custom component
1141 var baseTag = inDefinition.extends;
1142 // if our ancestry includes custom components, we only have a
1143 // baseTag if one of them does
1144 for (var i=0, a; (a=inDefinition.ancestry[i]); i++) {
1145 baseTag = a.is && a.tag;
1146 }
1147 // our tag is our baseTag, if it exists, and otherwise just our name
1148 inDefinition.tag = baseTag || inDefinition.name;
1149 if (baseTag) {
1150 // if there is a base tag, use secondary 'is' specifier
1151 inDefinition.is = inDefinition.name;
1152 }
1153 }
1154
1155 function resolvePrototypeChain(inDefinition) {
1156 // if we don't support __proto__ we need to locate the native level
1157 // prototype for precise mixing in
1158 if (!Object.__proto__) {
1159 // default prototype
1160 var native = HTMLElement.prototype;
1161 // work out prototype when using type-extension
1162 if (inDefinition.is) {
1163 var inst = document.createElement(inDefinition.tag);
1164 native = Object.getPrototypeOf(inst);
1165 }
1166 // ensure __proto__ reference is installed at each point on the prototype
1167 // chain.
1168 // NOTE: On platforms without __proto__, a mixin strategy is used instead
1169 // of prototype swizzling. In this case, this generated __proto__ provides
1170 // limited support for prototype traversal.
1171 var proto = inDefinition.prototype, ancestor;
1172 while (proto && (proto !== native)) {
1173 var ancestor = Object.getPrototypeOf(proto);
1174 proto.__proto__ = ancestor;
1175 proto = ancestor;
1176 }
1177 }
1178 // cache this in case of mixin
1179 inDefinition.native = native;
1180 }
1181
1182 // SECTION 4
1183
1184 function instantiate(inDefinition) {
1185 // 4.a.1. Create a new object that implements PROTOTYPE
1186 // 4.a.2. Let ELEMENT by this new object
1187 //
1188 // the custom element instantiation algorithm must also ensure that the
1189 // output is a valid DOM element with the proper wrapper in place.
1190 //
1191 return upgrade(domCreateElement(inDefinition.tag), inDefinition);
1192 }
1193
1194 function upgrade(inElement, inDefinition) {
1195 // some definitions specify an 'is' attribute
1196 if (inDefinition.is) {
1197 inElement.setAttribute('is', inDefinition.is);
1198 }
1199 // make 'element' implement inDefinition.prototype
1200 implement(inElement, inDefinition);
1201 // flag as upgraded
1202 inElement.__upgraded__ = true;
1203 // there should never be a shadow root on inElement at this point
1204 // we require child nodes be upgraded before `created`
1205 scope.upgradeSubtree(inElement);
1206 // lifecycle management
1207 created(inElement);
1208 // OUTPUT
1209 return inElement;
1210 }
1211
1212 function implement(inElement, inDefinition) {
1213 // prototype swizzling is best
1214 if (Object.__proto__) {
1215 inElement.__proto__ = inDefinition.prototype;
1216 } else {
1217 // where above we can re-acquire inPrototype via
1218 // getPrototypeOf(Element), we cannot do so when
1219 // we use mixin, so we install a magic reference
1220 customMixin(inElement, inDefinition.prototype, inDefinition.native);
1221 inElement.__proto__ = inDefinition.prototype;
1222 }
1223 }
1224
1225 function customMixin(inTarget, inSrc, inNative) {
1226 // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of
1227 // any property. This set should be precalculated. We also need to
1228 // consider this for supporting 'super'.
1229 var used = {};
1230 // start with inSrc
1231 var p = inSrc;
1232 // sometimes the default is HTMLUnknownElement.prototype instead of
1233 // HTMLElement.prototype, so we add a test
1234 // the idea is to avoid mixing in native prototypes, so adding
1235 // the second test is WLOG
1236 while (p !== inNative && p !== HTMLUnknownElement.prototype) {
1237 var keys = Object.getOwnPropertyNames(p);
1238 for (var i=0, k; k=keys[i]; i++) {
1239 if (!used[k]) {
1240 Object.defineProperty(inTarget, k,
1241 Object.getOwnPropertyDescriptor(p, k));
1242 used[k] = 1;
1243 }
1244 }
1245 p = Object.getPrototypeOf(p);
1246 }
1247 }
1248
1249 function created(inElement) {
1250 // invoke createdCallback
1251 if (inElement.createdCallback) {
1252 inElement.createdCallback();
1253 }
1254 }
1255
1256 // attribute watching
1257
1258 function overrideAttributeApi(prototype) {
1259 // overrides to implement callbacks
1260 // TODO(sjmiles): should support access via .attributes NamedNodeMap
1261 // TODO(sjmiles): preserves user defined overrides, if any
1262 var setAttribute = prototype.setAttribute;
1263 prototype.setAttribute = function(name, value) {
1264 changeAttribute.call(this, name, value, setAttribute);
1265 }
1266 var removeAttribute = prototype.removeAttribute;
1267 prototype.removeAttribute = function(name, value) {
1268 changeAttribute.call(this, name, value, removeAttribute);
1269 }
1270 }
1271
1272 function changeAttribute(name, value, operation) {
1273 var oldValue = this.getAttribute(name);
1274 operation.apply(this, arguments);
1275 if (this.attributeChangedCallback
1276 && (this.getAttribute(name) !== oldValue)) {
1277 this.attributeChangedCallback(name, oldValue);
1278 }
1279 }
1280
1281 // element registry (maps tag names to definitions)
1282
1283 var registry = {};
1284
1285 function registerDefinition(inName, inDefinition) {
1286 if (registry[inName]) {
1287 throw new Error('Cannot register a tag more than once');
1288 }
1289 registry[inName] = inDefinition;
1290 }
1291
1292 function generateConstructor(inDefinition) {
1293 return function() {
1294 return instantiate(inDefinition);
1295 };
1296 }
1297
1298 function createElement(tag, typeExtension) {
1299 // TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could
1300 // error check it, or perhaps there should only ever be one argument
1301 var definition = registry[typeExtension || tag];
1302 if (definition) {
1303 return new definition.ctor();
1304 }
1305 return domCreateElement(tag);
1306 }
1307
1308 function upgradeElement(inElement) {
1309 if (!inElement.__upgraded__ && (inElement.nodeType === Node.ELEMENT_NODE)) {
1310 var type = inElement.getAttribute('is') || inElement.localName;
1311 var definition = registry[type];
1312 return definition && upgrade(inElement, definition);
1313 }
1314 }
1315
1316 function cloneNode(deep) {
1317 // call original clone
1318 var n = domCloneNode.call(this, deep);
1319 // upgrade the element and subtree
1320 scope.upgradeAll(n);
1321 // return the clone
1322 return n;
1323 }
1324 // capture native createElement before we override it
1325
1326 var domCreateElement = document.createElement.bind(document);
1327
1328 // capture native cloneNode before we override it
1329
1330 var domCloneNode = Node.prototype.cloneNode;
1331
1332 // exports
1333
1334 document.register = register;
1335 document.createElement = createElement; // override
1336 Node.prototype.cloneNode = cloneNode; // override
1337
1338 scope.registry = registry;
1339
1340 /**
1341 * Upgrade an element to a custom element. Upgrading an element
1342 * causes the custom prototype to be applied, an `is` attribute
1343 * to be attached (as needed), and invocation of the `readyCallback`.
1344 * `upgrade` does nothing if the element is already upgraded, or
1345 * if it matches no registered custom tag name.
1346 *
1347 * @method ugprade
1348 * @param {Element} inElement The element to upgrade.
1349 * @return {Element} The upgraded element.
1350 */
1351 scope.upgrade = upgradeElement;
1352 }
1353
1354 scope.hasNative = hasNative;
1355 scope.useNative = useNative;
1356
1357 })(window.CustomElements);
1358
1359 (function() {
1360
1361 // import
1362
1363 var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none ';
1364
1365 // highlander object for parsing a document tree
1366
1367 var parser = {
1368 selectors: [
1369 'link[rel=' + IMPORT_LINK_TYPE + ']'
1370 ],
1371 map: {
1372 link: 'parseLink'
1373 },
1374 parse: function(inDocument) {
1375 if (!inDocument.__parsed) {
1376 // only parse once
1377 inDocument.__parsed = true;
1378 // all parsable elements in inDocument (depth-first pre-order traversal)
1379 var elts = inDocument.querySelectorAll(parser.selectors);
1380 // for each parsable node type, call the mapped parsing method
1381 forEach(elts, function(e) {
1382 parser[parser.map[e.localName]](e);
1383 });
1384 // upgrade all upgradeable static elements, anything dynamically
1385 // created should be caught by observer
1386 CustomElements.upgradeDocument(inDocument);
1387 // observe document for dom changes
1388 CustomElements.observeDocument(inDocument);
1389 }
1390 },
1391 parseLink: function(linkElt) {
1392 // imports
1393 if (isDocumentLink(linkElt)) {
1394 this.parseImport(linkElt);
1395 }
1396 },
1397 parseImport: function(linkElt) {
1398 if (linkElt.content) {
1399 parser.parse(linkElt.content);
1400 }
1401 }
1402 };
1403
1404 function isDocumentLink(inElt) {
1405 return (inElt.localName === 'link'
1406 && inElt.getAttribute('rel') === IMPORT_LINK_TYPE);
1407 }
1408
1409 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
1410
1411 // exports
1412
1413 CustomElements.parser = parser;
1414
1415 })();
1416 (function(){
1417
1418 // bootstrap parsing
1419
1420 function bootstrap() {
1421 // go async so call stack can unwind
1422 setTimeout(function() {
1423 // parse document
1424 CustomElements.parser.parse(document);
1425 // one more pass before register is 'live'
1426 CustomElements.upgradeDocument(document);
1427 // set internal 'ready' flag, now document.register will trigger
1428 // synchronous upgrades
1429 CustomElements.ready = true;
1430 // capture blunt profiling data
1431 CustomElements.readyTime = Date.now();
1432 if (window.HTMLImports) {
1433 CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime;
1434 }
1435 // notify the system that we are bootstrapped
1436 document.body.dispatchEvent(
1437 new CustomEvent('WebComponentsReady', {bubbles: true})
1438 );
1439 }, 0);
1440 }
1441
1442 // CustomEvent shim for IE
1443 if (typeof window.CustomEvent !== 'function') {
1444 window.CustomEvent = function(inType) {
1445 var e = document.createEvent('HTMLEvents');
1446 e.initEvent(inType, true, true);
1447 return e;
1448 };
1449 }
1450
1451 if (document.readyState === 'complete') {
1452 bootstrap();
1453 } else {
1454 var loadEvent = window.HTMLImports ? 'HTMLImportsLoaded' : 'DOMContentLoaded';
1455 window.addEventListener(loadEvent, bootstrap);
1456 }
1457
1458 })();
OLDNEW
« no previous file with comments | « no previous file | pkg/custom_element/lib/custom-elements.min.js » ('j') | tests/html/custom_elements_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698