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 window.CustomElements = window.CustomElements || {flags:{}}; | |
60 (function(scope){ | |
61 | |
62 var logFlags = window.logFlags || {}; | |
63 var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none
'; | |
64 | |
65 // walk the subtree rooted at node, applying 'find(element, data)' function | |
66 // to each element | |
67 // if 'find' returns true for 'element', do not search element's subtree | |
68 function findAll(node, find, data) { | |
69 var e = node.firstElementChild; | |
70 if (!e) { | |
71 e = node.firstChild; | |
72 while (e && e.nodeType !== Node.ELEMENT_NODE) { | |
73 e = e.nextSibling; | |
74 } | |
75 } | |
76 while (e) { | |
77 if (find(e, data) !== true) { | |
78 findAll(e, find, data); | |
79 } | |
80 e = e.nextElementSibling; | |
81 } | |
82 return null; | |
83 } | |
84 | |
85 // walk all shadowRoots on a given node. | |
86 function forRoots(node, cb) { | |
87 var root = node.shadowRoot; | |
88 while(root) { | |
89 forSubtree(root, cb); | |
90 root = root.olderShadowRoot; | |
91 } | |
92 } | |
93 | |
94 // walk the subtree rooted at node, including descent into shadow-roots, | |
95 // applying 'cb' to each element | |
96 function forSubtree(node, cb) { | |
97 //logFlags.dom && node.childNodes && node.childNodes.length && console.group('
subTree: ', node); | |
98 findAll(node, function(e) { | |
99 if (cb(e)) { | |
100 return true; | |
101 } | |
102 forRoots(e, cb); | |
103 }); | |
104 forRoots(node, cb); | |
105 //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEn
d(); | |
106 } | |
107 | |
108 // manage lifecycle on added node | |
109 function added(node) { | |
110 if (upgrade(node)) { | |
111 insertedNode(node); | |
112 return true; | |
113 } | |
114 inserted(node); | |
115 } | |
116 | |
117 // manage lifecycle on added node's subtree only | |
118 function addedSubtree(node) { | |
119 forSubtree(node, function(e) { | |
120 if (added(e)) { | |
121 return true; | |
122 } | |
123 }); | |
124 } | |
125 | |
126 // manage lifecycle on added node and it's subtree | |
127 function addedNode(node) { | |
128 return added(node) || addedSubtree(node); | |
129 } | |
130 | |
131 // upgrade custom elements at node, if applicable | |
132 function upgrade(node) { | |
133 if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) { | |
134 var type = node.getAttribute('is') || node.localName; | |
135 var definition = scope.registry[type]; | |
136 if (definition) { | |
137 logFlags.dom && console.group('upgrade:', node.localName); | |
138 scope.upgrade(node); | |
139 logFlags.dom && console.groupEnd(); | |
140 return true; | |
141 } | |
142 } | |
143 } | |
144 | |
145 function insertedNode(node) { | |
146 inserted(node); | |
147 if (inDocument(node)) { | |
148 forSubtree(node, function(e) { | |
149 inserted(e); | |
150 }); | |
151 } | |
152 } | |
153 | |
154 | |
155 // TODO(sorvell): on platforms without MutationObserver, mutations may not be | |
156 // reliable and therefore attached/detached are not reliable. | |
157 // To make these callbacks less likely to fail, we defer all inserts and removes | |
158 // to give a chance for elements to be inserted into dom. | |
159 // This ensures attachedCallback fires for elements that are created and | |
160 // immediately added to dom. | |
161 var hasPolyfillMutations = (!window.MutationObserver || | |
162 (window.MutationObserver === window.JsMutationObserver)); | |
163 scope.hasPolyfillMutations = hasPolyfillMutations; | |
164 | |
165 var isPendingMutations = false; | |
166 var pendingMutations = []; | |
167 function deferMutation(fn) { | |
168 pendingMutations.push(fn); | |
169 if (!isPendingMutations) { | |
170 isPendingMutations = true; | |
171 var async = (window.Platform && window.Platform.endOfMicrotask) || | |
172 setTimeout; | |
173 async(takeMutations); | |
174 } | |
175 } | |
176 | |
177 function takeMutations() { | |
178 isPendingMutations = false; | |
179 var $p = pendingMutations; | |
180 for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) { | |
181 p(); | |
182 } | |
183 pendingMutations = []; | |
184 } | |
185 | |
186 function inserted(element) { | |
187 if (hasPolyfillMutations) { | |
188 deferMutation(function() { | |
189 _inserted(element); | |
190 }); | |
191 } else { | |
192 _inserted(element); | |
193 } | |
194 } | |
195 | |
196 // TODO(sjmiles): if there are descents into trees that can never have inDocumen
t(*) true, fix this | |
197 function _inserted(element) { | |
198 // TODO(sjmiles): it's possible we were inserted and removed in the space | |
199 // of one microtask, in which case we won't be 'inDocument' here | |
200 // But there are other cases where we are testing for inserted without | |
201 // specific knowledge of mutations, and must test 'inDocument' to determine | |
202 // whether to call inserted | |
203 // If we can factor these cases into separate code paths we can have | |
204 // better diagnostics. | |
205 // TODO(sjmiles): when logging, do work on all custom elements so we can | |
206 // track behavior even when callbacks not defined | |
207 //console.log('inserted: ', element.localName); | |
208 if (element.attachedCallback || element.detachedCallback || (element.__upgrade
d__ && logFlags.dom)) { | |
209 logFlags.dom && console.group('inserted:', element.localName); | |
210 if (inDocument(element)) { | |
211 element.__inserted = (element.__inserted || 0) + 1; | |
212 // if we are in a 'removed' state, bluntly adjust to an 'inserted' state | |
213 if (element.__inserted < 1) { | |
214 element.__inserted = 1; | |
215 } | |
216 // if we are 'over inserted', squelch the callback | |
217 if (element.__inserted > 1) { | |
218 logFlags.dom && console.warn('inserted:', element.localName, | |
219 'insert/remove count:', element.__inserted) | |
220 } else if (element.attachedCallback) { | |
221 logFlags.dom && console.log('inserted:', element.localName); | |
222 element.attachedCallback(); | |
223 } | |
224 } | |
225 logFlags.dom && console.groupEnd(); | |
226 } | |
227 } | |
228 | |
229 function removedNode(node) { | |
230 removed(node); | |
231 forSubtree(node, function(e) { | |
232 removed(e); | |
233 }); | |
234 } | |
235 | |
236 function removed(element) { | |
237 if (hasPolyfillMutations) { | |
238 deferMutation(function() { | |
239 _removed(element); | |
240 }); | |
241 } else { | |
242 _removed(element); | |
243 } | |
244 } | |
245 | |
246 function _removed(element) { | |
247 // TODO(sjmiles): temporary: do work on all custom elements so we can track | |
248 // behavior even when callbacks not defined | |
249 if (element.attachedCallback || element.detachedCallback || (element.__upgrade
d__ && logFlags.dom)) { | |
250 logFlags.dom && console.group('removed:', element.localName); | |
251 if (!inDocument(element)) { | |
252 element.__inserted = (element.__inserted || 0) - 1; | |
253 // if we are in a 'inserted' state, bluntly adjust to an 'removed' state | |
254 if (element.__inserted > 0) { | |
255 element.__inserted = 0; | |
256 } | |
257 // if we are 'over removed', squelch the callback | |
258 if (element.__inserted < 0) { | |
259 logFlags.dom && console.warn('removed:', element.localName, | |
260 'insert/remove count:', element.__inserted) | |
261 } else if (element.detachedCallback) { | |
262 element.detachedCallback(); | |
263 } | |
264 } | |
265 logFlags.dom && console.groupEnd(); | |
266 } | |
267 } | |
268 | |
269 // SD polyfill intrustion due mainly to the fact that 'document' | |
270 // is not entirely wrapped | |
271 function wrapIfNeeded(node) { | |
272 return window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) | |
273 : node; | |
274 } | |
275 | |
276 function inDocument(element) { | |
277 var p = element; | |
278 var doc = wrapIfNeeded(document); | |
279 while (p) { | |
280 if (p == doc) { | |
281 return true; | |
282 } | |
283 p = p.parentNode || p.host; | |
284 } | |
285 } | |
286 | |
287 function watchShadow(node) { | |
288 if (node.shadowRoot && !node.shadowRoot.__watched) { | |
289 logFlags.dom && console.log('watching shadow-root for: ', node.localName); | |
290 // watch all unwatched roots... | |
291 var root = node.shadowRoot; | |
292 while (root) { | |
293 watchRoot(root); | |
294 root = root.olderShadowRoot; | |
295 } | |
296 } | |
297 } | |
298 | |
299 function watchRoot(root) { | |
300 if (!root.__watched) { | |
301 observe(root); | |
302 root.__watched = true; | |
303 } | |
304 } | |
305 | |
306 function handler(mutations) { | |
307 // | |
308 if (logFlags.dom) { | |
309 var mx = mutations[0]; | |
310 if (mx && mx.type === 'childList' && mx.addedNodes) { | |
311 if (mx.addedNodes) { | |
312 var d = mx.addedNodes[0]; | |
313 while (d && d !== document && !d.host) { | |
314 d = d.parentNode; | |
315 } | |
316 var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || ''; | |
317 u = u.split('/?').shift().split('/').pop(); | |
318 } | |
319 } | |
320 console.group('mutations (%d) [%s]', mutations.length, u || ''); | |
321 } | |
322 // | |
323 mutations.forEach(function(mx) { | |
324 //logFlags.dom && console.group('mutation'); | |
325 if (mx.type === 'childList') { | |
326 forEach(mx.addedNodes, function(n) { | |
327 //logFlags.dom && console.log(n.localName); | |
328 if (!n.localName) { | |
329 return; | |
330 } | |
331 // nodes added may need lifecycle management | |
332 addedNode(n); | |
333 }); | |
334 // removed nodes may need lifecycle management | |
335 forEach(mx.removedNodes, function(n) { | |
336 //logFlags.dom && console.log(n.localName); | |
337 if (!n.localName) { | |
338 return; | |
339 } | |
340 removedNode(n); | |
341 }); | |
342 } | |
343 //logFlags.dom && console.groupEnd(); | |
344 }); | |
345 logFlags.dom && console.groupEnd(); | |
346 }; | |
347 | |
348 var observer = new MutationObserver(handler); | |
349 | |
350 function takeRecords() { | |
351 // TODO(sjmiles): ask Raf why we have to call handler ourselves | |
352 handler(observer.takeRecords()); | |
353 takeMutations(); | |
354 } | |
355 | |
356 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
357 | |
358 function observe(inRoot) { | |
359 observer.observe(inRoot, {childList: true, subtree: true}); | |
360 } | |
361 | |
362 function observeDocument(doc) { | |
363 observe(doc); | |
364 } | |
365 | |
366 function upgradeDocument(doc) { | |
367 logFlags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').po
p()); | |
368 addedNode(doc); | |
369 logFlags.dom && console.groupEnd(); | |
370 } | |
371 | |
372 function upgradeDocumentTree(doc) { | |
373 doc = wrapIfNeeded(doc); | |
374 upgradeDocument(doc); | |
375 //console.log('upgradeDocumentTree: ', (doc.baseURI).split('/').pop()); | |
376 // upgrade contained imported documents | |
377 var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']'); | |
378 for (var i=0, l=imports.length, n; (i<l) && (n=imports[i]); i++) { | |
379 if (n.import && n.import.__parsed) { | |
380 upgradeDocumentTree(n.import); | |
381 } | |
382 } | |
383 } | |
384 | |
385 // exports | |
386 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
387 scope.watchShadow = watchShadow; | |
388 scope.upgradeDocumentTree = upgradeDocumentTree; | |
389 scope.upgradeAll = addedNode; | |
390 scope.upgradeSubtree = addedSubtree; | |
391 | |
392 scope.observeDocument = observeDocument; | |
393 scope.upgradeDocument = upgradeDocument; | |
394 | |
395 scope.takeRecords = takeRecords; | |
396 | |
397 })(window.CustomElements); | |
398 | |
399 /** | |
400 * Implements `document.register` | |
401 * @module CustomElements | |
402 */ | |
403 | |
404 /** | |
405 * Polyfilled extensions to the `document` object. | |
406 * @class Document | |
407 */ | |
408 | |
409 (function(scope) { | |
410 | |
411 // imports | |
412 | |
413 if (!scope) { | |
414 scope = window.CustomElements = {flags:{}}; | |
415 } | |
416 var flags = scope.flags; | |
417 | |
418 // native document.registerElement? | |
419 | |
420 var hasNative = Boolean(document.registerElement); | |
421 // TODO(sorvell): See https://github.com/Polymer/polymer/issues/399 | |
422 // we'll address this by defaulting to CE polyfill in the presence of the SD | |
423 // polyfill. This will avoid spamming excess attached/detached callbacks. | |
424 // If there is a compelling need to run CE native with SD polyfill, | |
425 // we'll need to fix this issue. | |
426 var useNative = !flags.register && hasNative && !window.ShadowDOMPolyfill; | |
427 | |
428 if (useNative) { | |
429 | |
430 // stub | |
431 var nop = function() {}; | |
432 | |
433 // exports | |
434 scope.registry = {}; | |
435 scope.upgradeElement = nop; | |
436 | |
437 scope.watchShadow = nop; | |
438 scope.upgrade = nop; | |
439 scope.upgradeAll = nop; | |
440 scope.upgradeSubtree = nop; | |
441 scope.observeDocument = nop; | |
442 scope.upgradeDocument = nop; | |
443 scope.takeRecords = nop; | |
444 | |
445 } else { | |
446 | |
447 /** | |
448 * Registers a custom tag name with the document. | |
449 * | |
450 * When a registered element is created, a `readyCallback` method is called | |
451 * in the scope of the element. The `readyCallback` method can be specified on | |
452 * either `options.prototype` or `options.lifecycle` with the latter taking | |
453 * precedence. | |
454 * | |
455 * @method register | |
456 * @param {String} name The tag name to register. Must include a dash ('-'), | |
457 * for example 'x-component'. | |
458 * @param {Object} options | |
459 * @param {String} [options.extends] | |
460 * (_off spec_) Tag name of an element to extend (or blank for a new | |
461 * element). This parameter is not part of the specification, but instead | |
462 * is a hint for the polyfill because the extendee is difficult to infer. | |
463 * Remember that the input prototype must chain to the extended element's | |
464 * prototype (or HTMLElement.prototype) regardless of the value of | |
465 * `extends`. | |
466 * @param {Object} options.prototype The prototype to use for the new | |
467 * element. The prototype must inherit from HTMLElement. | |
468 * @param {Object} [options.lifecycle] | |
469 * Callbacks that fire at important phases in the life of the custom | |
470 * element. | |
471 * | |
472 * @example | |
473 * FancyButton = document.registerElement("fancy-button", { | |
474 * extends: 'button', | |
475 * prototype: Object.create(HTMLButtonElement.prototype, { | |
476 * readyCallback: { | |
477 * value: function() { | |
478 * console.log("a fancy-button was created", | |
479 * } | |
480 * } | |
481 * }) | |
482 * }); | |
483 * @return {Function} Constructor for the newly registered type. | |
484 */ | |
485 function register(name, options) { | |
486 //console.warn('document.registerElement("' + name + '", ', options, ')'); | |
487 // construct a defintion out of options | |
488 // TODO(sjmiles): probably should clone options instead of mutating it | |
489 var definition = options || {}; | |
490 if (!name) { | |
491 // TODO(sjmiles): replace with more appropriate error (EricB can probably | |
492 // offer guidance) | |
493 throw new Error('document.registerElement: first argument `name` must not
be empty'); | |
494 } | |
495 if (name.indexOf('-') < 0) { | |
496 // TODO(sjmiles): replace with more appropriate error (EricB can probably | |
497 // offer guidance) | |
498 throw new Error('document.registerElement: first argument (\'name\') must
contain a dash (\'-\'). Argument provided was \'' + String(name) + '\'.'); | |
499 } | |
500 // elements may only be registered once | |
501 if (getRegisteredDefinition(name)) { | |
502 throw new Error('DuplicateDefinitionError: a type with name \'' + String(n
ame) + '\' is already registered'); | |
503 } | |
504 // must have a prototype, default to an extension of HTMLElement | |
505 // TODO(sjmiles): probably should throw if no prototype, check spec | |
506 if (!definition.prototype) { | |
507 // TODO(sjmiles): replace with more appropriate error (EricB can probably | |
508 // offer guidance) | |
509 throw new Error('Options missing required prototype property'); | |
510 } | |
511 // record name | |
512 definition.__name = name.toLowerCase(); | |
513 // ensure a lifecycle object so we don't have to null test it | |
514 definition.lifecycle = definition.lifecycle || {}; | |
515 // build a list of ancestral custom elements (for native base detection) | |
516 // TODO(sjmiles): we used to need to store this, but current code only | |
517 // uses it in 'resolveTagName': it should probably be inlined | |
518 definition.ancestry = ancestry(definition.extends); | |
519 // extensions of native specializations of HTMLElement require localName | |
520 // to remain native, and use secondary 'is' specifier for extension type | |
521 resolveTagName(definition); | |
522 // some platforms require modifications to the user-supplied prototype | |
523 // chain | |
524 resolvePrototypeChain(definition); | |
525 // overrides to implement attributeChanged callback | |
526 overrideAttributeApi(definition.prototype); | |
527 // 7.1.5: Register the DEFINITION with DOCUMENT | |
528 registerDefinition(definition.__name, definition); | |
529 // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE | |
530 // 7.1.8. Return the output of the previous step. | |
531 definition.ctor = generateConstructor(definition); | |
532 definition.ctor.prototype = definition.prototype; | |
533 // force our .constructor to be our actual constructor | |
534 definition.prototype.constructor = definition.ctor; | |
535 // if initial parsing is complete | |
536 if (scope.ready || scope.performedInitialDocumentUpgrade) { | |
537 // upgrade any pre-existing nodes of this type | |
538 scope.upgradeDocumentTree(document); | |
539 } | |
540 return definition.ctor; | |
541 } | |
542 | |
543 function ancestry(extnds) { | |
544 var extendee = getRegisteredDefinition(extnds); | |
545 if (extendee) { | |
546 return ancestry(extendee.extends).concat([extendee]); | |
547 } | |
548 return []; | |
549 } | |
550 | |
551 function resolveTagName(definition) { | |
552 // if we are explicitly extending something, that thing is our | |
553 // baseTag, unless it represents a custom component | |
554 var baseTag = definition.extends; | |
555 // if our ancestry includes custom components, we only have a | |
556 // baseTag if one of them does | |
557 for (var i=0, a; (a=definition.ancestry[i]); i++) { | |
558 baseTag = a.is && a.tag; | |
559 } | |
560 // our tag is our baseTag, if it exists, and otherwise just our name | |
561 definition.tag = baseTag || definition.__name; | |
562 if (baseTag) { | |
563 // if there is a base tag, use secondary 'is' specifier | |
564 definition.is = definition.__name; | |
565 } | |
566 } | |
567 | |
568 function resolvePrototypeChain(definition) { | |
569 // if we don't support __proto__ we need to locate the native level | |
570 // prototype for precise mixing in | |
571 if (!Object.__proto__) { | |
572 // default prototype | |
573 var nativePrototype = HTMLElement.prototype; | |
574 // work out prototype when using type-extension | |
575 if (definition.is) { | |
576 var inst = document.createElement(definition.tag); | |
577 nativePrototype = Object.getPrototypeOf(inst); | |
578 } | |
579 // ensure __proto__ reference is installed at each point on the prototype | |
580 // chain. | |
581 // NOTE: On platforms without __proto__, a mixin strategy is used instead | |
582 // of prototype swizzling. In this case, this generated __proto__ provides | |
583 // limited support for prototype traversal. | |
584 var proto = definition.prototype, ancestor; | |
585 while (proto && (proto !== nativePrototype)) { | |
586 var ancestor = Object.getPrototypeOf(proto); | |
587 proto.__proto__ = ancestor; | |
588 proto = ancestor; | |
589 } | |
590 } | |
591 // cache this in case of mixin | |
592 definition.native = nativePrototype; | |
593 } | |
594 | |
595 // SECTION 4 | |
596 | |
597 function instantiate(definition) { | |
598 // 4.a.1. Create a new object that implements PROTOTYPE | |
599 // 4.a.2. Let ELEMENT by this new object | |
600 // | |
601 // the custom element instantiation algorithm must also ensure that the | |
602 // output is a valid DOM element with the proper wrapper in place. | |
603 // | |
604 return upgrade(domCreateElement(definition.tag), definition); | |
605 } | |
606 | |
607 function upgrade(element, definition) { | |
608 // some definitions specify an 'is' attribute | |
609 if (definition.is) { | |
610 element.setAttribute('is', definition.is); | |
611 } | |
612 // remove 'unresolved' attr, which is a standin for :unresolved. | |
613 element.removeAttribute('unresolved'); | |
614 // make 'element' implement definition.prototype | |
615 implement(element, definition); | |
616 // flag as upgraded | |
617 element.__upgraded__ = true; | |
618 // there should never be a shadow root on element at this point | |
619 // we require child nodes be upgraded before `created` | |
620 scope.upgradeSubtree(element); | |
621 // lifecycle management | |
622 created(element); | |
623 // OUTPUT | |
624 return element; | |
625 } | |
626 | |
627 function implement(element, definition) { | |
628 // prototype swizzling is best | |
629 if (Object.__proto__) { | |
630 element.__proto__ = definition.prototype; | |
631 } else { | |
632 // where above we can re-acquire inPrototype via | |
633 // getPrototypeOf(Element), we cannot do so when | |
634 // we use mixin, so we install a magic reference | |
635 customMixin(element, definition.prototype, definition.native); | |
636 | |
637 // Dart note: make sure we pick up the right constructor. | |
638 // dart2js depends on this for dart:mirrors caching to work. | |
639 // See tests/html/custom/mirrors_test.dart | |
640 element.constructor = definition.prototype.constructor; | |
641 element.__proto__ = definition.prototype; | |
642 } | |
643 } | |
644 | |
645 function customMixin(inTarget, inSrc, inNative) { | |
646 // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of | |
647 // any property. This set should be precalculated. We also need to | |
648 // consider this for supporting 'super'. | |
649 var used = {}; | |
650 // start with inSrc | |
651 var p = inSrc; | |
652 // sometimes the default is HTMLUnknownElement.prototype instead of | |
653 // HTMLElement.prototype, so we add a test | |
654 // the idea is to avoid mixing in native prototypes, so adding | |
655 // the second test is WLOG | |
656 while (p !== inNative && p !== HTMLUnknownElement.prototype) { | |
657 var keys = Object.getOwnPropertyNames(p); | |
658 for (var i=0, k; k=keys[i]; i++) { | |
659 if (!used[k]) { | |
660 Object.defineProperty(inTarget, k, | |
661 Object.getOwnPropertyDescriptor(p, k)); | |
662 used[k] = 1; | |
663 } | |
664 } | |
665 p = Object.getPrototypeOf(p); | |
666 } | |
667 } | |
668 | |
669 function created(element) { | |
670 // invoke createdCallback | |
671 if (element.createdCallback) { | |
672 element.createdCallback(); | |
673 } | |
674 } | |
675 | |
676 // attribute watching | |
677 | |
678 function overrideAttributeApi(prototype) { | |
679 // overrides to implement callbacks | |
680 // TODO(sjmiles): should support access via .attributes NamedNodeMap | |
681 // TODO(sjmiles): preserves user defined overrides, if any | |
682 if (prototype.setAttribute._polyfilled) { | |
683 return; | |
684 } | |
685 var setAttribute = prototype.setAttribute; | |
686 prototype.setAttribute = function(name, value) { | |
687 changeAttribute.call(this, name, value, setAttribute); | |
688 } | |
689 var removeAttribute = prototype.removeAttribute; | |
690 prototype.removeAttribute = function(name) { | |
691 changeAttribute.call(this, name, null, removeAttribute); | |
692 } | |
693 prototype.setAttribute._polyfilled = true; | |
694 } | |
695 | |
696 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/ | |
697 // index.html#dfn-attribute-changed-callback | |
698 function changeAttribute(name, value, operation) { | |
699 var oldValue = this.getAttribute(name); | |
700 operation.apply(this, arguments); | |
701 var newValue = this.getAttribute(name); | |
702 if (this.attributeChangedCallback | |
703 && (newValue !== oldValue)) { | |
704 this.attributeChangedCallback(name, oldValue, newValue); | |
705 } | |
706 } | |
707 | |
708 // element registry (maps tag names to definitions) | |
709 | |
710 var registry = {}; | |
711 | |
712 function getRegisteredDefinition(name) { | |
713 if (name) { | |
714 return registry[name.toLowerCase()]; | |
715 } | |
716 } | |
717 | |
718 function registerDefinition(name, definition) { | |
719 if (registry[name]) { | |
720 throw new Error('a type with that name is already registered.'); | |
721 } | |
722 registry[name] = definition; | |
723 } | |
724 | |
725 function generateConstructor(definition) { | |
726 return function() { | |
727 return instantiate(definition); | |
728 }; | |
729 } | |
730 | |
731 function createElement(tag, typeExtension) { | |
732 // TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could | |
733 // error check it, or perhaps there should only ever be one argument | |
734 var definition = getRegisteredDefinition(typeExtension || tag); | |
735 if (definition) { | |
736 if (tag == definition.tag && typeExtension == definition.is) { | |
737 return new definition.ctor(); | |
738 } | |
739 // Handle empty string for type extension. | |
740 if (!typeExtension && !definition.is) { | |
741 return new definition.ctor(); | |
742 } | |
743 } | |
744 | |
745 if (typeExtension) { | |
746 var element = createElement(tag); | |
747 element.setAttribute('is', typeExtension); | |
748 return element; | |
749 } | |
750 var element = domCreateElement(tag); | |
751 // Custom tags should be HTMLElements even if not upgraded. | |
752 if (tag.indexOf('-') >= 0) { | |
753 implement(element, HTMLElement); | |
754 } | |
755 return element; | |
756 } | |
757 | |
758 function upgradeElement(element) { | |
759 if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) { | |
760 var is = element.getAttribute('is'); | |
761 var definition = registry[is || element.localName]; | |
762 if (definition) { | |
763 if (is && definition.tag == element.localName) { | |
764 return upgrade(element, definition); | |
765 } else if (!is && !definition.extends) { | |
766 return upgrade(element, definition); | |
767 } | |
768 } | |
769 } | |
770 } | |
771 | |
772 function cloneNode(deep) { | |
773 // call original clone | |
774 var n = domCloneNode.call(this, deep); | |
775 // upgrade the element and subtree | |
776 scope.upgradeAll(n); | |
777 // return the clone | |
778 return n; | |
779 } | |
780 // capture native createElement before we override it | |
781 | |
782 var domCreateElement = document.createElement.bind(document); | |
783 | |
784 // capture native cloneNode before we override it | |
785 | |
786 var domCloneNode = Node.prototype.cloneNode; | |
787 | |
788 // exports | |
789 | |
790 document.registerElement = register; | |
791 document.createElement = createElement; // override | |
792 Node.prototype.cloneNode = cloneNode; // override | |
793 | |
794 scope.registry = registry; | |
795 | |
796 /** | |
797 * Upgrade an element to a custom element. Upgrading an element | |
798 * causes the custom prototype to be applied, an `is` attribute | |
799 * to be attached (as needed), and invocation of the `readyCallback`. | |
800 * `upgrade` does nothing if the element is already upgraded, or | |
801 * if it matches no registered custom tag name. | |
802 * | |
803 * @method ugprade | |
804 * @param {Element} element The element to upgrade. | |
805 * @return {Element} The upgraded element. | |
806 */ | |
807 scope.upgrade = upgradeElement; | |
808 } | |
809 | |
810 // bc | |
811 document.register = document.registerElement; | |
812 | |
813 scope.hasNative = hasNative; | |
814 scope.useNative = useNative; | |
815 | |
816 })(window.CustomElements); | |
817 | |
818 (function(scope) { | |
819 | |
820 // import | |
821 | |
822 var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; | |
823 | |
824 // highlander object for parsing a document tree | |
825 | |
826 var parser = { | |
827 selectors: [ | |
828 'link[rel=' + IMPORT_LINK_TYPE + ']' | |
829 ], | |
830 map: { | |
831 link: 'parseLink' | |
832 }, | |
833 parse: function(inDocument) { | |
834 if (!inDocument.__parsed) { | |
835 // only parse once | |
836 inDocument.__parsed = true; | |
837 // all parsable elements in inDocument (depth-first pre-order traversal) | |
838 var elts = inDocument.querySelectorAll(parser.selectors); | |
839 // for each parsable node type, call the mapped parsing method | |
840 forEach(elts, function(e) { | |
841 parser[parser.map[e.localName]](e); | |
842 }); | |
843 // upgrade all upgradeable static elements, anything dynamically | |
844 // created should be caught by observer | |
845 CustomElements.upgradeDocument(inDocument); | |
846 // observe document for dom changes | |
847 CustomElements.observeDocument(inDocument); | |
848 } | |
849 }, | |
850 parseLink: function(linkElt) { | |
851 // imports | |
852 if (isDocumentLink(linkElt)) { | |
853 this.parseImport(linkElt); | |
854 } | |
855 }, | |
856 parseImport: function(linkElt) { | |
857 if (linkElt.import) { | |
858 parser.parse(linkElt.import); | |
859 } | |
860 } | |
861 }; | |
862 | |
863 function isDocumentLink(inElt) { | |
864 return (inElt.localName === 'link' | |
865 && inElt.getAttribute('rel') === IMPORT_LINK_TYPE); | |
866 } | |
867 | |
868 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
869 | |
870 // exports | |
871 | |
872 scope.parser = parser; | |
873 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
874 | |
875 })(window.CustomElements); | |
876 (function(scope){ | |
877 | |
878 // bootstrap parsing | |
879 function bootstrap() { | |
880 // parse document | |
881 CustomElements.parser.parse(document); | |
882 // one more pass before register is 'live' | |
883 CustomElements.upgradeDocument(document); | |
884 CustomElements.performedInitialDocumentUpgrade = true; | |
885 // choose async | |
886 var async = window.Platform && Platform.endOfMicrotask ? | |
887 Platform.endOfMicrotask : | |
888 setTimeout; | |
889 async(function() { | |
890 // set internal 'ready' flag, now document.registerElement will trigger | |
891 // synchronous upgrades | |
892 CustomElements.ready = true; | |
893 // capture blunt profiling data | |
894 CustomElements.readyTime = Date.now(); | |
895 if (window.HTMLImports) { | |
896 CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime; | |
897 } | |
898 // notify the system that we are bootstrapped | |
899 document.dispatchEvent( | |
900 new CustomEvent('WebComponentsReady', {bubbles: true}) | |
901 ); | |
902 }); | |
903 } | |
904 | |
905 // CustomEvent shim for IE | |
906 if (typeof window.CustomEvent !== 'function') { | |
907 window.CustomEvent = function(inType) { | |
908 var e = document.createEvent('HTMLEvents'); | |
909 e.initEvent(inType, true, true); | |
910 return e; | |
911 }; | |
912 } | |
913 | |
914 // When loading at readyState complete time (or via flag), boot custom elements | |
915 // immediately. | |
916 // If relevant, HTMLImports must already be loaded. | |
917 if (document.readyState === 'complete' || scope.flags.eager) { | |
918 bootstrap(); | |
919 // When loading at readyState interactive time, bootstrap only if HTMLImports | |
920 // are not pending. Also avoid IE as the semantics of this state are unreliable. | |
921 } else if (document.readyState === 'interactive' && !window.attachEvent && | |
922 (!window.HTMLImports || window.HTMLImports.ready)) { | |
923 bootstrap(); | |
924 // When loading at other readyStates, wait for the appropriate DOM event to | |
925 // bootstrap. | |
926 } else { | |
927 var loadEvent = window.HTMLImports && !HTMLImports.ready | |
928 ? 'HTMLImportsLoaded' | |
929 : document.readyState == 'loading' ? 'DOMContentLoaded' : 'load'; | |
930 window.addEventListener(loadEvent, bootstrap); | |
931 } | |
932 | |
933 })(window.CustomElements); | |
934 | |
935 (function() { | |
936 // Patch to allow custom element and shadow dom to work together, from: | |
937 // https://github.com/Polymer/platform-dev/blob/60ece8c323c5d9325cbfdfd6e8cd180d
4f38a3bc/src/patches-shadowdom-polyfill.js | |
938 // include .host reference | |
939 if (HTMLElement.prototype.createShadowRoot) { | |
940 var originalCreateShadowRoot = HTMLElement.prototype.createShadowRoot; | |
941 HTMLElement.prototype.createShadowRoot = function() { | |
942 var root = originalCreateShadowRoot.call(this); | |
943 root.host = this; | |
944 CustomElements.watchShadow(this); | |
945 return root; | |
946 } | |
947 } | |
948 | |
949 | |
950 // Patch to allow custom elements and shadow dom to work together, from: | |
951 // https://github.com/Polymer/platform-dev/blob/2bb9c56d90f9ac19c2e65cdad368668a
ff514f14/src/patches-custom-elements.js | |
952 if (window.ShadowDOMPolyfill) { | |
953 | |
954 // ensure wrapped inputs for these functions | |
955 var fns = ['upgradeAll', 'upgradeSubtree', 'observeDocument', | |
956 'upgradeDocument']; | |
957 | |
958 // cache originals | |
959 var original = {}; | |
960 fns.forEach(function(fn) { | |
961 original[fn] = CustomElements[fn]; | |
962 }); | |
963 | |
964 // override | |
965 fns.forEach(function(fn) { | |
966 CustomElements[fn] = function(inNode) { | |
967 return original[fn](window.ShadowDOMPolyfill.wrapIfNeeded(inNode)); | |
968 }; | |
969 }); | |
970 | |
971 } | |
972 | |
973 // Patch to make importNode work. | |
974 // https://github.com/Polymer/platform-dev/blob/64a92f273462f04a84abbe2f054294f2
b62dbcd6/src/patches-mdv.js | |
975 if (window.CustomElements && !CustomElements.useNative) { | |
976 var originalImportNode = Document.prototype.importNode; | |
977 Document.prototype.importNode = function(node, deep) { | |
978 var imported = originalImportNode.call(this, node, deep); | |
979 CustomElements.upgradeAll(imported); | |
980 return imported; | |
981 } | |
982 } | |
983 | |
984 })(); | |
OLD | NEW |