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