OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2012 The Polymer Authors. All rights reserved. | |
3 * Use of this source code is governed by a BSD-style | |
4 * license that can be found in the LICENSE file. | |
5 */ | |
6 | |
7 if (typeof WeakMap === 'undefined') { | |
8 (function() { | |
9 var defineProperty = Object.defineProperty; | |
10 var counter = Date.now() % 1e9; | |
11 | |
12 var WeakMap = function() { | |
13 this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__'); | |
14 }; | |
15 | |
16 WeakMap.prototype = { | |
17 set: function(key, value) { | |
18 var entry = key[this.name]; | |
19 if (entry && entry[0] === key) | |
20 entry[1] = value; | |
21 else | |
22 defineProperty(key, this.name, {value: [key, value], writable: true}); | |
23 }, | |
24 get: function(key) { | |
25 var entry; | |
26 return (entry = key[this.name]) && entry[0] === key ? | |
27 entry[1] : undefined; | |
28 }, | |
29 delete: function(key) { | |
30 this.set(key, undefined); | |
31 } | |
32 }; | |
33 | |
34 window.WeakMap = WeakMap; | |
35 })(); | |
36 } | |
37 | |
38 // Copyright 2012 Google Inc. | |
39 // | |
40 // Licensed under the Apache License, Version 2.0 (the "License"); | |
41 // you may not use this file except in compliance with the License. | |
42 // You may obtain a copy of the License at | |
43 // | |
44 // http://www.apache.org/licenses/LICENSE-2.0 | |
45 // | |
46 // Unless required by applicable law or agreed to in writing, software | |
47 // distributed under the License is distributed on an "AS IS" BASIS, | |
48 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
49 // See the License for the specific language governing permissions and | |
50 // limitations under the License. | |
51 | |
52 (function(global) { | |
53 'use strict'; | |
54 | |
55 // Detect and do basic sanity checking on Object/Array.observe. | |
56 function detectObjectObserve() { | |
57 if (typeof Object.observe !== 'function' || | |
58 typeof Array.observe !== 'function') { | |
59 return false; | |
60 } | |
61 | |
62 var records = []; | |
63 | |
64 function callback(recs) { | |
65 records = recs; | |
66 } | |
67 | |
68 var test = {}; | |
69 var arr = []; | |
70 Object.observe(test, callback); | |
71 Array.observe(arr, callback); | |
72 test.id = 1; | |
73 test.id = 2; | |
74 delete test.id; | |
75 arr.push(1, 2); | |
76 arr.length = 0; | |
77 | |
78 Object.deliverChangeRecords(callback); | |
79 if (records.length !== 5) | |
80 return false; | |
81 | |
82 if (records[0].type != 'add' || | |
83 records[1].type != 'update' || | |
84 records[2].type != 'delete' || | |
85 records[3].type != 'splice' || | |
86 records[4].type != 'splice') { | |
87 return false; | |
88 } | |
89 | |
90 Object.unobserve(test, callback); | |
91 Array.unobserve(arr, callback); | |
92 | |
93 return true; | |
94 } | |
95 | |
96 var hasObserve = detectObjectObserve(); | |
97 | |
98 function detectEval() { | |
99 // don't test for eval if document has CSP securityPolicy object and we can
see that | |
100 // eval is not supported. This avoids an error message in console even when
the exception | |
101 // is caught | |
102 if (global.document && | |
103 'securityPolicy' in global.document && | |
104 !global.document.securityPolicy.allowsEval) { | |
105 return false; | |
106 } | |
107 | |
108 try { | |
109 var f = new Function('', 'return true;'); | |
110 return f(); | |
111 } catch (ex) { | |
112 return false; | |
113 } | |
114 } | |
115 | |
116 var hasEval = detectEval(); | |
117 | |
118 function isIndex(s) { | |
119 return +s === s >>> 0; | |
120 } | |
121 | |
122 function toNumber(s) { | |
123 return +s; | |
124 } | |
125 | |
126 function isObject(obj) { | |
127 return obj === Object(obj); | |
128 } | |
129 | |
130 var numberIsNaN = global.Number.isNaN || function isNaN(value) { | |
131 return typeof value === 'number' && global.isNaN(value); | |
132 } | |
133 | |
134 function areSameValue(left, right) { | |
135 if (left === right) | |
136 return left !== 0 || 1 / left === 1 / right; | |
137 if (numberIsNaN(left) && numberIsNaN(right)) | |
138 return true; | |
139 | |
140 return left !== left && right !== right; | |
141 } | |
142 | |
143 var createObject = ('__proto__' in {}) ? | |
144 function(obj) { return obj; } : | |
145 function(obj) { | |
146 var proto = obj.__proto__; | |
147 if (!proto) | |
148 return obj; | |
149 var newObject = Object.create(proto); | |
150 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
151 Object.defineProperty(newObject, name, | |
152 Object.getOwnPropertyDescriptor(obj, name)); | |
153 }); | |
154 return newObject; | |
155 }; | |
156 | |
157 var identStart = '[\$_a-zA-Z]'; | |
158 var identPart = '[\$_a-zA-Z0-9]'; | |
159 var ident = identStart + '+' + identPart + '*'; | |
160 var elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; | |
161 var identOrElementIndex = '(?:' + ident + '|' + elementIndex + ')'; | |
162 var path = '(?:' + identOrElementIndex + ')(?:\\s*\\.\\s*' + identOrElementInd
ex + ')*'; | |
163 var pathRegExp = new RegExp('^' + path + '$'); | |
164 | |
165 function isPathValid(s) { | |
166 if (typeof s != 'string') | |
167 return false; | |
168 s = s.trim(); | |
169 | |
170 if (s == '') | |
171 return true; | |
172 | |
173 if (s[0] == '.') | |
174 return false; | |
175 | |
176 return pathRegExp.test(s); | |
177 } | |
178 | |
179 var constructorIsPrivate = {}; | |
180 | |
181 function Path(s, privateToken) { | |
182 if (privateToken !== constructorIsPrivate) | |
183 throw Error('Use Path.get to retrieve path objects'); | |
184 | |
185 if (s.trim() == '') | |
186 return this; | |
187 | |
188 if (isIndex(s)) { | |
189 this.push(s); | |
190 return this; | |
191 } | |
192 | |
193 s.split(/\s*\.\s*/).filter(function(part) { | |
194 return part; | |
195 }).forEach(function(part) { | |
196 this.push(part); | |
197 }, this); | |
198 | |
199 if (hasEval && this.length) { | |
200 this.getValueFrom = this.compiledGetValueFromFn(); | |
201 } | |
202 } | |
203 | |
204 // TODO(rafaelw): Make simple LRU cache | |
205 var pathCache = {}; | |
206 | |
207 function getPath(pathString) { | |
208 if (pathString instanceof Path) | |
209 return pathString; | |
210 | |
211 if (pathString == null) | |
212 pathString = ''; | |
213 | |
214 if (typeof pathString !== 'string') | |
215 pathString = String(pathString); | |
216 | |
217 var path = pathCache[pathString]; | |
218 if (path) | |
219 return path; | |
220 if (!isPathValid(pathString)) | |
221 return invalidPath; | |
222 var path = new Path(pathString, constructorIsPrivate); | |
223 pathCache[pathString] = path; | |
224 return path; | |
225 } | |
226 | |
227 Path.get = getPath; | |
228 | |
229 Path.prototype = createObject({ | |
230 __proto__: [], | |
231 valid: true, | |
232 | |
233 toString: function() { | |
234 return this.join('.'); | |
235 }, | |
236 | |
237 getValueFrom: function(obj, directObserver) { | |
238 for (var i = 0; i < this.length; i++) { | |
239 if (obj == null) | |
240 return; | |
241 obj = obj[this[i]]; | |
242 } | |
243 return obj; | |
244 }, | |
245 | |
246 iterateObjects: function(obj, observe) { | |
247 for (var i = 0; i < this.length; i++) { | |
248 if (i) | |
249 obj = obj[this[i - 1]]; | |
250 if (!isObject(obj)) | |
251 return; | |
252 observe(obj); | |
253 } | |
254 }, | |
255 | |
256 compiledGetValueFromFn: function() { | |
257 var accessors = this.map(function(ident) { | |
258 return isIndex(ident) ? '["' + ident + '"]' : '.' + ident; | |
259 }); | |
260 | |
261 var str = ''; | |
262 var pathString = 'obj'; | |
263 str += 'if (obj != null'; | |
264 var i = 0; | |
265 for (; i < (this.length - 1); i++) { | |
266 var ident = this[i]; | |
267 pathString += accessors[i]; | |
268 str += ' &&\n ' + pathString + ' != null'; | |
269 } | |
270 str += ')\n'; | |
271 | |
272 pathString += accessors[i]; | |
273 | |
274 str += ' return ' + pathString + ';\nelse\n return undefined;'; | |
275 return new Function('obj', str); | |
276 }, | |
277 | |
278 setValueFrom: function(obj, value) { | |
279 if (!this.length) | |
280 return false; | |
281 | |
282 for (var i = 0; i < this.length - 1; i++) { | |
283 if (!isObject(obj)) | |
284 return false; | |
285 obj = obj[this[i]]; | |
286 } | |
287 | |
288 if (!isObject(obj)) | |
289 return false; | |
290 | |
291 obj[this[i]] = value; | |
292 return true; | |
293 } | |
294 }); | |
295 | |
296 var invalidPath = new Path('', constructorIsPrivate); | |
297 invalidPath.valid = false; | |
298 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; | |
299 | |
300 var MAX_DIRTY_CHECK_CYCLES = 1000; | |
301 | |
302 function dirtyCheck(observer) { | |
303 var cycles = 0; | |
304 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { | |
305 cycles++; | |
306 } | |
307 if (global.testingExposeCycleCount) | |
308 global.dirtyCheckCycleCount = cycles; | |
309 | |
310 return cycles > 0; | |
311 } | |
312 | |
313 function objectIsEmpty(object) { | |
314 for (var prop in object) | |
315 return false; | |
316 return true; | |
317 } | |
318 | |
319 function diffIsEmpty(diff) { | |
320 return objectIsEmpty(diff.added) && | |
321 objectIsEmpty(diff.removed) && | |
322 objectIsEmpty(diff.changed); | |
323 } | |
324 | |
325 function diffObjectFromOldObject(object, oldObject) { | |
326 var added = {}; | |
327 var removed = {}; | |
328 var changed = {}; | |
329 | |
330 for (var prop in oldObject) { | |
331 var newValue = object[prop]; | |
332 | |
333 if (newValue !== undefined && newValue === oldObject[prop]) | |
334 continue; | |
335 | |
336 if (!(prop in object)) { | |
337 removed[prop] = undefined; | |
338 continue; | |
339 } | |
340 | |
341 if (newValue !== oldObject[prop]) | |
342 changed[prop] = newValue; | |
343 } | |
344 | |
345 for (var prop in object) { | |
346 if (prop in oldObject) | |
347 continue; | |
348 | |
349 added[prop] = object[prop]; | |
350 } | |
351 | |
352 if (Array.isArray(object) && object.length !== oldObject.length) | |
353 changed.length = object.length; | |
354 | |
355 return { | |
356 added: added, | |
357 removed: removed, | |
358 changed: changed | |
359 }; | |
360 } | |
361 | |
362 var eomTasks = []; | |
363 function runEOMTasks() { | |
364 if (!eomTasks.length) | |
365 return false; | |
366 | |
367 for (var i = 0; i < eomTasks.length; i++) { | |
368 eomTasks[i](); | |
369 } | |
370 eomTasks.length = 0; | |
371 return true; | |
372 } | |
373 | |
374 var runEOM = hasObserve ? (function(){ | |
375 var eomObj = { pingPong: true }; | |
376 var eomRunScheduled = false; | |
377 | |
378 Object.observe(eomObj, function() { | |
379 runEOMTasks(); | |
380 eomRunScheduled = false; | |
381 }); | |
382 | |
383 return function(fn) { | |
384 eomTasks.push(fn); | |
385 if (!eomRunScheduled) { | |
386 eomRunScheduled = true; | |
387 eomObj.pingPong = !eomObj.pingPong; | |
388 } | |
389 }; | |
390 })() : | |
391 (function() { | |
392 return function(fn) { | |
393 eomTasks.push(fn); | |
394 }; | |
395 })(); | |
396 | |
397 var observedObjectCache = []; | |
398 | |
399 function newObservedObject() { | |
400 var observer; | |
401 var object; | |
402 var discardRecords = false; | |
403 var first = true; | |
404 | |
405 function callback(records) { | |
406 if (observer && observer.state_ === OPENED && !discardRecords) | |
407 observer.check_(records); | |
408 } | |
409 | |
410 return { | |
411 open: function(obs) { | |
412 if (observer) | |
413 throw Error('ObservedObject in use'); | |
414 | |
415 if (!first) | |
416 Object.deliverChangeRecords(callback); | |
417 | |
418 observer = obs; | |
419 first = false; | |
420 }, | |
421 observe: function(obj, arrayObserve) { | |
422 object = obj; | |
423 if (arrayObserve) | |
424 Array.observe(object, callback); | |
425 else | |
426 Object.observe(object, callback); | |
427 }, | |
428 deliver: function(discard) { | |
429 discardRecords = discard; | |
430 Object.deliverChangeRecords(callback); | |
431 discardRecords = false; | |
432 }, | |
433 close: function() { | |
434 observer = undefined; | |
435 Object.unobserve(object, callback); | |
436 observedObjectCache.push(this); | |
437 } | |
438 }; | |
439 } | |
440 | |
441 function getObservedObject(observer, object, arrayObserve) { | |
442 var dir = observedObjectCache.pop() || newObservedObject(); | |
443 dir.open(observer); | |
444 dir.observe(object, arrayObserve); | |
445 return dir; | |
446 } | |
447 | |
448 var emptyArray = []; | |
449 var observedSetCache = []; | |
450 | |
451 function newObservedSet() { | |
452 var observers = []; | |
453 var observerCount = 0; | |
454 var objects = []; | |
455 var toRemove = emptyArray; | |
456 var resetNeeded = false; | |
457 var resetScheduled = false; | |
458 | |
459 function observe(obj) { | |
460 if (!obj) | |
461 return; | |
462 | |
463 var index = toRemove.indexOf(obj); | |
464 if (index >= 0) { | |
465 toRemove[index] = undefined; | |
466 objects.push(obj); | |
467 } else if (objects.indexOf(obj) < 0) { | |
468 objects.push(obj); | |
469 Object.observe(obj, callback); | |
470 } | |
471 | |
472 observe(Object.getPrototypeOf(obj)); | |
473 } | |
474 | |
475 function reset() { | |
476 var objs = toRemove === emptyArray ? [] : toRemove; | |
477 toRemove = objects; | |
478 objects = objs; | |
479 | |
480 var observer; | |
481 for (var id in observers) { | |
482 observer = observers[id]; | |
483 if (!observer || observer.state_ != OPENED) | |
484 continue; | |
485 | |
486 observer.iterateObjects_(observe); | |
487 } | |
488 | |
489 for (var i = 0; i < toRemove.length; i++) { | |
490 var obj = toRemove[i]; | |
491 if (obj) | |
492 Object.unobserve(obj, callback); | |
493 } | |
494 | |
495 toRemove.length = 0; | |
496 } | |
497 | |
498 function scheduledReset() { | |
499 resetScheduled = false; | |
500 if (!resetNeeded) | |
501 return; | |
502 | |
503 reset(); | |
504 } | |
505 | |
506 function scheduleReset() { | |
507 if (resetScheduled) | |
508 return; | |
509 | |
510 resetNeeded = true; | |
511 resetScheduled = true; | |
512 runEOM(scheduledReset); | |
513 } | |
514 | |
515 function callback() { | |
516 reset(); | |
517 | |
518 var observer; | |
519 | |
520 for (var id in observers) { | |
521 observer = observers[id]; | |
522 if (!observer || observer.state_ != OPENED) | |
523 continue; | |
524 | |
525 observer.check_(); | |
526 } | |
527 } | |
528 | |
529 var record = { | |
530 object: undefined, | |
531 objects: objects, | |
532 open: function(obs) { | |
533 observers[obs.id_] = obs; | |
534 observerCount++; | |
535 obs.iterateObjects_(observe); | |
536 }, | |
537 close: function(obs) { | |
538 var anyLeft = false; | |
539 | |
540 observers[obs.id_] = undefined; | |
541 observerCount--; | |
542 | |
543 if (observerCount) { | |
544 scheduleReset(); | |
545 return; | |
546 } | |
547 resetNeeded = false; | |
548 | |
549 for (var i = 0; i < objects.length; i++) { | |
550 Object.unobserve(objects[i], callback); | |
551 Observer.unobservedCount++; | |
552 } | |
553 | |
554 observers.length = 0; | |
555 objects.length = 0; | |
556 observedSetCache.push(this); | |
557 }, | |
558 reset: scheduleReset | |
559 }; | |
560 | |
561 return record; | |
562 } | |
563 | |
564 var lastObservedSet; | |
565 | |
566 function getObservedSet(observer, obj) { | |
567 if (!lastObservedSet || lastObservedSet.object !== obj) { | |
568 lastObservedSet = observedSetCache.pop() || newObservedSet(); | |
569 lastObservedSet.object = obj; | |
570 } | |
571 lastObservedSet.open(observer); | |
572 return lastObservedSet; | |
573 } | |
574 | |
575 var UNOPENED = 0; | |
576 var OPENED = 1; | |
577 var CLOSED = 2; | |
578 var RESETTING = 3; | |
579 | |
580 var nextObserverId = 1; | |
581 | |
582 function Observer() { | |
583 this.state_ = UNOPENED; | |
584 this.callback_ = undefined; | |
585 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef | |
586 this.directObserver_ = undefined; | |
587 this.value_ = undefined; | |
588 this.id_ = nextObserverId++; | |
589 } | |
590 | |
591 Observer.prototype = { | |
592 open: function(callback, target) { | |
593 if (this.state_ != UNOPENED) | |
594 throw Error('Observer has already been opened.'); | |
595 | |
596 addToAll(this); | |
597 this.callback_ = callback; | |
598 this.target_ = target; | |
599 this.state_ = OPENED; | |
600 this.connect_(); | |
601 return this.value_; | |
602 }, | |
603 | |
604 close: function() { | |
605 if (this.state_ != OPENED) | |
606 return; | |
607 | |
608 removeFromAll(this); | |
609 this.state_ = CLOSED; | |
610 this.disconnect_(); | |
611 this.value_ = undefined; | |
612 this.callback_ = undefined; | |
613 this.target_ = undefined; | |
614 }, | |
615 | |
616 deliver: function() { | |
617 if (this.state_ != OPENED) | |
618 return; | |
619 | |
620 dirtyCheck(this); | |
621 }, | |
622 | |
623 report_: function(changes) { | |
624 try { | |
625 this.callback_.apply(this.target_, changes); | |
626 } catch (ex) { | |
627 Observer._errorThrownDuringCallback = true; | |
628 console.error('Exception caught during observer callback: ' + | |
629 (ex.stack || ex)); | |
630 } | |
631 }, | |
632 | |
633 discardChanges: function() { | |
634 this.check_(undefined, true); | |
635 return this.value_; | |
636 } | |
637 } | |
638 | |
639 var collectObservers = !hasObserve; | |
640 var allObservers; | |
641 Observer._allObserversCount = 0; | |
642 | |
643 if (collectObservers) { | |
644 allObservers = []; | |
645 } | |
646 | |
647 function addToAll(observer) { | |
648 Observer._allObserversCount++; | |
649 if (!collectObservers) | |
650 return; | |
651 | |
652 allObservers.push(observer); | |
653 } | |
654 | |
655 function removeFromAll(observer) { | |
656 Observer._allObserversCount--; | |
657 } | |
658 | |
659 var runningMicrotaskCheckpoint = false; | |
660 | |
661 var hasDebugForceFullDelivery = hasObserve && (function() { | |
662 try { | |
663 eval('%RunMicrotasks()'); | |
664 return true; | |
665 } catch (ex) { | |
666 return false; | |
667 } | |
668 })(); | |
669 | |
670 global.Platform = global.Platform || {}; | |
671 | |
672 global.Platform.performMicrotaskCheckpoint = function() { | |
673 if (runningMicrotaskCheckpoint) | |
674 return; | |
675 | |
676 if (hasDebugForceFullDelivery) { | |
677 eval('%RunMicrotasks()'); | |
678 return; | |
679 } | |
680 | |
681 if (!collectObservers) | |
682 return; | |
683 | |
684 runningMicrotaskCheckpoint = true; | |
685 | |
686 var cycles = 0; | |
687 var anyChanged, toCheck; | |
688 | |
689 do { | |
690 cycles++; | |
691 toCheck = allObservers; | |
692 allObservers = []; | |
693 anyChanged = false; | |
694 | |
695 for (var i = 0; i < toCheck.length; i++) { | |
696 var observer = toCheck[i]; | |
697 if (observer.state_ != OPENED) | |
698 continue; | |
699 | |
700 if (observer.check_()) | |
701 anyChanged = true; | |
702 | |
703 allObservers.push(observer); | |
704 } | |
705 if (runEOMTasks()) | |
706 anyChanged = true; | |
707 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); | |
708 | |
709 if (global.testingExposeCycleCount) | |
710 global.dirtyCheckCycleCount = cycles; | |
711 | |
712 runningMicrotaskCheckpoint = false; | |
713 }; | |
714 | |
715 if (collectObservers) { | |
716 global.Platform.clearObservers = function() { | |
717 allObservers = []; | |
718 }; | |
719 } | |
720 | |
721 function ObjectObserver(object) { | |
722 Observer.call(this); | |
723 this.value_ = object; | |
724 this.oldObject_ = undefined; | |
725 } | |
726 | |
727 ObjectObserver.prototype = createObject({ | |
728 __proto__: Observer.prototype, | |
729 | |
730 arrayObserve: false, | |
731 | |
732 connect_: function(callback, target) { | |
733 if (hasObserve) { | |
734 this.directObserver_ = getObservedObject(this, this.value_, | |
735 this.arrayObserve); | |
736 } else { | |
737 this.oldObject_ = this.copyObject(this.value_); | |
738 } | |
739 | |
740 }, | |
741 | |
742 copyObject: function(object) { | |
743 var copy = Array.isArray(object) ? [] : {}; | |
744 for (var prop in object) { | |
745 copy[prop] = object[prop]; | |
746 }; | |
747 if (Array.isArray(object)) | |
748 copy.length = object.length; | |
749 return copy; | |
750 }, | |
751 | |
752 check_: function(changeRecords, skipChanges) { | |
753 var diff; | |
754 var oldValues; | |
755 if (hasObserve) { | |
756 if (!changeRecords) | |
757 return false; | |
758 | |
759 oldValues = {}; | |
760 diff = diffObjectFromChangeRecords(this.value_, changeRecords, | |
761 oldValues); | |
762 } else { | |
763 oldValues = this.oldObject_; | |
764 diff = diffObjectFromOldObject(this.value_, this.oldObject_); | |
765 } | |
766 | |
767 if (diffIsEmpty(diff)) | |
768 return false; | |
769 | |
770 if (!hasObserve) | |
771 this.oldObject_ = this.copyObject(this.value_); | |
772 | |
773 this.report_([ | |
774 diff.added || {}, | |
775 diff.removed || {}, | |
776 diff.changed || {}, | |
777 function(property) { | |
778 return oldValues[property]; | |
779 } | |
780 ]); | |
781 | |
782 return true; | |
783 }, | |
784 | |
785 disconnect_: function() { | |
786 if (hasObserve) { | |
787 this.directObserver_.close(); | |
788 this.directObserver_ = undefined; | |
789 } else { | |
790 this.oldObject_ = undefined; | |
791 } | |
792 }, | |
793 | |
794 deliver: function() { | |
795 if (this.state_ != OPENED) | |
796 return; | |
797 | |
798 if (hasObserve) | |
799 this.directObserver_.deliver(false); | |
800 else | |
801 dirtyCheck(this); | |
802 }, | |
803 | |
804 discardChanges: function() { | |
805 if (this.directObserver_) | |
806 this.directObserver_.deliver(true); | |
807 else | |
808 this.oldObject_ = this.copyObject(this.value_); | |
809 | |
810 return this.value_; | |
811 } | |
812 }); | |
813 | |
814 function ArrayObserver(array) { | |
815 if (!Array.isArray(array)) | |
816 throw Error('Provided object is not an Array'); | |
817 ObjectObserver.call(this, array); | |
818 } | |
819 | |
820 ArrayObserver.prototype = createObject({ | |
821 | |
822 __proto__: ObjectObserver.prototype, | |
823 | |
824 arrayObserve: true, | |
825 | |
826 copyObject: function(arr) { | |
827 return arr.slice(); | |
828 }, | |
829 | |
830 check_: function(changeRecords) { | |
831 var splices; | |
832 if (hasObserve) { | |
833 if (!changeRecords) | |
834 return false; | |
835 splices = projectArraySplices(this.value_, changeRecords); | |
836 } else { | |
837 splices = calcSplices(this.value_, 0, this.value_.length, | |
838 this.oldObject_, 0, this.oldObject_.length); | |
839 } | |
840 | |
841 if (!splices || !splices.length) | |
842 return false; | |
843 | |
844 if (!hasObserve) | |
845 this.oldObject_ = this.copyObject(this.value_); | |
846 | |
847 this.report_([splices]); | |
848 return true; | |
849 } | |
850 }); | |
851 | |
852 ArrayObserver.applySplices = function(previous, current, splices) { | |
853 splices.forEach(function(splice) { | |
854 var spliceArgs = [splice.index, splice.removed.length]; | |
855 var addIndex = splice.index; | |
856 while (addIndex < splice.index + splice.addedCount) { | |
857 spliceArgs.push(current[addIndex]); | |
858 addIndex++; | |
859 } | |
860 | |
861 Array.prototype.splice.apply(previous, spliceArgs); | |
862 }); | |
863 }; | |
864 | |
865 function PathObserver(object, path) { | |
866 Observer.call(this); | |
867 | |
868 this.object_ = object; | |
869 this.path_ = path instanceof Path ? path : getPath(path); | |
870 this.directObserver_ = undefined; | |
871 } | |
872 | |
873 PathObserver.prototype = createObject({ | |
874 __proto__: Observer.prototype, | |
875 | |
876 connect_: function() { | |
877 if (hasObserve) | |
878 this.directObserver_ = getObservedSet(this, this.object_); | |
879 | |
880 this.check_(undefined, true); | |
881 }, | |
882 | |
883 disconnect_: function() { | |
884 this.value_ = undefined; | |
885 | |
886 if (this.directObserver_) { | |
887 this.directObserver_.close(this); | |
888 this.directObserver_ = undefined; | |
889 } | |
890 }, | |
891 | |
892 iterateObjects_: function(observe) { | |
893 this.path_.iterateObjects(this.object_, observe); | |
894 }, | |
895 | |
896 check_: function(changeRecords, skipChanges) { | |
897 var oldValue = this.value_; | |
898 this.value_ = this.path_.getValueFrom(this.object_); | |
899 if (skipChanges || areSameValue(this.value_, oldValue)) | |
900 return false; | |
901 | |
902 this.report_([this.value_, oldValue]); | |
903 return true; | |
904 }, | |
905 | |
906 setValue: function(newValue) { | |
907 if (this.path_) | |
908 this.path_.setValueFrom(this.object_, newValue); | |
909 } | |
910 }); | |
911 | |
912 function CompoundObserver() { | |
913 Observer.call(this); | |
914 | |
915 this.value_ = []; | |
916 this.directObserver_ = undefined; | |
917 this.observed_ = []; | |
918 } | |
919 | |
920 var observerSentinel = {}; | |
921 | |
922 CompoundObserver.prototype = createObject({ | |
923 __proto__: Observer.prototype, | |
924 | |
925 connect_: function() { | |
926 this.check_(undefined, true); | |
927 | |
928 if (!hasObserve) | |
929 return; | |
930 | |
931 var object; | |
932 var needsDirectObserver = false; | |
933 for (var i = 0; i < this.observed_.length; i += 2) { | |
934 object = this.observed_[i] | |
935 if (object !== observerSentinel) { | |
936 needsDirectObserver = true; | |
937 break; | |
938 } | |
939 } | |
940 | |
941 if (this.directObserver_) { | |
942 if (needsDirectObserver) { | |
943 this.directObserver_.reset(); | |
944 return; | |
945 } | |
946 this.directObserver_.close(); | |
947 this.directObserver_ = undefined; | |
948 return; | |
949 } | |
950 | |
951 if (needsDirectObserver) | |
952 this.directObserver_ = getObservedSet(this, object); | |
953 }, | |
954 | |
955 closeObservers_: function() { | |
956 for (var i = 0; i < this.observed_.length; i += 2) { | |
957 if (this.observed_[i] === observerSentinel) | |
958 this.observed_[i + 1].close(); | |
959 } | |
960 this.observed_.length = 0; | |
961 }, | |
962 | |
963 disconnect_: function() { | |
964 this.value_ = undefined; | |
965 | |
966 if (this.directObserver_) { | |
967 this.directObserver_.close(this); | |
968 this.directObserver_ = undefined; | |
969 } | |
970 | |
971 this.closeObservers_(); | |
972 }, | |
973 | |
974 addPath: function(object, path) { | |
975 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
976 throw Error('Cannot add paths once started.'); | |
977 | |
978 this.observed_.push(object, path instanceof Path ? path : getPath(path)); | |
979 }, | |
980 | |
981 addObserver: function(observer) { | |
982 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
983 throw Error('Cannot add observers once started.'); | |
984 | |
985 observer.open(this.deliver, this); | |
986 this.observed_.push(observerSentinel, observer); | |
987 }, | |
988 | |
989 startReset: function() { | |
990 if (this.state_ != OPENED) | |
991 throw Error('Can only reset while open'); | |
992 | |
993 this.state_ = RESETTING; | |
994 this.closeObservers_(); | |
995 }, | |
996 | |
997 finishReset: function() { | |
998 if (this.state_ != RESETTING) | |
999 throw Error('Can only finishReset after startReset'); | |
1000 this.state_ = OPENED; | |
1001 this.connect_(); | |
1002 | |
1003 return this.value_; | |
1004 }, | |
1005 | |
1006 iterateObjects_: function(observe) { | |
1007 var object; | |
1008 for (var i = 0; i < this.observed_.length; i += 2) { | |
1009 object = this.observed_[i] | |
1010 if (object !== observerSentinel) | |
1011 this.observed_[i + 1].iterateObjects(object, observe) | |
1012 } | |
1013 }, | |
1014 | |
1015 check_: function(changeRecords, skipChanges) { | |
1016 var oldValues; | |
1017 for (var i = 0; i < this.observed_.length; i += 2) { | |
1018 var pathOrObserver = this.observed_[i+1]; | |
1019 var object = this.observed_[i]; | |
1020 var value = object === observerSentinel ? | |
1021 pathOrObserver.discardChanges() : | |
1022 pathOrObserver.getValueFrom(object) | |
1023 | |
1024 if (skipChanges) { | |
1025 this.value_[i / 2] = value; | |
1026 continue; | |
1027 } | |
1028 | |
1029 if (areSameValue(value, this.value_[i / 2])) | |
1030 continue; | |
1031 | |
1032 oldValues = oldValues || []; | |
1033 oldValues[i / 2] = this.value_[i / 2]; | |
1034 this.value_[i / 2] = value; | |
1035 } | |
1036 | |
1037 if (!oldValues) | |
1038 return false; | |
1039 | |
1040 // TODO(rafaelw): Having observed_ as the third callback arg here is | |
1041 // pretty lame API. Fix. | |
1042 this.report_([this.value_, oldValues, this.observed_]); | |
1043 return true; | |
1044 } | |
1045 }); | |
1046 | |
1047 function identFn(value) { return value; } | |
1048 | |
1049 function ObserverTransform(observable, getValueFn, setValueFn, | |
1050 dontPassThroughSet) { | |
1051 this.callback_ = undefined; | |
1052 this.target_ = undefined; | |
1053 this.value_ = undefined; | |
1054 this.observable_ = observable; | |
1055 this.getValueFn_ = getValueFn || identFn; | |
1056 this.setValueFn_ = setValueFn || identFn; | |
1057 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this | |
1058 // at the moment because of a bug in it's dependency tracking. | |
1059 this.dontPassThroughSet_ = dontPassThroughSet; | |
1060 } | |
1061 | |
1062 ObserverTransform.prototype = { | |
1063 open: function(callback, target) { | |
1064 this.callback_ = callback; | |
1065 this.target_ = target; | |
1066 this.value_ = | |
1067 this.getValueFn_(this.observable_.open(this.observedCallback_, this)); | |
1068 return this.value_; | |
1069 }, | |
1070 | |
1071 observedCallback_: function(value) { | |
1072 value = this.getValueFn_(value); | |
1073 if (areSameValue(value, this.value_)) | |
1074 return; | |
1075 var oldValue = this.value_; | |
1076 this.value_ = value; | |
1077 this.callback_.call(this.target_, this.value_, oldValue); | |
1078 }, | |
1079 | |
1080 discardChanges: function() { | |
1081 this.value_ = this.getValueFn_(this.observable_.discardChanges()); | |
1082 return this.value_; | |
1083 }, | |
1084 | |
1085 deliver: function() { | |
1086 return this.observable_.deliver(); | |
1087 }, | |
1088 | |
1089 setValue: function(value) { | |
1090 value = this.setValueFn_(value); | |
1091 if (!this.dontPassThroughSet_ && this.observable_.setValue) | |
1092 return this.observable_.setValue(value); | |
1093 }, | |
1094 | |
1095 close: function() { | |
1096 if (this.observable_) | |
1097 this.observable_.close(); | |
1098 this.callback_ = undefined; | |
1099 this.target_ = undefined; | |
1100 this.observable_ = undefined; | |
1101 this.value_ = undefined; | |
1102 this.getValueFn_ = undefined; | |
1103 this.setValueFn_ = undefined; | |
1104 } | |
1105 } | |
1106 | |
1107 var expectedRecordTypes = { | |
1108 add: true, | |
1109 update: true, | |
1110 delete: true | |
1111 }; | |
1112 | |
1113 function notifyFunction(object, name) { | |
1114 if (typeof Object.observe !== 'function') | |
1115 return; | |
1116 | |
1117 var notifier = Object.getNotifier(object); | |
1118 return function(type, oldValue) { | |
1119 var changeRecord = { | |
1120 object: object, | |
1121 type: type, | |
1122 name: name | |
1123 }; | |
1124 if (arguments.length === 2) | |
1125 changeRecord.oldValue = oldValue; | |
1126 notifier.notify(changeRecord); | |
1127 } | |
1128 } | |
1129 | |
1130 Observer.defineComputedProperty = function(target, name, observable) { | |
1131 var notify = notifyFunction(target, name); | |
1132 var value = observable.open(function(newValue, oldValue) { | |
1133 value = newValue; | |
1134 if (notify) | |
1135 notify('update', oldValue); | |
1136 }); | |
1137 | |
1138 Object.defineProperty(target, name, { | |
1139 get: function() { | |
1140 observable.deliver(); | |
1141 return value; | |
1142 }, | |
1143 set: function(newValue) { | |
1144 observable.setValue(newValue); | |
1145 return newValue; | |
1146 }, | |
1147 configurable: true | |
1148 }); | |
1149 | |
1150 return { | |
1151 close: function() { | |
1152 observable.close(); | |
1153 Object.defineProperty(target, name, { | |
1154 value: value, | |
1155 writable: true, | |
1156 configurable: true | |
1157 }); | |
1158 } | |
1159 }; | |
1160 } | |
1161 | |
1162 function diffObjectFromChangeRecords(object, changeRecords, oldValues) { | |
1163 var added = {}; | |
1164 var removed = {}; | |
1165 | |
1166 for (var i = 0; i < changeRecords.length; i++) { | |
1167 var record = changeRecords[i]; | |
1168 if (!expectedRecordTypes[record.type]) { | |
1169 console.error('Unknown changeRecord type: ' + record.type); | |
1170 console.error(record); | |
1171 continue; | |
1172 } | |
1173 | |
1174 if (!(record.name in oldValues)) | |
1175 oldValues[record.name] = record.oldValue; | |
1176 | |
1177 if (record.type == 'update') | |
1178 continue; | |
1179 | |
1180 if (record.type == 'add') { | |
1181 if (record.name in removed) | |
1182 delete removed[record.name]; | |
1183 else | |
1184 added[record.name] = true; | |
1185 | |
1186 continue; | |
1187 } | |
1188 | |
1189 // type = 'delete' | |
1190 if (record.name in added) { | |
1191 delete added[record.name]; | |
1192 delete oldValues[record.name]; | |
1193 } else { | |
1194 removed[record.name] = true; | |
1195 } | |
1196 } | |
1197 | |
1198 for (var prop in added) | |
1199 added[prop] = object[prop]; | |
1200 | |
1201 for (var prop in removed) | |
1202 removed[prop] = undefined; | |
1203 | |
1204 var changed = {}; | |
1205 for (var prop in oldValues) { | |
1206 if (prop in added || prop in removed) | |
1207 continue; | |
1208 | |
1209 var newValue = object[prop]; | |
1210 if (oldValues[prop] !== newValue) | |
1211 changed[prop] = newValue; | |
1212 } | |
1213 | |
1214 return { | |
1215 added: added, | |
1216 removed: removed, | |
1217 changed: changed | |
1218 }; | |
1219 } | |
1220 | |
1221 function newSplice(index, removed, addedCount) { | |
1222 return { | |
1223 index: index, | |
1224 removed: removed, | |
1225 addedCount: addedCount | |
1226 }; | |
1227 } | |
1228 | |
1229 var EDIT_LEAVE = 0; | |
1230 var EDIT_UPDATE = 1; | |
1231 var EDIT_ADD = 2; | |
1232 var EDIT_DELETE = 3; | |
1233 | |
1234 function ArraySplice() {} | |
1235 | |
1236 ArraySplice.prototype = { | |
1237 | |
1238 // Note: This function is *based* on the computation of the Levenshtein | |
1239 // "edit" distance. The one change is that "updates" are treated as two | |
1240 // edits - not one. With Array splices, an update is really a delete | |
1241 // followed by an add. By retaining this, we optimize for "keeping" the | |
1242 // maximum array items in the original array. For example: | |
1243 // | |
1244 // 'xxxx123' -> '123yyyy' | |
1245 // | |
1246 // With 1-edit updates, the shortest path would be just to update all seven | |
1247 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This | |
1248 // leaves the substring '123' intact. | |
1249 calcEditDistances: function(current, currentStart, currentEnd, | |
1250 old, oldStart, oldEnd) { | |
1251 // "Deletion" columns | |
1252 var rowCount = oldEnd - oldStart + 1; | |
1253 var columnCount = currentEnd - currentStart + 1; | |
1254 var distances = new Array(rowCount); | |
1255 | |
1256 // "Addition" rows. Initialize null column. | |
1257 for (var i = 0; i < rowCount; i++) { | |
1258 distances[i] = new Array(columnCount); | |
1259 distances[i][0] = i; | |
1260 } | |
1261 | |
1262 // Initialize null row | |
1263 for (var j = 0; j < columnCount; j++) | |
1264 distances[0][j] = j; | |
1265 | |
1266 for (var i = 1; i < rowCount; i++) { | |
1267 for (var j = 1; j < columnCount; j++) { | |
1268 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) | |
1269 distances[i][j] = distances[i - 1][j - 1]; | |
1270 else { | |
1271 var north = distances[i - 1][j] + 1; | |
1272 var west = distances[i][j - 1] + 1; | |
1273 distances[i][j] = north < west ? north : west; | |
1274 } | |
1275 } | |
1276 } | |
1277 | |
1278 return distances; | |
1279 }, | |
1280 | |
1281 // This starts at the final weight, and walks "backward" by finding | |
1282 // the minimum previous weight recursively until the origin of the weight | |
1283 // matrix. | |
1284 spliceOperationsFromEditDistances: function(distances) { | |
1285 var i = distances.length - 1; | |
1286 var j = distances[0].length - 1; | |
1287 var current = distances[i][j]; | |
1288 var edits = []; | |
1289 while (i > 0 || j > 0) { | |
1290 if (i == 0) { | |
1291 edits.push(EDIT_ADD); | |
1292 j--; | |
1293 continue; | |
1294 } | |
1295 if (j == 0) { | |
1296 edits.push(EDIT_DELETE); | |
1297 i--; | |
1298 continue; | |
1299 } | |
1300 var northWest = distances[i - 1][j - 1]; | |
1301 var west = distances[i - 1][j]; | |
1302 var north = distances[i][j - 1]; | |
1303 | |
1304 var min; | |
1305 if (west < north) | |
1306 min = west < northWest ? west : northWest; | |
1307 else | |
1308 min = north < northWest ? north : northWest; | |
1309 | |
1310 if (min == northWest) { | |
1311 if (northWest == current) { | |
1312 edits.push(EDIT_LEAVE); | |
1313 } else { | |
1314 edits.push(EDIT_UPDATE); | |
1315 current = northWest; | |
1316 } | |
1317 i--; | |
1318 j--; | |
1319 } else if (min == west) { | |
1320 edits.push(EDIT_DELETE); | |
1321 i--; | |
1322 current = west; | |
1323 } else { | |
1324 edits.push(EDIT_ADD); | |
1325 j--; | |
1326 current = north; | |
1327 } | |
1328 } | |
1329 | |
1330 edits.reverse(); | |
1331 return edits; | |
1332 }, | |
1333 | |
1334 /** | |
1335 * Splice Projection functions: | |
1336 * | |
1337 * A splice map is a representation of how a previous array of items | |
1338 * was transformed into a new array of items. Conceptually it is a list of | |
1339 * tuples of | |
1340 * | |
1341 * <index, removed, addedCount> | |
1342 * | |
1343 * which are kept in ascending index order of. The tuple represents that at | |
1344 * the |index|, |removed| sequence of items were removed, and counting forwa
rd | |
1345 * from |index|, |addedCount| items were added. | |
1346 */ | |
1347 | |
1348 /** | |
1349 * Lacking individual splice mutation information, the minimal set of | |
1350 * splices can be synthesized given the previous state and final state of an | |
1351 * array. The basic approach is to calculate the edit distance matrix and | |
1352 * choose the shortest path through it. | |
1353 * | |
1354 * Complexity: O(l * p) | |
1355 * l: The length of the current array | |
1356 * p: The length of the old array | |
1357 */ | |
1358 calcSplices: function(current, currentStart, currentEnd, | |
1359 old, oldStart, oldEnd) { | |
1360 var prefixCount = 0; | |
1361 var suffixCount = 0; | |
1362 | |
1363 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); | |
1364 if (currentStart == 0 && oldStart == 0) | |
1365 prefixCount = this.sharedPrefix(current, old, minLength); | |
1366 | |
1367 if (currentEnd == current.length && oldEnd == old.length) | |
1368 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); | |
1369 | |
1370 currentStart += prefixCount; | |
1371 oldStart += prefixCount; | |
1372 currentEnd -= suffixCount; | |
1373 oldEnd -= suffixCount; | |
1374 | |
1375 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) | |
1376 return []; | |
1377 | |
1378 if (currentStart == currentEnd) { | |
1379 var splice = newSplice(currentStart, [], 0); | |
1380 while (oldStart < oldEnd) | |
1381 splice.removed.push(old[oldStart++]); | |
1382 | |
1383 return [ splice ]; | |
1384 } else if (oldStart == oldEnd) | |
1385 return [ newSplice(currentStart, [], currentEnd - currentStart) ]; | |
1386 | |
1387 var ops = this.spliceOperationsFromEditDistances( | |
1388 this.calcEditDistances(current, currentStart, currentEnd, | |
1389 old, oldStart, oldEnd)); | |
1390 | |
1391 var splice = undefined; | |
1392 var splices = []; | |
1393 var index = currentStart; | |
1394 var oldIndex = oldStart; | |
1395 for (var i = 0; i < ops.length; i++) { | |
1396 switch(ops[i]) { | |
1397 case EDIT_LEAVE: | |
1398 if (splice) { | |
1399 splices.push(splice); | |
1400 splice = undefined; | |
1401 } | |
1402 | |
1403 index++; | |
1404 oldIndex++; | |
1405 break; | |
1406 case EDIT_UPDATE: | |
1407 if (!splice) | |
1408 splice = newSplice(index, [], 0); | |
1409 | |
1410 splice.addedCount++; | |
1411 index++; | |
1412 | |
1413 splice.removed.push(old[oldIndex]); | |
1414 oldIndex++; | |
1415 break; | |
1416 case EDIT_ADD: | |
1417 if (!splice) | |
1418 splice = newSplice(index, [], 0); | |
1419 | |
1420 splice.addedCount++; | |
1421 index++; | |
1422 break; | |
1423 case EDIT_DELETE: | |
1424 if (!splice) | |
1425 splice = newSplice(index, [], 0); | |
1426 | |
1427 splice.removed.push(old[oldIndex]); | |
1428 oldIndex++; | |
1429 break; | |
1430 } | |
1431 } | |
1432 | |
1433 if (splice) { | |
1434 splices.push(splice); | |
1435 } | |
1436 return splices; | |
1437 }, | |
1438 | |
1439 sharedPrefix: function(current, old, searchLength) { | |
1440 for (var i = 0; i < searchLength; i++) | |
1441 if (!this.equals(current[i], old[i])) | |
1442 return i; | |
1443 return searchLength; | |
1444 }, | |
1445 | |
1446 sharedSuffix: function(current, old, searchLength) { | |
1447 var index1 = current.length; | |
1448 var index2 = old.length; | |
1449 var count = 0; | |
1450 while (count < searchLength && this.equals(current[--index1], old[--index2
])) | |
1451 count++; | |
1452 | |
1453 return count; | |
1454 }, | |
1455 | |
1456 calculateSplices: function(current, previous) { | |
1457 return this.calcSplices(current, 0, current.length, previous, 0, | |
1458 previous.length); | |
1459 }, | |
1460 | |
1461 equals: function(currentValue, previousValue) { | |
1462 return currentValue === previousValue; | |
1463 } | |
1464 }; | |
1465 | |
1466 var arraySplice = new ArraySplice(); | |
1467 | |
1468 function calcSplices(current, currentStart, currentEnd, | |
1469 old, oldStart, oldEnd) { | |
1470 return arraySplice.calcSplices(current, currentStart, currentEnd, | |
1471 old, oldStart, oldEnd); | |
1472 } | |
1473 | |
1474 function intersect(start1, end1, start2, end2) { | |
1475 // Disjoint | |
1476 if (end1 < start2 || end2 < start1) | |
1477 return -1; | |
1478 | |
1479 // Adjacent | |
1480 if (end1 == start2 || end2 == start1) | |
1481 return 0; | |
1482 | |
1483 // Non-zero intersect, span1 first | |
1484 if (start1 < start2) { | |
1485 if (end1 < end2) | |
1486 return end1 - start2; // Overlap | |
1487 else | |
1488 return end2 - start2; // Contained | |
1489 } else { | |
1490 // Non-zero intersect, span2 first | |
1491 if (end2 < end1) | |
1492 return end2 - start1; // Overlap | |
1493 else | |
1494 return end1 - start1; // Contained | |
1495 } | |
1496 } | |
1497 | |
1498 function mergeSplice(splices, index, removed, addedCount) { | |
1499 | |
1500 var splice = newSplice(index, removed, addedCount); | |
1501 | |
1502 var inserted = false; | |
1503 var insertionOffset = 0; | |
1504 | |
1505 for (var i = 0; i < splices.length; i++) { | |
1506 var current = splices[i]; | |
1507 current.index += insertionOffset; | |
1508 | |
1509 if (inserted) | |
1510 continue; | |
1511 | |
1512 var intersectCount = intersect(splice.index, | |
1513 splice.index + splice.removed.length, | |
1514 current.index, | |
1515 current.index + current.addedCount); | |
1516 | |
1517 if (intersectCount >= 0) { | |
1518 // Merge the two splices | |
1519 | |
1520 splices.splice(i, 1); | |
1521 i--; | |
1522 | |
1523 insertionOffset -= current.addedCount - current.removed.length; | |
1524 | |
1525 splice.addedCount += current.addedCount - intersectCount; | |
1526 var deleteCount = splice.removed.length + | |
1527 current.removed.length - intersectCount; | |
1528 | |
1529 if (!splice.addedCount && !deleteCount) { | |
1530 // merged splice is a noop. discard. | |
1531 inserted = true; | |
1532 } else { | |
1533 var removed = current.removed; | |
1534 | |
1535 if (splice.index < current.index) { | |
1536 // some prefix of splice.removed is prepended to current.removed. | |
1537 var prepend = splice.removed.slice(0, current.index - splice.index); | |
1538 Array.prototype.push.apply(prepend, removed); | |
1539 removed = prepend; | |
1540 } | |
1541 | |
1542 if (splice.index + splice.removed.length > current.index + current.add
edCount) { | |
1543 // some suffix of splice.removed is appended to current.removed. | |
1544 var append = splice.removed.slice(current.index + current.addedCount
- splice.index); | |
1545 Array.prototype.push.apply(removed, append); | |
1546 } | |
1547 | |
1548 splice.removed = removed; | |
1549 if (current.index < splice.index) { | |
1550 splice.index = current.index; | |
1551 } | |
1552 } | |
1553 } else if (splice.index < current.index) { | |
1554 // Insert splice here. | |
1555 | |
1556 inserted = true; | |
1557 | |
1558 splices.splice(i, 0, splice); | |
1559 i++; | |
1560 | |
1561 var offset = splice.addedCount - splice.removed.length | |
1562 current.index += offset; | |
1563 insertionOffset += offset; | |
1564 } | |
1565 } | |
1566 | |
1567 if (!inserted) | |
1568 splices.push(splice); | |
1569 } | |
1570 | |
1571 function createInitialSplices(array, changeRecords) { | |
1572 var splices = []; | |
1573 | |
1574 for (var i = 0; i < changeRecords.length; i++) { | |
1575 var record = changeRecords[i]; | |
1576 switch(record.type) { | |
1577 case 'splice': | |
1578 mergeSplice(splices, record.index, record.removed.slice(), record.adde
dCount); | |
1579 break; | |
1580 case 'add': | |
1581 case 'update': | |
1582 case 'delete': | |
1583 if (!isIndex(record.name)) | |
1584 continue; | |
1585 var index = toNumber(record.name); | |
1586 if (index < 0) | |
1587 continue; | |
1588 mergeSplice(splices, index, [record.oldValue], 1); | |
1589 break; | |
1590 default: | |
1591 console.error('Unexpected record type: ' + JSON.stringify(record)); | |
1592 break; | |
1593 } | |
1594 } | |
1595 | |
1596 return splices; | |
1597 } | |
1598 | |
1599 function projectArraySplices(array, changeRecords) { | |
1600 var splices = []; | |
1601 | |
1602 createInitialSplices(array, changeRecords).forEach(function(splice) { | |
1603 if (splice.addedCount == 1 && splice.removed.length == 1) { | |
1604 if (splice.removed[0] !== array[splice.index]) | |
1605 splices.push(splice); | |
1606 | |
1607 return | |
1608 }; | |
1609 | |
1610 splices = splices.concat(calcSplices(array, splice.index, splice.index + s
plice.addedCount, | |
1611 splice.removed, 0, splice.removed.len
gth)); | |
1612 }); | |
1613 | |
1614 return splices; | |
1615 } | |
1616 | |
1617 global.Observer = Observer; | |
1618 global.Observer.runEOM_ = runEOM; | |
1619 global.Observer.hasObjectObserve = hasObserve; | |
1620 global.ArrayObserver = ArrayObserver; | |
1621 global.ArrayObserver.calculateSplices = function(current, previous) { | |
1622 return arraySplice.calculateSplices(current, previous); | |
1623 }; | |
1624 | |
1625 global.ArraySplice = ArraySplice; | |
1626 global.ObjectObserver = ObjectObserver; | |
1627 global.PathObserver = PathObserver; | |
1628 global.CompoundObserver = CompoundObserver; | |
1629 global.Path = Path; | |
1630 global.ObserverTransform = ObserverTransform; | |
1631 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m
odule ? global : this || window); | |
1632 | |
1633 // prepoulate window.Platform.flags for default controls | |
1634 window.Platform = window.Platform || {}; | |
1635 // prepopulate window.logFlags if necessary | |
1636 window.logFlags = window.logFlags || {}; | |
1637 // process flags | |
1638 (function(scope){ | |
1639 // import | |
1640 var flags = scope.flags || {}; | |
1641 // populate flags from location | |
1642 location.search.slice(1).split('&').forEach(function(o) { | |
1643 o = o.split('='); | |
1644 o[0] && (flags[o[0]] = o[1] || true); | |
1645 }); | |
1646 var entryPoint = document.currentScript || | |
1647 document.querySelector('script[src*="platform.js"]'); | |
1648 if (entryPoint) { | |
1649 var a = entryPoint.attributes; | |
1650 for (var i = 0, n; i < a.length; i++) { | |
1651 n = a[i]; | |
1652 if (n.name !== 'src') { | |
1653 flags[n.name] = n.value || true; | |
1654 } | |
1655 } | |
1656 } | |
1657 if (flags.log) { | |
1658 flags.log.split(',').forEach(function(f) { | |
1659 window.logFlags[f] = true; | |
1660 }); | |
1661 } | |
1662 // If any of these flags match 'native', then force native ShadowDOM; any | |
1663 // other truthy value, or failure to detect native | |
1664 // ShadowDOM, results in polyfill | |
1665 flags.shadow = flags.shadow || flags.shadowdom || flags.polyfill; | |
1666 if (flags.shadow === 'native') { | |
1667 flags.shadow = false; | |
1668 } else { | |
1669 flags.shadow = flags.shadow || !HTMLElement.prototype.createShadowRoot; | |
1670 } | |
1671 | |
1672 if (flags.shadow && document.querySelectorAll('script').length > 1) { | |
1673 console.warn('platform.js is not the first script on the page. ' + | |
1674 'See http://www.polymer-project.org/docs/start/platform.html#setup ' + | |
1675 'for details.'); | |
1676 } | |
1677 | |
1678 // CustomElements polyfill flag | |
1679 if (flags.register) { | |
1680 window.CustomElements = window.CustomElements || {flags: {}}; | |
1681 window.CustomElements.flags.register = flags.register; | |
1682 } | |
1683 | |
1684 if (flags.imports) { | |
1685 window.HTMLImports = window.HTMLImports || {flags: {}}; | |
1686 window.HTMLImports.flags.imports = flags.imports; | |
1687 } | |
1688 | |
1689 // export | |
1690 scope.flags = flags; | |
1691 })(Platform); | |
1692 | |
1693 // select ShadowDOM impl | |
1694 if (Platform.flags.shadow) { | |
1695 | |
1696 // Copyright 2012 The Polymer Authors. All rights reserved. | |
1697 // Use of this source code is goverened by a BSD-style | |
1698 // license that can be found in the LICENSE file. | |
1699 | |
1700 window.ShadowDOMPolyfill = {}; | |
1701 | |
1702 (function(scope) { | |
1703 'use strict'; | |
1704 | |
1705 var constructorTable = new WeakMap(); | |
1706 var nativePrototypeTable = new WeakMap(); | |
1707 var wrappers = Object.create(null); | |
1708 | |
1709 // Don't test for eval if document has CSP securityPolicy object and we can | |
1710 // see that eval is not supported. This avoids an error message in console | |
1711 // even when the exception is caught | |
1712 var hasEval = !('securityPolicy' in document) || | |
1713 document.securityPolicy.allowsEval; | |
1714 if (hasEval) { | |
1715 try { | |
1716 var f = new Function('', 'return true;'); | |
1717 hasEval = f(); | |
1718 } catch (ex) { | |
1719 hasEval = false; | |
1720 } | |
1721 } | |
1722 | |
1723 function assert(b) { | |
1724 if (!b) | |
1725 throw new Error('Assertion failed'); | |
1726 }; | |
1727 | |
1728 var defineProperty = Object.defineProperty; | |
1729 var getOwnPropertyNames = Object.getOwnPropertyNames; | |
1730 var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; | |
1731 | |
1732 function mixin(to, from) { | |
1733 getOwnPropertyNames(from).forEach(function(name) { | |
1734 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); | |
1735 }); | |
1736 return to; | |
1737 }; | |
1738 | |
1739 function mixinStatics(to, from) { | |
1740 getOwnPropertyNames(from).forEach(function(name) { | |
1741 switch (name) { | |
1742 case 'arguments': | |
1743 case 'caller': | |
1744 case 'length': | |
1745 case 'name': | |
1746 case 'prototype': | |
1747 case 'toString': | |
1748 return; | |
1749 } | |
1750 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); | |
1751 }); | |
1752 return to; | |
1753 }; | |
1754 | |
1755 function oneOf(object, propertyNames) { | |
1756 for (var i = 0; i < propertyNames.length; i++) { | |
1757 if (propertyNames[i] in object) | |
1758 return propertyNames[i]; | |
1759 } | |
1760 } | |
1761 | |
1762 // Mozilla's old DOM bindings are bretty busted: | |
1763 // https://bugzilla.mozilla.org/show_bug.cgi?id=855844 | |
1764 // Make sure they are create before we start modifying things. | |
1765 getOwnPropertyNames(window); | |
1766 | |
1767 function getWrapperConstructor(node) { | |
1768 var nativePrototype = node.__proto__ || Object.getPrototypeOf(node); | |
1769 var wrapperConstructor = constructorTable.get(nativePrototype); | |
1770 if (wrapperConstructor) | |
1771 return wrapperConstructor; | |
1772 | |
1773 var parentWrapperConstructor = getWrapperConstructor(nativePrototype); | |
1774 | |
1775 var GeneratedWrapper = createWrapperConstructor(parentWrapperConstructor); | |
1776 registerInternal(nativePrototype, GeneratedWrapper, node); | |
1777 | |
1778 return GeneratedWrapper; | |
1779 } | |
1780 | |
1781 function addForwardingProperties(nativePrototype, wrapperPrototype) { | |
1782 installProperty(nativePrototype, wrapperPrototype, true); | |
1783 } | |
1784 | |
1785 function registerInstanceProperties(wrapperPrototype, instanceObject) { | |
1786 installProperty(instanceObject, wrapperPrototype, false); | |
1787 } | |
1788 | |
1789 var isFirefox = /Firefox/.test(navigator.userAgent); | |
1790 | |
1791 // This is used as a fallback when getting the descriptor fails in | |
1792 // installProperty. | |
1793 var dummyDescriptor = { | |
1794 get: function() {}, | |
1795 set: function(v) {}, | |
1796 configurable: true, | |
1797 enumerable: true | |
1798 }; | |
1799 | |
1800 function isEventHandlerName(name) { | |
1801 return /^on[a-z]+$/.test(name); | |
1802 } | |
1803 | |
1804 function isIdentifierName(name) { | |
1805 return /^\w[a-zA-Z_0-9]*$/.test(name); | |
1806 } | |
1807 | |
1808 function getGetter(name) { | |
1809 return hasEval && isIdentifierName(name) ? | |
1810 new Function('return this.impl.' + name) : | |
1811 function() { return this.impl[name]; }; | |
1812 } | |
1813 | |
1814 function getSetter(name) { | |
1815 return hasEval && isIdentifierName(name) ? | |
1816 new Function('v', 'this.impl.' + name + ' = v') : | |
1817 function(v) { this.impl[name] = v; }; | |
1818 } | |
1819 | |
1820 function getMethod(name) { | |
1821 return hasEval && isIdentifierName(name) ? | |
1822 new Function('return this.impl.' + name + | |
1823 '.apply(this.impl, arguments)') : | |
1824 function() { return this.impl[name].apply(this.impl, arguments); }; | |
1825 } | |
1826 | |
1827 function getDescriptor(source, name) { | |
1828 try { | |
1829 return Object.getOwnPropertyDescriptor(source, name); | |
1830 } catch (ex) { | |
1831 // JSC and V8 both use data properties instead of accessors which can | |
1832 // cause getting the property desciptor to throw an exception. | |
1833 // https://bugs.webkit.org/show_bug.cgi?id=49739 | |
1834 return dummyDescriptor; | |
1835 } | |
1836 } | |
1837 | |
1838 function installProperty(source, target, allowMethod, opt_blacklist) { | |
1839 var names = getOwnPropertyNames(source); | |
1840 for (var i = 0; i < names.length; i++) { | |
1841 var name = names[i]; | |
1842 if (name === 'polymerBlackList_') | |
1843 continue; | |
1844 | |
1845 if (name in target) | |
1846 continue; | |
1847 | |
1848 if (source.polymerBlackList_ && source.polymerBlackList_[name]) | |
1849 continue; | |
1850 | |
1851 if (isFirefox) { | |
1852 // Tickle Firefox's old bindings. | |
1853 source.__lookupGetter__(name); | |
1854 } | |
1855 var descriptor = getDescriptor(source, name); | |
1856 var getter, setter; | |
1857 if (allowMethod && typeof descriptor.value === 'function') { | |
1858 target[name] = getMethod(name); | |
1859 continue; | |
1860 } | |
1861 | |
1862 var isEvent = isEventHandlerName(name); | |
1863 if (isEvent) | |
1864 getter = scope.getEventHandlerGetter(name); | |
1865 else | |
1866 getter = getGetter(name); | |
1867 | |
1868 if (descriptor.writable || descriptor.set) { | |
1869 if (isEvent) | |
1870 setter = scope.getEventHandlerSetter(name); | |
1871 else | |
1872 setter = getSetter(name); | |
1873 } | |
1874 | |
1875 defineProperty(target, name, { | |
1876 get: getter, | |
1877 set: setter, | |
1878 configurable: descriptor.configurable, | |
1879 enumerable: descriptor.enumerable | |
1880 }); | |
1881 } | |
1882 } | |
1883 | |
1884 /** | |
1885 * @param {Function} nativeConstructor | |
1886 * @param {Function} wrapperConstructor | |
1887 * @param {Object=} opt_instance If present, this is used to extract | |
1888 * properties from an instance object. | |
1889 */ | |
1890 function register(nativeConstructor, wrapperConstructor, opt_instance) { | |
1891 var nativePrototype = nativeConstructor.prototype; | |
1892 registerInternal(nativePrototype, wrapperConstructor, opt_instance); | |
1893 mixinStatics(wrapperConstructor, nativeConstructor); | |
1894 } | |
1895 | |
1896 function registerInternal(nativePrototype, wrapperConstructor, opt_instance) { | |
1897 var wrapperPrototype = wrapperConstructor.prototype; | |
1898 assert(constructorTable.get(nativePrototype) === undefined); | |
1899 | |
1900 constructorTable.set(nativePrototype, wrapperConstructor); | |
1901 nativePrototypeTable.set(wrapperPrototype, nativePrototype); | |
1902 | |
1903 addForwardingProperties(nativePrototype, wrapperPrototype); | |
1904 if (opt_instance) | |
1905 registerInstanceProperties(wrapperPrototype, opt_instance); | |
1906 defineProperty(wrapperPrototype, 'constructor', { | |
1907 value: wrapperConstructor, | |
1908 configurable: true, | |
1909 enumerable: false, | |
1910 writable: true | |
1911 }); | |
1912 // Set it again. Some VMs optimizes objects that are used as prototypes. | |
1913 wrapperConstructor.prototype = wrapperPrototype; | |
1914 } | |
1915 | |
1916 function isWrapperFor(wrapperConstructor, nativeConstructor) { | |
1917 return constructorTable.get(nativeConstructor.prototype) === | |
1918 wrapperConstructor; | |
1919 } | |
1920 | |
1921 /** | |
1922 * Creates a generic wrapper constructor based on |object| and its | |
1923 * constructor. | |
1924 * @param {Node} object | |
1925 * @return {Function} The generated constructor. | |
1926 */ | |
1927 function registerObject(object) { | |
1928 var nativePrototype = Object.getPrototypeOf(object); | |
1929 | |
1930 var superWrapperConstructor = getWrapperConstructor(nativePrototype); | |
1931 var GeneratedWrapper = createWrapperConstructor(superWrapperConstructor); | |
1932 registerInternal(nativePrototype, GeneratedWrapper, object); | |
1933 | |
1934 return GeneratedWrapper; | |
1935 } | |
1936 | |
1937 function createWrapperConstructor(superWrapperConstructor) { | |
1938 function GeneratedWrapper(node) { | |
1939 superWrapperConstructor.call(this, node); | |
1940 } | |
1941 var p = Object.create(superWrapperConstructor.prototype); | |
1942 p.constructor = GeneratedWrapper; | |
1943 GeneratedWrapper.prototype = p; | |
1944 | |
1945 return GeneratedWrapper; | |
1946 } | |
1947 | |
1948 var OriginalDOMImplementation = window.DOMImplementation; | |
1949 var OriginalEventTarget = window.EventTarget; | |
1950 var OriginalEvent = window.Event; | |
1951 var OriginalNode = window.Node; | |
1952 var OriginalWindow = window.Window; | |
1953 var OriginalRange = window.Range; | |
1954 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; | |
1955 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; | |
1956 var OriginalSVGElementInstance = window.SVGElementInstance; | |
1957 | |
1958 function isWrapper(object) { | |
1959 return object instanceof wrappers.EventTarget || | |
1960 object instanceof wrappers.Event || | |
1961 object instanceof wrappers.Range || | |
1962 object instanceof wrappers.DOMImplementation || | |
1963 object instanceof wrappers.CanvasRenderingContext2D || | |
1964 wrappers.WebGLRenderingContext && | |
1965 object instanceof wrappers.WebGLRenderingContext; | |
1966 } | |
1967 | |
1968 function isNative(object) { | |
1969 return OriginalEventTarget && object instanceof OriginalEventTarget || | |
1970 object instanceof OriginalNode || | |
1971 object instanceof OriginalEvent || | |
1972 object instanceof OriginalWindow || | |
1973 object instanceof OriginalRange || | |
1974 object instanceof OriginalDOMImplementation || | |
1975 object instanceof OriginalCanvasRenderingContext2D || | |
1976 OriginalWebGLRenderingContext && | |
1977 object instanceof OriginalWebGLRenderingContext || | |
1978 OriginalSVGElementInstance && | |
1979 object instanceof OriginalSVGElementInstance; | |
1980 } | |
1981 | |
1982 /** | |
1983 * Wraps a node in a WrapperNode. If there already exists a wrapper for the | |
1984 * |node| that wrapper is returned instead. | |
1985 * @param {Node} node | |
1986 * @return {WrapperNode} | |
1987 */ | |
1988 function wrap(impl) { | |
1989 if (impl === null) | |
1990 return null; | |
1991 | |
1992 assert(isNative(impl)); | |
1993 return impl.polymerWrapper_ || | |
1994 (impl.polymerWrapper_ = new (getWrapperConstructor(impl))(impl)); | |
1995 } | |
1996 | |
1997 /** | |
1998 * Unwraps a wrapper and returns the node it is wrapping. | |
1999 * @param {WrapperNode} wrapper | |
2000 * @return {Node} | |
2001 */ | |
2002 function unwrap(wrapper) { | |
2003 if (wrapper === null) | |
2004 return null; | |
2005 assert(isWrapper(wrapper)); | |
2006 return wrapper.impl; | |
2007 } | |
2008 | |
2009 /** | |
2010 * Unwraps object if it is a wrapper. | |
2011 * @param {Object} object | |
2012 * @return {Object} The native implementation object. | |
2013 */ | |
2014 function unwrapIfNeeded(object) { | |
2015 return object && isWrapper(object) ? unwrap(object) : object; | |
2016 } | |
2017 | |
2018 /** | |
2019 * Wraps object if it is not a wrapper. | |
2020 * @param {Object} object | |
2021 * @return {Object} The wrapper for object. | |
2022 */ | |
2023 function wrapIfNeeded(object) { | |
2024 return object && !isWrapper(object) ? wrap(object) : object; | |
2025 } | |
2026 | |
2027 /** | |
2028 * Overrides the current wrapper (if any) for node. | |
2029 * @param {Node} node | |
2030 * @param {WrapperNode=} wrapper If left out the wrapper will be created as | |
2031 * needed next time someone wraps the node. | |
2032 */ | |
2033 function rewrap(node, wrapper) { | |
2034 if (wrapper === null) | |
2035 return; | |
2036 assert(isNative(node)); | |
2037 assert(wrapper === undefined || isWrapper(wrapper)); | |
2038 node.polymerWrapper_ = wrapper; | |
2039 } | |
2040 | |
2041 function defineGetter(constructor, name, getter) { | |
2042 defineProperty(constructor.prototype, name, { | |
2043 get: getter, | |
2044 configurable: true, | |
2045 enumerable: true | |
2046 }); | |
2047 } | |
2048 | |
2049 function defineWrapGetter(constructor, name) { | |
2050 defineGetter(constructor, name, function() { | |
2051 return wrap(this.impl[name]); | |
2052 }); | |
2053 } | |
2054 | |
2055 /** | |
2056 * Forwards existing methods on the native object to the wrapper methods. | |
2057 * This does not wrap any of the arguments or the return value since the | |
2058 * wrapper implementation already takes care of that. | |
2059 * @param {Array.<Function>} constructors | |
2060 * @parem {Array.<string>} names | |
2061 */ | |
2062 function forwardMethodsToWrapper(constructors, names) { | |
2063 constructors.forEach(function(constructor) { | |
2064 names.forEach(function(name) { | |
2065 constructor.prototype[name] = function() { | |
2066 var w = wrapIfNeeded(this); | |
2067 return w[name].apply(w, arguments); | |
2068 }; | |
2069 }); | |
2070 }); | |
2071 } | |
2072 | |
2073 scope.assert = assert; | |
2074 scope.constructorTable = constructorTable; | |
2075 scope.defineGetter = defineGetter; | |
2076 scope.defineWrapGetter = defineWrapGetter; | |
2077 scope.forwardMethodsToWrapper = forwardMethodsToWrapper; | |
2078 scope.isWrapper = isWrapper; | |
2079 scope.isWrapperFor = isWrapperFor; | |
2080 scope.mixin = mixin; | |
2081 scope.nativePrototypeTable = nativePrototypeTable; | |
2082 scope.oneOf = oneOf; | |
2083 scope.registerObject = registerObject; | |
2084 scope.registerWrapper = register; | |
2085 scope.rewrap = rewrap; | |
2086 scope.unwrap = unwrap; | |
2087 scope.unwrapIfNeeded = unwrapIfNeeded; | |
2088 scope.wrap = wrap; | |
2089 scope.wrapIfNeeded = wrapIfNeeded; | |
2090 scope.wrappers = wrappers; | |
2091 | |
2092 })(window.ShadowDOMPolyfill); | |
2093 | |
2094 /* | |
2095 * Copyright 2013 The Polymer Authors. All rights reserved. | |
2096 * Use of this source code is goverened by a BSD-style | |
2097 * license that can be found in the LICENSE file. | |
2098 */ | |
2099 | |
2100 (function(context) { | |
2101 'use strict'; | |
2102 | |
2103 var OriginalMutationObserver = window.MutationObserver; | |
2104 var callbacks = []; | |
2105 var pending = false; | |
2106 var timerFunc; | |
2107 | |
2108 function handle() { | |
2109 pending = false; | |
2110 var copies = callbacks.slice(0); | |
2111 callbacks = []; | |
2112 for (var i = 0; i < copies.length; i++) { | |
2113 (0, copies[i])(); | |
2114 } | |
2115 } | |
2116 | |
2117 if (OriginalMutationObserver) { | |
2118 var counter = 1; | |
2119 var observer = new OriginalMutationObserver(handle); | |
2120 var textNode = document.createTextNode(counter); | |
2121 observer.observe(textNode, {characterData: true}); | |
2122 | |
2123 timerFunc = function() { | |
2124 counter = (counter + 1) % 2; | |
2125 textNode.data = counter; | |
2126 }; | |
2127 | |
2128 } else { | |
2129 timerFunc = window.setImmediate || window.setTimeout; | |
2130 } | |
2131 | |
2132 function setEndOfMicrotask(func) { | |
2133 callbacks.push(func); | |
2134 if (pending) | |
2135 return; | |
2136 pending = true; | |
2137 timerFunc(handle, 0); | |
2138 } | |
2139 | |
2140 context.setEndOfMicrotask = setEndOfMicrotask; | |
2141 | |
2142 })(window.ShadowDOMPolyfill); | |
2143 | |
2144 /* | |
2145 * Copyright 2013 The Polymer Authors. All rights reserved. | |
2146 * Use of this source code is goverened by a BSD-style | |
2147 * license that can be found in the LICENSE file. | |
2148 */ | |
2149 | |
2150 (function(scope) { | |
2151 'use strict'; | |
2152 | |
2153 var setEndOfMicrotask = scope.setEndOfMicrotask | |
2154 var wrapIfNeeded = scope.wrapIfNeeded | |
2155 var wrappers = scope.wrappers; | |
2156 | |
2157 var registrationsTable = new WeakMap(); | |
2158 var globalMutationObservers = []; | |
2159 var isScheduled = false; | |
2160 | |
2161 function scheduleCallback(observer) { | |
2162 if (isScheduled) | |
2163 return; | |
2164 setEndOfMicrotask(notifyObservers); | |
2165 isScheduled = true; | |
2166 } | |
2167 | |
2168 // http://dom.spec.whatwg.org/#mutation-observers | |
2169 function notifyObservers() { | |
2170 isScheduled = false; | |
2171 | |
2172 do { | |
2173 var notifyList = globalMutationObservers.slice(); | |
2174 var anyNonEmpty = false; | |
2175 for (var i = 0; i < notifyList.length; i++) { | |
2176 var mo = notifyList[i]; | |
2177 var queue = mo.takeRecords(); | |
2178 removeTransientObserversFor(mo); | |
2179 if (queue.length) { | |
2180 mo.callback_(queue, mo); | |
2181 anyNonEmpty = true; | |
2182 } | |
2183 } | |
2184 } while (anyNonEmpty); | |
2185 } | |
2186 | |
2187 /** | |
2188 * @param {string} type | |
2189 * @param {Node} target | |
2190 * @constructor | |
2191 */ | |
2192 function MutationRecord(type, target) { | |
2193 this.type = type; | |
2194 this.target = target; | |
2195 this.addedNodes = new wrappers.NodeList(); | |
2196 this.removedNodes = new wrappers.NodeList(); | |
2197 this.previousSibling = null; | |
2198 this.nextSibling = null; | |
2199 this.attributeName = null; | |
2200 this.attributeNamespace = null; | |
2201 this.oldValue = null; | |
2202 } | |
2203 | |
2204 /** | |
2205 * Registers transient observers to ancestor and its ancesors for the node | |
2206 * which was removed. | |
2207 * @param {!Node} ancestor | |
2208 * @param {!Node} node | |
2209 */ | |
2210 function registerTransientObservers(ancestor, node) { | |
2211 for (; ancestor; ancestor = ancestor.parentNode) { | |
2212 var registrations = registrationsTable.get(ancestor); | |
2213 if (!registrations) | |
2214 continue; | |
2215 for (var i = 0; i < registrations.length; i++) { | |
2216 var registration = registrations[i]; | |
2217 if (registration.options.subtree) | |
2218 registration.addTransientObserver(node); | |
2219 } | |
2220 } | |
2221 } | |
2222 | |
2223 function removeTransientObserversFor(observer) { | |
2224 for (var i = 0; i < observer.nodes_.length; i++) { | |
2225 var node = observer.nodes_[i]; | |
2226 var registrations = registrationsTable.get(node); | |
2227 if (!registrations) | |
2228 return; | |
2229 for (var j = 0; j < registrations.length; j++) { | |
2230 var registration = registrations[j]; | |
2231 if (registration.observer === observer) | |
2232 registration.removeTransientObservers(); | |
2233 } | |
2234 } | |
2235 } | |
2236 | |
2237 // http://dom.spec.whatwg.org/#queue-a-mutation-record | |
2238 function enqueueMutation(target, type, data) { | |
2239 // 1. | |
2240 var interestedObservers = Object.create(null); | |
2241 var associatedStrings = Object.create(null); | |
2242 | |
2243 // 2. | |
2244 for (var node = target; node; node = node.parentNode) { | |
2245 // 3. | |
2246 var registrations = registrationsTable.get(node); | |
2247 if (!registrations) | |
2248 continue; | |
2249 for (var j = 0; j < registrations.length; j++) { | |
2250 var registration = registrations[j]; | |
2251 var options = registration.options; | |
2252 // 1. | |
2253 if (node !== target && !options.subtree) | |
2254 continue; | |
2255 | |
2256 // 2. | |
2257 if (type === 'attributes' && !options.attributes) | |
2258 continue; | |
2259 | |
2260 // 3. If type is "attributes", options's attributeFilter is present, and | |
2261 // either options's attributeFilter does not contain name or namespace | |
2262 // is non-null, continue. | |
2263 if (type === 'attributes' && options.attributeFilter && | |
2264 (data.namespace !== null || | |
2265 options.attributeFilter.indexOf(data.name) === -1)) { | |
2266 continue; | |
2267 } | |
2268 | |
2269 // 4. | |
2270 if (type === 'characterData' && !options.characterData) | |
2271 continue; | |
2272 | |
2273 // 5. | |
2274 if (type === 'childList' && !options.childList) | |
2275 continue; | |
2276 | |
2277 // 6. | |
2278 var observer = registration.observer; | |
2279 interestedObservers[observer.uid_] = observer; | |
2280 | |
2281 // 7. If either type is "attributes" and options's attributeOldValue is | |
2282 // true, or type is "characterData" and options's characterDataOldValue | |
2283 // is true, set the paired string of registered observer's observer in | |
2284 // interested observers to oldValue. | |
2285 if (type === 'attributes' && options.attributeOldValue || | |
2286 type === 'characterData' && options.characterDataOldValue) { | |
2287 associatedStrings[observer.uid_] = data.oldValue; | |
2288 } | |
2289 } | |
2290 } | |
2291 | |
2292 var anyRecordsEnqueued = false; | |
2293 | |
2294 // 4. | |
2295 for (var uid in interestedObservers) { | |
2296 var observer = interestedObservers[uid]; | |
2297 var record = new MutationRecord(type, target); | |
2298 | |
2299 // 2. | |
2300 if ('name' in data && 'namespace' in data) { | |
2301 record.attributeName = data.name; | |
2302 record.attributeNamespace = data.namespace; | |
2303 } | |
2304 | |
2305 // 3. | |
2306 if (data.addedNodes) | |
2307 record.addedNodes = data.addedNodes; | |
2308 | |
2309 // 4. | |
2310 if (data.removedNodes) | |
2311 record.removedNodes = data.removedNodes; | |
2312 | |
2313 // 5. | |
2314 if (data.previousSibling) | |
2315 record.previousSibling = data.previousSibling; | |
2316 | |
2317 // 6. | |
2318 if (data.nextSibling) | |
2319 record.nextSibling = data.nextSibling; | |
2320 | |
2321 // 7. | |
2322 if (associatedStrings[uid] !== undefined) | |
2323 record.oldValue = associatedStrings[uid]; | |
2324 | |
2325 // 8. | |
2326 observer.records_.push(record); | |
2327 | |
2328 anyRecordsEnqueued = true; | |
2329 } | |
2330 | |
2331 if (anyRecordsEnqueued) | |
2332 scheduleCallback(); | |
2333 } | |
2334 | |
2335 var slice = Array.prototype.slice; | |
2336 | |
2337 /** | |
2338 * @param {!Object} options | |
2339 * @constructor | |
2340 */ | |
2341 function MutationObserverOptions(options) { | |
2342 this.childList = !!options.childList; | |
2343 this.subtree = !!options.subtree; | |
2344 | |
2345 // 1. If either options' attributeOldValue or attributeFilter is present | |
2346 // and options' attributes is omitted, set options' attributes to true. | |
2347 if (!('attributes' in options) && | |
2348 ('attributeOldValue' in options || 'attributeFilter' in options)) { | |
2349 this.attributes = true; | |
2350 } else { | |
2351 this.attributes = !!options.attributes; | |
2352 } | |
2353 | |
2354 // 2. If options' characterDataOldValue is present and options' | |
2355 // characterData is omitted, set options' characterData to true. | |
2356 if ('characterDataOldValue' in options && !('characterData' in options)) | |
2357 this.characterData = true; | |
2358 else | |
2359 this.characterData = !!options.characterData; | |
2360 | |
2361 // 3. & 4. | |
2362 if (!this.attributes && | |
2363 (options.attributeOldValue || 'attributeFilter' in options) || | |
2364 // 5. | |
2365 !this.characterData && options.characterDataOldValue) { | |
2366 throw new TypeError(); | |
2367 } | |
2368 | |
2369 this.characterData = !!options.characterData; | |
2370 this.attributeOldValue = !!options.attributeOldValue; | |
2371 this.characterDataOldValue = !!options.characterDataOldValue; | |
2372 if ('attributeFilter' in options) { | |
2373 if (options.attributeFilter == null || | |
2374 typeof options.attributeFilter !== 'object') { | |
2375 throw new TypeError(); | |
2376 } | |
2377 this.attributeFilter = slice.call(options.attributeFilter); | |
2378 } else { | |
2379 this.attributeFilter = null; | |
2380 } | |
2381 } | |
2382 | |
2383 var uidCounter = 0; | |
2384 | |
2385 /** | |
2386 * The class that maps to the DOM MutationObserver interface. | |
2387 * @param {Function} callback. | |
2388 * @constructor | |
2389 */ | |
2390 function MutationObserver(callback) { | |
2391 this.callback_ = callback; | |
2392 this.nodes_ = []; | |
2393 this.records_ = []; | |
2394 this.uid_ = ++uidCounter; | |
2395 | |
2396 // This will leak. There is no way to implement this without WeakRefs :'( | |
2397 globalMutationObservers.push(this); | |
2398 } | |
2399 | |
2400 MutationObserver.prototype = { | |
2401 // http://dom.spec.whatwg.org/#dom-mutationobserver-observe | |
2402 observe: function(target, options) { | |
2403 target = wrapIfNeeded(target); | |
2404 | |
2405 var newOptions = new MutationObserverOptions(options); | |
2406 | |
2407 // 6. | |
2408 var registration; | |
2409 var registrations = registrationsTable.get(target); | |
2410 if (!registrations) | |
2411 registrationsTable.set(target, registrations = []); | |
2412 | |
2413 for (var i = 0; i < registrations.length; i++) { | |
2414 if (registrations[i].observer === this) { | |
2415 registration = registrations[i]; | |
2416 // 6.1. | |
2417 registration.removeTransientObservers(); | |
2418 // 6.2. | |
2419 registration.options = newOptions; | |
2420 } | |
2421 } | |
2422 | |
2423 // 7. | |
2424 if (!registration) { | |
2425 registration = new Registration(this, target, newOptions); | |
2426 registrations.push(registration); | |
2427 this.nodes_.push(target); | |
2428 } | |
2429 }, | |
2430 | |
2431 // http://dom.spec.whatwg.org/#dom-mutationobserver-disconnect | |
2432 disconnect: function() { | |
2433 this.nodes_.forEach(function(node) { | |
2434 var registrations = registrationsTable.get(node); | |
2435 for (var i = 0; i < registrations.length; i++) { | |
2436 var registration = registrations[i]; | |
2437 if (registration.observer === this) { | |
2438 registrations.splice(i, 1); | |
2439 // Each node can only have one registered observer associated with | |
2440 // this observer. | |
2441 break; | |
2442 } | |
2443 } | |
2444 }, this); | |
2445 this.records_ = []; | |
2446 }, | |
2447 | |
2448 takeRecords: function() { | |
2449 var copyOfRecords = this.records_; | |
2450 this.records_ = []; | |
2451 return copyOfRecords; | |
2452 } | |
2453 }; | |
2454 | |
2455 /** | |
2456 * Class used to represent a registered observer. | |
2457 * @param {MutationObserver} observer | |
2458 * @param {Node} target | |
2459 * @param {MutationObserverOptions} options | |
2460 * @constructor | |
2461 */ | |
2462 function Registration(observer, target, options) { | |
2463 this.observer = observer; | |
2464 this.target = target; | |
2465 this.options = options; | |
2466 this.transientObservedNodes = []; | |
2467 } | |
2468 | |
2469 Registration.prototype = { | |
2470 /** | |
2471 * Adds a transient observer on node. The transient observer gets removed | |
2472 * next time we deliver the change records. | |
2473 * @param {Node} node | |
2474 */ | |
2475 addTransientObserver: function(node) { | |
2476 // Don't add transient observers on the target itself. We already have all | |
2477 // the required listeners set up on the target. | |
2478 if (node === this.target) | |
2479 return; | |
2480 | |
2481 this.transientObservedNodes.push(node); | |
2482 var registrations = registrationsTable.get(node); | |
2483 if (!registrations) | |
2484 registrationsTable.set(node, registrations = []); | |
2485 | |
2486 // We know that registrations does not contain this because we already | |
2487 // checked if node === this.target. | |
2488 registrations.push(this); | |
2489 }, | |
2490 | |
2491 removeTransientObservers: function() { | |
2492 var transientObservedNodes = this.transientObservedNodes; | |
2493 this.transientObservedNodes = []; | |
2494 | |
2495 for (var i = 0; i < transientObservedNodes.length; i++) { | |
2496 var node = transientObservedNodes[i]; | |
2497 var registrations = registrationsTable.get(node); | |
2498 for (var j = 0; j < registrations.length; j++) { | |
2499 if (registrations[j] === this) { | |
2500 registrations.splice(j, 1); | |
2501 // Each node can only have one registered observer associated with | |
2502 // this observer. | |
2503 break; | |
2504 } | |
2505 } | |
2506 } | |
2507 } | |
2508 }; | |
2509 | |
2510 scope.enqueueMutation = enqueueMutation; | |
2511 scope.registerTransientObservers = registerTransientObservers; | |
2512 scope.wrappers.MutationObserver = MutationObserver; | |
2513 scope.wrappers.MutationRecord = MutationRecord; | |
2514 | |
2515 })(window.ShadowDOMPolyfill); | |
2516 | |
2517 /** | |
2518 * Copyright 2014 The Polymer Authors. All rights reserved. | |
2519 * Use of this source code is goverened by a BSD-style | |
2520 * license that can be found in the LICENSE file. | |
2521 */ | |
2522 | |
2523 (function(scope) { | |
2524 'use strict'; | |
2525 | |
2526 /** | |
2527 * A tree scope represents the root of a tree. All nodes in a tree point to | |
2528 * the same TreeScope object. The tree scope of a node get set the first time | |
2529 * it is accessed or when a node is added or remove to a tree. | |
2530 * @constructor | |
2531 */ | |
2532 function TreeScope(root, parent) { | |
2533 this.root = root; | |
2534 this.parent = parent; | |
2535 } | |
2536 | |
2537 TreeScope.prototype = { | |
2538 get renderer() { | |
2539 if (this.root instanceof scope.wrappers.ShadowRoot) { | |
2540 return scope.getRendererForHost(this.root.host); | |
2541 } | |
2542 return null; | |
2543 }, | |
2544 | |
2545 contains: function(treeScope) { | |
2546 for (; treeScope; treeScope = treeScope.parent) { | |
2547 if (treeScope === this) | |
2548 return true; | |
2549 } | |
2550 return false; | |
2551 } | |
2552 }; | |
2553 | |
2554 function setTreeScope(node, treeScope) { | |
2555 if (node.treeScope_ !== treeScope) { | |
2556 node.treeScope_ = treeScope; | |
2557 for (var sr = node.shadowRoot; sr; sr = sr.olderShadowRoot) { | |
2558 sr.treeScope_.parent = treeScope; | |
2559 } | |
2560 for (var child = node.firstChild; child; child = child.nextSibling) { | |
2561 setTreeScope(child, treeScope); | |
2562 } | |
2563 } | |
2564 } | |
2565 | |
2566 function getTreeScope(node) { | |
2567 if (node.treeScope_) | |
2568 return node.treeScope_; | |
2569 var parent = node.parentNode; | |
2570 var treeScope; | |
2571 if (parent) | |
2572 treeScope = getTreeScope(parent); | |
2573 else | |
2574 treeScope = new TreeScope(node, null); | |
2575 return node.treeScope_ = treeScope; | |
2576 } | |
2577 | |
2578 scope.TreeScope = TreeScope; | |
2579 scope.getTreeScope = getTreeScope; | |
2580 scope.setTreeScope = setTreeScope; | |
2581 | |
2582 })(window.ShadowDOMPolyfill); | |
2583 | |
2584 // Copyright 2013 The Polymer Authors. All rights reserved. | |
2585 // Use of this source code is goverened by a BSD-style | |
2586 // license that can be found in the LICENSE file. | |
2587 | |
2588 (function(scope) { | |
2589 'use strict'; | |
2590 | |
2591 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; | |
2592 var getTreeScope = scope.getTreeScope; | |
2593 var mixin = scope.mixin; | |
2594 var registerWrapper = scope.registerWrapper; | |
2595 var unwrap = scope.unwrap; | |
2596 var wrap = scope.wrap; | |
2597 var wrappers = scope.wrappers; | |
2598 | |
2599 var wrappedFuns = new WeakMap(); | |
2600 var listenersTable = new WeakMap(); | |
2601 var handledEventsTable = new WeakMap(); | |
2602 var currentlyDispatchingEvents = new WeakMap(); | |
2603 var targetTable = new WeakMap(); | |
2604 var currentTargetTable = new WeakMap(); | |
2605 var relatedTargetTable = new WeakMap(); | |
2606 var eventPhaseTable = new WeakMap(); | |
2607 var stopPropagationTable = new WeakMap(); | |
2608 var stopImmediatePropagationTable = new WeakMap(); | |
2609 var eventHandlersTable = new WeakMap(); | |
2610 var eventPathTable = new WeakMap(); | |
2611 | |
2612 function isShadowRoot(node) { | |
2613 return node instanceof wrappers.ShadowRoot; | |
2614 } | |
2615 | |
2616 function isInsertionPoint(node) { | |
2617 var localName = node.localName; | |
2618 return localName === 'content' || localName === 'shadow'; | |
2619 } | |
2620 | |
2621 function isShadowHost(node) { | |
2622 return !!node.shadowRoot; | |
2623 } | |
2624 | |
2625 function getEventParent(node) { | |
2626 var dv; | |
2627 return node.parentNode || (dv = node.defaultView) && wrap(dv) || null; | |
2628 } | |
2629 | |
2630 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-parent | |
2631 function calculateParents(node, context, ancestors) { | |
2632 if (ancestors.length) | |
2633 return ancestors.shift(); | |
2634 | |
2635 // 1. | |
2636 if (isShadowRoot(node)) | |
2637 return getInsertionParent(node) || node.host; | |
2638 | |
2639 // 2. | |
2640 var eventParents = scope.eventParentsTable.get(node); | |
2641 if (eventParents) { | |
2642 // Copy over the remaining event parents for next iteration. | |
2643 for (var i = 1; i < eventParents.length; i++) { | |
2644 ancestors[i - 1] = eventParents[i]; | |
2645 } | |
2646 return eventParents[0]; | |
2647 } | |
2648 | |
2649 // 3. | |
2650 if (context && isInsertionPoint(node)) { | |
2651 var parentNode = node.parentNode; | |
2652 if (parentNode && isShadowHost(parentNode)) { | |
2653 var trees = scope.getShadowTrees(parentNode); | |
2654 var p = getInsertionParent(context); | |
2655 for (var i = 0; i < trees.length; i++) { | |
2656 if (trees[i].contains(p)) | |
2657 return p; | |
2658 } | |
2659 } | |
2660 } | |
2661 | |
2662 return getEventParent(node); | |
2663 } | |
2664 | |
2665 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ev
ent-retargeting | |
2666 function retarget(node) { | |
2667 var stack = []; // 1. | |
2668 var ancestor = node; // 2. | |
2669 var targets = []; | |
2670 var ancestors = []; | |
2671 while (ancestor) { // 3. | |
2672 var context = null; // 3.2. | |
2673 // TODO(arv): Change order of these. If the stack is empty we always end | |
2674 // up pushing ancestor, no matter what. | |
2675 if (isInsertionPoint(ancestor)) { // 3.1. | |
2676 context = topMostNotInsertionPoint(stack); // 3.1.1. | |
2677 var top = stack[stack.length - 1] || ancestor; // 3.1.2. | |
2678 stack.push(top); | |
2679 } else if (!stack.length) { | |
2680 stack.push(ancestor); // 3.3. | |
2681 } | |
2682 var target = stack[stack.length - 1]; // 3.4. | |
2683 targets.push({target: target, currentTarget: ancestor}); // 3.5. | |
2684 if (isShadowRoot(ancestor)) // 3.6. | |
2685 stack.pop(); // 3.6.1. | |
2686 | |
2687 ancestor = calculateParents(ancestor, context, ancestors); // 3.7. | |
2688 } | |
2689 return targets; | |
2690 } | |
2691 | |
2692 function topMostNotInsertionPoint(stack) { | |
2693 for (var i = stack.length - 1; i >= 0; i--) { | |
2694 if (!isInsertionPoint(stack[i])) | |
2695 return stack[i]; | |
2696 } | |
2697 return null; | |
2698 } | |
2699 | |
2700 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-related-target | |
2701 function adjustRelatedTarget(target, related) { | |
2702 var ancestors = []; | |
2703 while (target) { // 3. | |
2704 var stack = []; // 3.1. | |
2705 var ancestor = related; // 3.2. | |
2706 var last = undefined; // 3.3. Needs to be reset every iteration. | |
2707 while (ancestor) { | |
2708 var context = null; | |
2709 if (!stack.length) { | |
2710 stack.push(ancestor); | |
2711 } else { | |
2712 if (isInsertionPoint(ancestor)) { // 3.4.3. | |
2713 context = topMostNotInsertionPoint(stack); | |
2714 // isDistributed is more general than checking whether last is | |
2715 // assigned into ancestor. | |
2716 if (isDistributed(last)) { // 3.4.3.2. | |
2717 var head = stack[stack.length - 1]; | |
2718 stack.push(head); | |
2719 } | |
2720 } | |
2721 } | |
2722 | |
2723 if (inSameTree(ancestor, target)) // 3.4.4. | |
2724 return stack[stack.length - 1]; | |
2725 | |
2726 if (isShadowRoot(ancestor)) // 3.4.5. | |
2727 stack.pop(); | |
2728 | |
2729 last = ancestor; // 3.4.6. | |
2730 ancestor = calculateParents(ancestor, context, ancestors); // 3.4.7. | |
2731 } | |
2732 if (isShadowRoot(target)) // 3.5. | |
2733 target = target.host; | |
2734 else | |
2735 target = target.parentNode; // 3.6. | |
2736 } | |
2737 } | |
2738 | |
2739 function getInsertionParent(node) { | |
2740 return scope.insertionParentTable.get(node); | |
2741 } | |
2742 | |
2743 function isDistributed(node) { | |
2744 return getInsertionParent(node); | |
2745 } | |
2746 | |
2747 function inSameTree(a, b) { | |
2748 return getTreeScope(a) === getTreeScope(b); | |
2749 } | |
2750 | |
2751 function dispatchOriginalEvent(originalEvent) { | |
2752 // Make sure this event is only dispatched once. | |
2753 if (handledEventsTable.get(originalEvent)) | |
2754 return; | |
2755 handledEventsTable.set(originalEvent, true); | |
2756 dispatchEvent(wrap(originalEvent), wrap(originalEvent.target)); | |
2757 } | |
2758 | |
2759 function isLoadLikeEvent(event) { | |
2760 switch (event.type) { | |
2761 case 'beforeunload': | |
2762 case 'load': | |
2763 case 'unload': | |
2764 return true; | |
2765 } | |
2766 return false; | |
2767 } | |
2768 | |
2769 function dispatchEvent(event, originalWrapperTarget) { | |
2770 if (currentlyDispatchingEvents.get(event)) | |
2771 throw new Error('InvalidStateError') | |
2772 currentlyDispatchingEvents.set(event, true); | |
2773 | |
2774 // Render to ensure that the event path is correct. | |
2775 scope.renderAllPending(); | |
2776 var eventPath = retarget(originalWrapperTarget); | |
2777 | |
2778 // For window "load" events the "load" event is dispatched at the window but | |
2779 // the target is set to the document. | |
2780 // | |
2781 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#
the-end | |
2782 // | |
2783 // TODO(arv): Find a less hacky way to do this. | |
2784 if (eventPath.length === 2 && | |
2785 eventPath[0].target instanceof wrappers.Document && | |
2786 isLoadLikeEvent(event)) { | |
2787 eventPath.shift(); | |
2788 } | |
2789 | |
2790 eventPathTable.set(event, eventPath); | |
2791 | |
2792 if (dispatchCapturing(event, eventPath)) { | |
2793 if (dispatchAtTarget(event, eventPath)) { | |
2794 dispatchBubbling(event, eventPath); | |
2795 } | |
2796 } | |
2797 | |
2798 eventPhaseTable.set(event, Event.NONE); | |
2799 currentTargetTable.delete(event, null); | |
2800 currentlyDispatchingEvents.delete(event); | |
2801 | |
2802 return event.defaultPrevented; | |
2803 } | |
2804 | |
2805 function dispatchCapturing(event, eventPath) { | |
2806 var phase; | |
2807 | |
2808 for (var i = eventPath.length - 1; i > 0; i--) { | |
2809 var target = eventPath[i].target; | |
2810 var currentTarget = eventPath[i].currentTarget; | |
2811 if (target === currentTarget) | |
2812 continue; | |
2813 | |
2814 phase = Event.CAPTURING_PHASE; | |
2815 if (!invoke(eventPath[i], event, phase)) | |
2816 return false; | |
2817 } | |
2818 | |
2819 return true; | |
2820 } | |
2821 | |
2822 function dispatchAtTarget(event, eventPath) { | |
2823 var phase = Event.AT_TARGET; | |
2824 return invoke(eventPath[0], event, phase); | |
2825 } | |
2826 | |
2827 function dispatchBubbling(event, eventPath) { | |
2828 var bubbles = event.bubbles; | |
2829 var phase; | |
2830 | |
2831 for (var i = 1; i < eventPath.length; i++) { | |
2832 var target = eventPath[i].target; | |
2833 var currentTarget = eventPath[i].currentTarget; | |
2834 if (target === currentTarget) | |
2835 phase = Event.AT_TARGET; | |
2836 else if (bubbles && !stopImmediatePropagationTable.get(event)) | |
2837 phase = Event.BUBBLING_PHASE; | |
2838 else | |
2839 continue; | |
2840 | |
2841 if (!invoke(eventPath[i], event, phase)) | |
2842 return; | |
2843 } | |
2844 } | |
2845 | |
2846 function invoke(tuple, event, phase) { | |
2847 var target = tuple.target; | |
2848 var currentTarget = tuple.currentTarget; | |
2849 | |
2850 var listeners = listenersTable.get(currentTarget); | |
2851 if (!listeners) | |
2852 return true; | |
2853 | |
2854 if ('relatedTarget' in event) { | |
2855 var originalEvent = unwrap(event); | |
2856 var unwrappedRelatedTarget = originalEvent.relatedTarget; | |
2857 | |
2858 // X-Tag sets relatedTarget on a CustomEvent. If they do that there is no | |
2859 // way to have relatedTarget return the adjusted target but worse is that | |
2860 // the originalEvent might not have a relatedTarget so we hit an assert | |
2861 // when we try to wrap it. | |
2862 if (unwrappedRelatedTarget) { | |
2863 // In IE we can get objects that are not EventTargets at this point. | |
2864 // Safari does not have an EventTarget interface so revert to checking | |
2865 // for addEventListener as an approximation. | |
2866 if (unwrappedRelatedTarget instanceof Object && | |
2867 unwrappedRelatedTarget.addEventListener) { | |
2868 var relatedTarget = wrap(unwrappedRelatedTarget); | |
2869 | |
2870 var adjusted = adjustRelatedTarget(currentTarget, relatedTarget); | |
2871 if (adjusted === target) | |
2872 return true; | |
2873 } else { | |
2874 adjusted = null; | |
2875 } | |
2876 relatedTargetTable.set(event, adjusted); | |
2877 } | |
2878 } | |
2879 | |
2880 eventPhaseTable.set(event, phase); | |
2881 var type = event.type; | |
2882 | |
2883 var anyRemoved = false; | |
2884 targetTable.set(event, target); | |
2885 currentTargetTable.set(event, currentTarget); | |
2886 | |
2887 for (var i = 0; i < listeners.length; i++) { | |
2888 var listener = listeners[i]; | |
2889 if (listener.removed) { | |
2890 anyRemoved = true; | |
2891 continue; | |
2892 } | |
2893 | |
2894 if (listener.type !== type || | |
2895 !listener.capture && phase === Event.CAPTURING_PHASE || | |
2896 listener.capture && phase === Event.BUBBLING_PHASE) { | |
2897 continue; | |
2898 } | |
2899 | |
2900 try { | |
2901 if (typeof listener.handler === 'function') | |
2902 listener.handler.call(currentTarget, event); | |
2903 else | |
2904 listener.handler.handleEvent(event); | |
2905 | |
2906 if (stopImmediatePropagationTable.get(event)) | |
2907 return false; | |
2908 | |
2909 } catch (ex) { | |
2910 if (window.onerror) | |
2911 window.onerror(ex.message); | |
2912 else | |
2913 console.error(ex, ex.stack); | |
2914 } | |
2915 } | |
2916 | |
2917 if (anyRemoved) { | |
2918 var copy = listeners.slice(); | |
2919 listeners.length = 0; | |
2920 for (var i = 0; i < copy.length; i++) { | |
2921 if (!copy[i].removed) | |
2922 listeners.push(copy[i]); | |
2923 } | |
2924 } | |
2925 | |
2926 return !stopPropagationTable.get(event); | |
2927 } | |
2928 | |
2929 function Listener(type, handler, capture) { | |
2930 this.type = type; | |
2931 this.handler = handler; | |
2932 this.capture = Boolean(capture); | |
2933 } | |
2934 Listener.prototype = { | |
2935 equals: function(that) { | |
2936 return this.handler === that.handler && this.type === that.type && | |
2937 this.capture === that.capture; | |
2938 }, | |
2939 get removed() { | |
2940 return this.handler === null; | |
2941 }, | |
2942 remove: function() { | |
2943 this.handler = null; | |
2944 } | |
2945 }; | |
2946 | |
2947 var OriginalEvent = window.Event; | |
2948 OriginalEvent.prototype.polymerBlackList_ = { | |
2949 returnValue: true, | |
2950 // TODO(arv): keyLocation is part of KeyboardEvent but Firefox does not | |
2951 // support constructable KeyboardEvent so we keep it here for now. | |
2952 keyLocation: true | |
2953 }; | |
2954 | |
2955 /** | |
2956 * Creates a new Event wrapper or wraps an existin native Event object. | |
2957 * @param {string|Event} type | |
2958 * @param {Object=} options | |
2959 * @constructor | |
2960 */ | |
2961 function Event(type, options) { | |
2962 if (type instanceof OriginalEvent) { | |
2963 var impl = type; | |
2964 if (!OriginalBeforeUnloadEvent && impl.type === 'beforeunload') | |
2965 return new BeforeUnloadEvent(impl); | |
2966 this.impl = impl; | |
2967 } else { | |
2968 return wrap(constructEvent(OriginalEvent, 'Event', type, options)); | |
2969 } | |
2970 } | |
2971 Event.prototype = { | |
2972 get target() { | |
2973 return targetTable.get(this); | |
2974 }, | |
2975 get currentTarget() { | |
2976 return currentTargetTable.get(this); | |
2977 }, | |
2978 get eventPhase() { | |
2979 return eventPhaseTable.get(this); | |
2980 }, | |
2981 get path() { | |
2982 var nodeList = new wrappers.NodeList(); | |
2983 var eventPath = eventPathTable.get(this); | |
2984 if (eventPath) { | |
2985 var index = 0; | |
2986 var lastIndex = eventPath.length - 1; | |
2987 var baseRoot = getTreeScope(currentTargetTable.get(this)); | |
2988 | |
2989 for (var i = 0; i <= lastIndex; i++) { | |
2990 var currentTarget = eventPath[i].currentTarget; | |
2991 var currentRoot = getTreeScope(currentTarget); | |
2992 if (currentRoot.contains(baseRoot) && | |
2993 // Make sure we do not add Window to the path. | |
2994 (i !== lastIndex || currentTarget instanceof wrappers.Node)) { | |
2995 nodeList[index++] = currentTarget; | |
2996 } | |
2997 } | |
2998 nodeList.length = index; | |
2999 } | |
3000 return nodeList; | |
3001 }, | |
3002 stopPropagation: function() { | |
3003 stopPropagationTable.set(this, true); | |
3004 }, | |
3005 stopImmediatePropagation: function() { | |
3006 stopPropagationTable.set(this, true); | |
3007 stopImmediatePropagationTable.set(this, true); | |
3008 } | |
3009 }; | |
3010 registerWrapper(OriginalEvent, Event, document.createEvent('Event')); | |
3011 | |
3012 function unwrapOptions(options) { | |
3013 if (!options || !options.relatedTarget) | |
3014 return options; | |
3015 return Object.create(options, { | |
3016 relatedTarget: {value: unwrap(options.relatedTarget)} | |
3017 }); | |
3018 } | |
3019 | |
3020 function registerGenericEvent(name, SuperEvent, prototype) { | |
3021 var OriginalEvent = window[name]; | |
3022 var GenericEvent = function(type, options) { | |
3023 if (type instanceof OriginalEvent) | |
3024 this.impl = type; | |
3025 else | |
3026 return wrap(constructEvent(OriginalEvent, name, type, options)); | |
3027 }; | |
3028 GenericEvent.prototype = Object.create(SuperEvent.prototype); | |
3029 if (prototype) | |
3030 mixin(GenericEvent.prototype, prototype); | |
3031 if (OriginalEvent) { | |
3032 // - Old versions of Safari fails on new FocusEvent (and others?). | |
3033 // - IE does not support event constructors. | |
3034 // - createEvent('FocusEvent') throws in Firefox. | |
3035 // => Try the best practice solution first and fallback to the old way | |
3036 // if needed. | |
3037 try { | |
3038 registerWrapper(OriginalEvent, GenericEvent, new OriginalEvent('temp')); | |
3039 } catch (ex) { | |
3040 registerWrapper(OriginalEvent, GenericEvent, | |
3041 document.createEvent(name)); | |
3042 } | |
3043 } | |
3044 return GenericEvent; | |
3045 } | |
3046 | |
3047 var UIEvent = registerGenericEvent('UIEvent', Event); | |
3048 var CustomEvent = registerGenericEvent('CustomEvent', Event); | |
3049 | |
3050 var relatedTargetProto = { | |
3051 get relatedTarget() { | |
3052 var relatedTarget = relatedTargetTable.get(this); | |
3053 // relatedTarget can be null. | |
3054 if (relatedTarget !== undefined) | |
3055 return relatedTarget; | |
3056 return wrap(unwrap(this).relatedTarget); | |
3057 } | |
3058 }; | |
3059 | |
3060 function getInitFunction(name, relatedTargetIndex) { | |
3061 return function() { | |
3062 arguments[relatedTargetIndex] = unwrap(arguments[relatedTargetIndex]); | |
3063 var impl = unwrap(this); | |
3064 impl[name].apply(impl, arguments); | |
3065 }; | |
3066 } | |
3067 | |
3068 var mouseEventProto = mixin({ | |
3069 initMouseEvent: getInitFunction('initMouseEvent', 14) | |
3070 }, relatedTargetProto); | |
3071 | |
3072 var focusEventProto = mixin({ | |
3073 initFocusEvent: getInitFunction('initFocusEvent', 5) | |
3074 }, relatedTargetProto); | |
3075 | |
3076 var MouseEvent = registerGenericEvent('MouseEvent', UIEvent, mouseEventProto); | |
3077 var FocusEvent = registerGenericEvent('FocusEvent', UIEvent, focusEventProto); | |
3078 | |
3079 // In case the browser does not support event constructors we polyfill that | |
3080 // by calling `createEvent('Foo')` and `initFooEvent` where the arguments to | |
3081 // `initFooEvent` are derived from the registered default event init dict. | |
3082 var defaultInitDicts = Object.create(null); | |
3083 | |
3084 var supportsEventConstructors = (function() { | |
3085 try { | |
3086 new window.FocusEvent('focus'); | |
3087 } catch (ex) { | |
3088 return false; | |
3089 } | |
3090 return true; | |
3091 })(); | |
3092 | |
3093 /** | |
3094 * Constructs a new native event. | |
3095 */ | |
3096 function constructEvent(OriginalEvent, name, type, options) { | |
3097 if (supportsEventConstructors) | |
3098 return new OriginalEvent(type, unwrapOptions(options)); | |
3099 | |
3100 // Create the arguments from the default dictionary. | |
3101 var event = unwrap(document.createEvent(name)); | |
3102 var defaultDict = defaultInitDicts[name]; | |
3103 var args = [type]; | |
3104 Object.keys(defaultDict).forEach(function(key) { | |
3105 var v = options != null && key in options ? | |
3106 options[key] : defaultDict[key]; | |
3107 if (key === 'relatedTarget') | |
3108 v = unwrap(v); | |
3109 args.push(v); | |
3110 }); | |
3111 event['init' + name].apply(event, args); | |
3112 return event; | |
3113 } | |
3114 | |
3115 if (!supportsEventConstructors) { | |
3116 var configureEventConstructor = function(name, initDict, superName) { | |
3117 if (superName) { | |
3118 var superDict = defaultInitDicts[superName]; | |
3119 initDict = mixin(mixin({}, superDict), initDict); | |
3120 } | |
3121 | |
3122 defaultInitDicts[name] = initDict; | |
3123 }; | |
3124 | |
3125 // The order of the default event init dictionary keys is important, the | |
3126 // arguments to initFooEvent is derived from that. | |
3127 configureEventConstructor('Event', {bubbles: false, cancelable: false}); | |
3128 configureEventConstructor('CustomEvent', {detail: null}, 'Event'); | |
3129 configureEventConstructor('UIEvent', {view: null, detail: 0}, 'Event'); | |
3130 configureEventConstructor('MouseEvent', { | |
3131 screenX: 0, | |
3132 screenY: 0, | |
3133 clientX: 0, | |
3134 clientY: 0, | |
3135 ctrlKey: false, | |
3136 altKey: false, | |
3137 shiftKey: false, | |
3138 metaKey: false, | |
3139 button: 0, | |
3140 relatedTarget: null | |
3141 }, 'UIEvent'); | |
3142 configureEventConstructor('FocusEvent', {relatedTarget: null}, 'UIEvent'); | |
3143 } | |
3144 | |
3145 // Safari 7 does not yet have BeforeUnloadEvent. | |
3146 // https://bugs.webkit.org/show_bug.cgi?id=120849 | |
3147 var OriginalBeforeUnloadEvent = window.BeforeUnloadEvent; | |
3148 | |
3149 function BeforeUnloadEvent(impl) { | |
3150 Event.call(this, impl); | |
3151 } | |
3152 BeforeUnloadEvent.prototype = Object.create(Event.prototype); | |
3153 mixin(BeforeUnloadEvent.prototype, { | |
3154 get returnValue() { | |
3155 return this.impl.returnValue; | |
3156 }, | |
3157 set returnValue(v) { | |
3158 this.impl.returnValue = v; | |
3159 } | |
3160 }); | |
3161 | |
3162 if (OriginalBeforeUnloadEvent) | |
3163 registerWrapper(OriginalBeforeUnloadEvent, BeforeUnloadEvent); | |
3164 | |
3165 function isValidListener(fun) { | |
3166 if (typeof fun === 'function') | |
3167 return true; | |
3168 return fun && fun.handleEvent; | |
3169 } | |
3170 | |
3171 function isMutationEvent(type) { | |
3172 switch (type) { | |
3173 case 'DOMAttrModified': | |
3174 case 'DOMAttributeNameChanged': | |
3175 case 'DOMCharacterDataModified': | |
3176 case 'DOMElementNameChanged': | |
3177 case 'DOMNodeInserted': | |
3178 case 'DOMNodeInsertedIntoDocument': | |
3179 case 'DOMNodeRemoved': | |
3180 case 'DOMNodeRemovedFromDocument': | |
3181 case 'DOMSubtreeModified': | |
3182 return true; | |
3183 } | |
3184 return false; | |
3185 } | |
3186 | |
3187 var OriginalEventTarget = window.EventTarget; | |
3188 | |
3189 /** | |
3190 * This represents a wrapper for an EventTarget. | |
3191 * @param {!EventTarget} impl The original event target. | |
3192 * @constructor | |
3193 */ | |
3194 function EventTarget(impl) { | |
3195 this.impl = impl; | |
3196 } | |
3197 | |
3198 // Node and Window have different internal type checks in WebKit so we cannot | |
3199 // use the same method as the original function. | |
3200 var methodNames = [ | |
3201 'addEventListener', | |
3202 'removeEventListener', | |
3203 'dispatchEvent' | |
3204 ]; | |
3205 | |
3206 [Node, Window].forEach(function(constructor) { | |
3207 var p = constructor.prototype; | |
3208 methodNames.forEach(function(name) { | |
3209 Object.defineProperty(p, name + '_', {value: p[name]}); | |
3210 }); | |
3211 }); | |
3212 | |
3213 function getTargetToListenAt(wrapper) { | |
3214 if (wrapper instanceof wrappers.ShadowRoot) | |
3215 wrapper = wrapper.host; | |
3216 return unwrap(wrapper); | |
3217 } | |
3218 | |
3219 EventTarget.prototype = { | |
3220 addEventListener: function(type, fun, capture) { | |
3221 if (!isValidListener(fun) || isMutationEvent(type)) | |
3222 return; | |
3223 | |
3224 var listener = new Listener(type, fun, capture); | |
3225 var listeners = listenersTable.get(this); | |
3226 if (!listeners) { | |
3227 listeners = []; | |
3228 listenersTable.set(this, listeners); | |
3229 } else { | |
3230 // Might have a duplicate. | |
3231 for (var i = 0; i < listeners.length; i++) { | |
3232 if (listener.equals(listeners[i])) | |
3233 return; | |
3234 } | |
3235 } | |
3236 | |
3237 listeners.push(listener); | |
3238 | |
3239 var target = getTargetToListenAt(this); | |
3240 target.addEventListener_(type, dispatchOriginalEvent, true); | |
3241 }, | |
3242 removeEventListener: function(type, fun, capture) { | |
3243 capture = Boolean(capture); | |
3244 var listeners = listenersTable.get(this); | |
3245 if (!listeners) | |
3246 return; | |
3247 var count = 0, found = false; | |
3248 for (var i = 0; i < listeners.length; i++) { | |
3249 if (listeners[i].type === type && listeners[i].capture === capture) { | |
3250 count++; | |
3251 if (listeners[i].handler === fun) { | |
3252 found = true; | |
3253 listeners[i].remove(); | |
3254 } | |
3255 } | |
3256 } | |
3257 | |
3258 if (found && count === 1) { | |
3259 var target = getTargetToListenAt(this); | |
3260 target.removeEventListener_(type, dispatchOriginalEvent, true); | |
3261 } | |
3262 }, | |
3263 dispatchEvent: function(event) { | |
3264 // We want to use the native dispatchEvent because it triggers the default | |
3265 // actions (like checking a checkbox). However, if there are no listeners | |
3266 // in the composed tree then there are no events that will trigger and | |
3267 // listeners in the non composed tree that are part of the event path are | |
3268 // not notified. | |
3269 // | |
3270 // If we find out that there are no listeners in the composed tree we add | |
3271 // a temporary listener to the target which makes us get called back even | |
3272 // in that case. | |
3273 | |
3274 var nativeEvent = unwrap(event); | |
3275 var eventType = nativeEvent.type; | |
3276 | |
3277 // Allow dispatching the same event again. This is safe because if user | |
3278 // code calls this during an existing dispatch of the same event the | |
3279 // native dispatchEvent throws (that is required by the spec). | |
3280 handledEventsTable.set(nativeEvent, false); | |
3281 | |
3282 // Force rendering since we prefer native dispatch and that works on the | |
3283 // composed tree. | |
3284 scope.renderAllPending(); | |
3285 | |
3286 var tempListener; | |
3287 if (!hasListenerInAncestors(this, eventType)) { | |
3288 tempListener = function() {}; | |
3289 this.addEventListener(eventType, tempListener, true); | |
3290 } | |
3291 | |
3292 try { | |
3293 return unwrap(this).dispatchEvent_(nativeEvent); | |
3294 } finally { | |
3295 if (tempListener) | |
3296 this.removeEventListener(eventType, tempListener, true); | |
3297 } | |
3298 } | |
3299 }; | |
3300 | |
3301 function hasListener(node, type) { | |
3302 var listeners = listenersTable.get(node); | |
3303 if (listeners) { | |
3304 for (var i = 0; i < listeners.length; i++) { | |
3305 if (!listeners[i].removed && listeners[i].type === type) | |
3306 return true; | |
3307 } | |
3308 } | |
3309 return false; | |
3310 } | |
3311 | |
3312 function hasListenerInAncestors(target, type) { | |
3313 for (var node = unwrap(target); node; node = node.parentNode) { | |
3314 if (hasListener(wrap(node), type)) | |
3315 return true; | |
3316 } | |
3317 return false; | |
3318 } | |
3319 | |
3320 if (OriginalEventTarget) | |
3321 registerWrapper(OriginalEventTarget, EventTarget); | |
3322 | |
3323 function wrapEventTargetMethods(constructors) { | |
3324 forwardMethodsToWrapper(constructors, methodNames); | |
3325 } | |
3326 | |
3327 var originalElementFromPoint = document.elementFromPoint; | |
3328 | |
3329 function elementFromPoint(self, document, x, y) { | |
3330 scope.renderAllPending(); | |
3331 | |
3332 var element = wrap(originalElementFromPoint.call(document.impl, x, y)); | |
3333 var targets = retarget(element, this) | |
3334 for (var i = 0; i < targets.length; i++) { | |
3335 var target = targets[i]; | |
3336 if (target.currentTarget === self) | |
3337 return target.target; | |
3338 } | |
3339 return null; | |
3340 } | |
3341 | |
3342 /** | |
3343 * Returns a function that is to be used as a getter for `onfoo` properties. | |
3344 * @param {string} name | |
3345 * @return {Function} | |
3346 */ | |
3347 function getEventHandlerGetter(name) { | |
3348 return function() { | |
3349 var inlineEventHandlers = eventHandlersTable.get(this); | |
3350 return inlineEventHandlers && inlineEventHandlers[name] && | |
3351 inlineEventHandlers[name].value || null; | |
3352 }; | |
3353 } | |
3354 | |
3355 /** | |
3356 * Returns a function that is to be used as a setter for `onfoo` properties. | |
3357 * @param {string} name | |
3358 * @return {Function} | |
3359 */ | |
3360 function getEventHandlerSetter(name) { | |
3361 var eventType = name.slice(2); | |
3362 return function(value) { | |
3363 var inlineEventHandlers = eventHandlersTable.get(this); | |
3364 if (!inlineEventHandlers) { | |
3365 inlineEventHandlers = Object.create(null); | |
3366 eventHandlersTable.set(this, inlineEventHandlers); | |
3367 } | |
3368 | |
3369 var old = inlineEventHandlers[name]; | |
3370 if (old) | |
3371 this.removeEventListener(eventType, old.wrapped, false); | |
3372 | |
3373 if (typeof value === 'function') { | |
3374 var wrapped = function(e) { | |
3375 var rv = value.call(this, e); | |
3376 if (rv === false) | |
3377 e.preventDefault(); | |
3378 else if (name === 'onbeforeunload' && typeof rv === 'string') | |
3379 e.returnValue = rv; | |
3380 // mouseover uses true for preventDefault but preventDefault for | |
3381 // mouseover is ignored by browsers these day. | |
3382 }; | |
3383 | |
3384 this.addEventListener(eventType, wrapped, false); | |
3385 inlineEventHandlers[name] = { | |
3386 value: value, | |
3387 wrapped: wrapped | |
3388 }; | |
3389 } | |
3390 }; | |
3391 } | |
3392 | |
3393 scope.adjustRelatedTarget = adjustRelatedTarget; | |
3394 scope.elementFromPoint = elementFromPoint; | |
3395 scope.getEventHandlerGetter = getEventHandlerGetter; | |
3396 scope.getEventHandlerSetter = getEventHandlerSetter; | |
3397 scope.wrapEventTargetMethods = wrapEventTargetMethods; | |
3398 scope.wrappers.BeforeUnloadEvent = BeforeUnloadEvent; | |
3399 scope.wrappers.CustomEvent = CustomEvent; | |
3400 scope.wrappers.Event = Event; | |
3401 scope.wrappers.EventTarget = EventTarget; | |
3402 scope.wrappers.FocusEvent = FocusEvent; | |
3403 scope.wrappers.MouseEvent = MouseEvent; | |
3404 scope.wrappers.UIEvent = UIEvent; | |
3405 | |
3406 })(window.ShadowDOMPolyfill); | |
3407 | |
3408 // Copyright 2012 The Polymer Authors. All rights reserved. | |
3409 // Use of this source code is goverened by a BSD-style | |
3410 // license that can be found in the LICENSE file. | |
3411 | |
3412 (function(scope) { | |
3413 'use strict'; | |
3414 | |
3415 var wrap = scope.wrap; | |
3416 | |
3417 function nonEnum(obj, prop) { | |
3418 Object.defineProperty(obj, prop, {enumerable: false}); | |
3419 } | |
3420 | |
3421 function NodeList() { | |
3422 this.length = 0; | |
3423 nonEnum(this, 'length'); | |
3424 } | |
3425 NodeList.prototype = { | |
3426 item: function(index) { | |
3427 return this[index]; | |
3428 } | |
3429 }; | |
3430 nonEnum(NodeList.prototype, 'item'); | |
3431 | |
3432 function wrapNodeList(list) { | |
3433 if (list == null) | |
3434 return list; | |
3435 var wrapperList = new NodeList(); | |
3436 for (var i = 0, length = list.length; i < length; i++) { | |
3437 wrapperList[i] = wrap(list[i]); | |
3438 } | |
3439 wrapperList.length = length; | |
3440 return wrapperList; | |
3441 } | |
3442 | |
3443 function addWrapNodeListMethod(wrapperConstructor, name) { | |
3444 wrapperConstructor.prototype[name] = function() { | |
3445 return wrapNodeList(this.impl[name].apply(this.impl, arguments)); | |
3446 }; | |
3447 } | |
3448 | |
3449 scope.wrappers.NodeList = NodeList; | |
3450 scope.addWrapNodeListMethod = addWrapNodeListMethod; | |
3451 scope.wrapNodeList = wrapNodeList; | |
3452 | |
3453 })(window.ShadowDOMPolyfill); | |
3454 | |
3455 /* | |
3456 * Copyright 2014 The Polymer Authors. All rights reserved. | |
3457 * Use of this source code is goverened by a BSD-style | |
3458 * license that can be found in the LICENSE file. | |
3459 */ | |
3460 | |
3461 (function(scope) { | |
3462 'use strict'; | |
3463 | |
3464 // TODO(arv): Implement. | |
3465 | |
3466 scope.wrapHTMLCollection = scope.wrapNodeList; | |
3467 scope.wrappers.HTMLCollection = scope.wrappers.NodeList; | |
3468 | |
3469 })(window.ShadowDOMPolyfill); | |
3470 | |
3471 /** | |
3472 * Copyright 2012 The Polymer Authors. All rights reserved. | |
3473 * Use of this source code is goverened by a BSD-style | |
3474 * license that can be found in the LICENSE file. | |
3475 */ | |
3476 | |
3477 (function(scope) { | |
3478 'use strict'; | |
3479 | |
3480 var EventTarget = scope.wrappers.EventTarget; | |
3481 var NodeList = scope.wrappers.NodeList; | |
3482 var TreeScope = scope.TreeScope; | |
3483 var assert = scope.assert; | |
3484 var defineWrapGetter = scope.defineWrapGetter; | |
3485 var enqueueMutation = scope.enqueueMutation; | |
3486 var getTreeScope = scope.getTreeScope; | |
3487 var isWrapper = scope.isWrapper; | |
3488 var mixin = scope.mixin; | |
3489 var registerTransientObservers = scope.registerTransientObservers; | |
3490 var registerWrapper = scope.registerWrapper; | |
3491 var setTreeScope = scope.setTreeScope; | |
3492 var unwrap = scope.unwrap; | |
3493 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
3494 var wrap = scope.wrap; | |
3495 var wrapIfNeeded = scope.wrapIfNeeded; | |
3496 var wrappers = scope.wrappers; | |
3497 | |
3498 function assertIsNodeWrapper(node) { | |
3499 assert(node instanceof Node); | |
3500 } | |
3501 | |
3502 function createOneElementNodeList(node) { | |
3503 var nodes = new NodeList(); | |
3504 nodes[0] = node; | |
3505 nodes.length = 1; | |
3506 return nodes; | |
3507 } | |
3508 | |
3509 var surpressMutations = false; | |
3510 | |
3511 /** | |
3512 * Called before node is inserted into a node to enqueue its removal from its | |
3513 * old parent. | |
3514 * @param {!Node} node The node that is about to be removed. | |
3515 * @param {!Node} parent The parent node that the node is being removed from. | |
3516 * @param {!NodeList} nodes The collected nodes. | |
3517 */ | |
3518 function enqueueRemovalForInsertedNodes(node, parent, nodes) { | |
3519 enqueueMutation(parent, 'childList', { | |
3520 removedNodes: nodes, | |
3521 previousSibling: node.previousSibling, | |
3522 nextSibling: node.nextSibling | |
3523 }); | |
3524 } | |
3525 | |
3526 function enqueueRemovalForInsertedDocumentFragment(df, nodes) { | |
3527 enqueueMutation(df, 'childList', { | |
3528 removedNodes: nodes | |
3529 }); | |
3530 } | |
3531 | |
3532 /** | |
3533 * Collects nodes from a DocumentFragment or a Node for removal followed | |
3534 * by an insertion. | |
3535 * | |
3536 * This updates the internal pointers for node, previousNode and nextNode. | |
3537 */ | |
3538 function collectNodes(node, parentNode, previousNode, nextNode) { | |
3539 if (node instanceof DocumentFragment) { | |
3540 var nodes = collectNodesForDocumentFragment(node); | |
3541 | |
3542 // The extra loop is to work around bugs with DocumentFragments in IE. | |
3543 surpressMutations = true; | |
3544 for (var i = nodes.length - 1; i >= 0; i--) { | |
3545 node.removeChild(nodes[i]); | |
3546 nodes[i].parentNode_ = parentNode; | |
3547 } | |
3548 surpressMutations = false; | |
3549 | |
3550 for (var i = 0; i < nodes.length; i++) { | |
3551 nodes[i].previousSibling_ = nodes[i - 1] || previousNode; | |
3552 nodes[i].nextSibling_ = nodes[i + 1] || nextNode; | |
3553 } | |
3554 | |
3555 if (previousNode) | |
3556 previousNode.nextSibling_ = nodes[0]; | |
3557 if (nextNode) | |
3558 nextNode.previousSibling_ = nodes[nodes.length - 1]; | |
3559 | |
3560 return nodes; | |
3561 } | |
3562 | |
3563 var nodes = createOneElementNodeList(node); | |
3564 var oldParent = node.parentNode; | |
3565 if (oldParent) { | |
3566 // This will enqueue the mutation record for the removal as needed. | |
3567 oldParent.removeChild(node); | |
3568 } | |
3569 | |
3570 node.parentNode_ = parentNode; | |
3571 node.previousSibling_ = previousNode; | |
3572 node.nextSibling_ = nextNode; | |
3573 if (previousNode) | |
3574 previousNode.nextSibling_ = node; | |
3575 if (nextNode) | |
3576 nextNode.previousSibling_ = node; | |
3577 | |
3578 return nodes; | |
3579 } | |
3580 | |
3581 function collectNodesNative(node) { | |
3582 if (node instanceof DocumentFragment) | |
3583 return collectNodesForDocumentFragment(node); | |
3584 | |
3585 var nodes = createOneElementNodeList(node); | |
3586 var oldParent = node.parentNode; | |
3587 if (oldParent) | |
3588 enqueueRemovalForInsertedNodes(node, oldParent, nodes); | |
3589 return nodes; | |
3590 } | |
3591 | |
3592 function collectNodesForDocumentFragment(node) { | |
3593 var nodes = new NodeList(); | |
3594 var i = 0; | |
3595 for (var child = node.firstChild; child; child = child.nextSibling) { | |
3596 nodes[i++] = child; | |
3597 } | |
3598 nodes.length = i; | |
3599 enqueueRemovalForInsertedDocumentFragment(node, nodes); | |
3600 return nodes; | |
3601 } | |
3602 | |
3603 function snapshotNodeList(nodeList) { | |
3604 // NodeLists are not live at the moment so just return the same object. | |
3605 return nodeList; | |
3606 } | |
3607 | |
3608 // http://dom.spec.whatwg.org/#node-is-inserted | |
3609 function nodeWasAdded(node, treeScope) { | |
3610 setTreeScope(node, treeScope); | |
3611 node.nodeIsInserted_(); | |
3612 } | |
3613 | |
3614 function nodesWereAdded(nodes, parent) { | |
3615 var treeScope = getTreeScope(parent); | |
3616 for (var i = 0; i < nodes.length; i++) { | |
3617 nodeWasAdded(nodes[i], treeScope); | |
3618 } | |
3619 } | |
3620 | |
3621 // http://dom.spec.whatwg.org/#node-is-removed | |
3622 function nodeWasRemoved(node) { | |
3623 setTreeScope(node, new TreeScope(node, null)); | |
3624 } | |
3625 | |
3626 function nodesWereRemoved(nodes) { | |
3627 for (var i = 0; i < nodes.length; i++) { | |
3628 nodeWasRemoved(nodes[i]); | |
3629 } | |
3630 } | |
3631 | |
3632 function ensureSameOwnerDocument(parent, child) { | |
3633 var ownerDoc = parent.nodeType === Node.DOCUMENT_NODE ? | |
3634 parent : parent.ownerDocument; | |
3635 if (ownerDoc !== child.ownerDocument) | |
3636 ownerDoc.adoptNode(child); | |
3637 } | |
3638 | |
3639 function adoptNodesIfNeeded(owner, nodes) { | |
3640 if (!nodes.length) | |
3641 return; | |
3642 | |
3643 var ownerDoc = owner.ownerDocument; | |
3644 | |
3645 // All nodes have the same ownerDocument when we get here. | |
3646 if (ownerDoc === nodes[0].ownerDocument) | |
3647 return; | |
3648 | |
3649 for (var i = 0; i < nodes.length; i++) { | |
3650 scope.adoptNodeNoRemove(nodes[i], ownerDoc); | |
3651 } | |
3652 } | |
3653 | |
3654 function unwrapNodesForInsertion(owner, nodes) { | |
3655 adoptNodesIfNeeded(owner, nodes); | |
3656 var length = nodes.length; | |
3657 | |
3658 if (length === 1) | |
3659 return unwrap(nodes[0]); | |
3660 | |
3661 var df = unwrap(owner.ownerDocument.createDocumentFragment()); | |
3662 for (var i = 0; i < length; i++) { | |
3663 df.appendChild(unwrap(nodes[i])); | |
3664 } | |
3665 return df; | |
3666 } | |
3667 | |
3668 function clearChildNodes(wrapper) { | |
3669 if (wrapper.firstChild_ !== undefined) { | |
3670 var child = wrapper.firstChild_; | |
3671 while (child) { | |
3672 var tmp = child; | |
3673 child = child.nextSibling_; | |
3674 tmp.parentNode_ = tmp.previousSibling_ = tmp.nextSibling_ = undefined; | |
3675 } | |
3676 } | |
3677 wrapper.firstChild_ = wrapper.lastChild_ = undefined; | |
3678 } | |
3679 | |
3680 function removeAllChildNodes(wrapper) { | |
3681 if (wrapper.invalidateShadowRenderer()) { | |
3682 var childWrapper = wrapper.firstChild; | |
3683 while (childWrapper) { | |
3684 assert(childWrapper.parentNode === wrapper); | |
3685 var nextSibling = childWrapper.nextSibling; | |
3686 var childNode = unwrap(childWrapper); | |
3687 var parentNode = childNode.parentNode; | |
3688 if (parentNode) | |
3689 originalRemoveChild.call(parentNode, childNode); | |
3690 childWrapper.previousSibling_ = childWrapper.nextSibling_ = | |
3691 childWrapper.parentNode_ = null; | |
3692 childWrapper = nextSibling; | |
3693 } | |
3694 wrapper.firstChild_ = wrapper.lastChild_ = null; | |
3695 } else { | |
3696 var node = unwrap(wrapper); | |
3697 var child = node.firstChild; | |
3698 var nextSibling; | |
3699 while (child) { | |
3700 nextSibling = child.nextSibling; | |
3701 originalRemoveChild.call(node, child); | |
3702 child = nextSibling; | |
3703 } | |
3704 } | |
3705 } | |
3706 | |
3707 function invalidateParent(node) { | |
3708 var p = node.parentNode; | |
3709 return p && p.invalidateShadowRenderer(); | |
3710 } | |
3711 | |
3712 function cleanupNodes(nodes) { | |
3713 for (var i = 0, n; i < nodes.length; i++) { | |
3714 n = nodes[i]; | |
3715 n.parentNode.removeChild(n); | |
3716 } | |
3717 } | |
3718 | |
3719 var originalImportNode = document.importNode; | |
3720 var originalCloneNode = window.Node.prototype.cloneNode; | |
3721 | |
3722 function cloneNode(node, deep, opt_doc) { | |
3723 var clone; | |
3724 if (opt_doc) | |
3725 clone = wrap(originalImportNode.call(opt_doc, node.impl, false)); | |
3726 else | |
3727 clone = wrap(originalCloneNode.call(node.impl, false)); | |
3728 | |
3729 if (deep) { | |
3730 for (var child = node.firstChild; child; child = child.nextSibling) { | |
3731 clone.appendChild(cloneNode(child, true, opt_doc)); | |
3732 } | |
3733 | |
3734 if (node instanceof wrappers.HTMLTemplateElement) { | |
3735 var cloneContent = clone.content; | |
3736 for (var child = node.content.firstChild; | |
3737 child; | |
3738 child = child.nextSibling) { | |
3739 cloneContent.appendChild(cloneNode(child, true, opt_doc)); | |
3740 } | |
3741 } | |
3742 } | |
3743 // TODO(arv): Some HTML elements also clone other data like value. | |
3744 return clone; | |
3745 } | |
3746 | |
3747 function contains(self, child) { | |
3748 if (!child || getTreeScope(self) !== getTreeScope(child)) | |
3749 return false; | |
3750 | |
3751 for (var node = child; node; node = node.parentNode) { | |
3752 if (node === self) | |
3753 return true; | |
3754 } | |
3755 return false; | |
3756 } | |
3757 | |
3758 var OriginalNode = window.Node; | |
3759 | |
3760 /** | |
3761 * This represents a wrapper of a native DOM node. | |
3762 * @param {!Node} original The original DOM node, aka, the visual DOM node. | |
3763 * @constructor | |
3764 * @extends {EventTarget} | |
3765 */ | |
3766 function Node(original) { | |
3767 assert(original instanceof OriginalNode); | |
3768 | |
3769 EventTarget.call(this, original); | |
3770 | |
3771 // These properties are used to override the visual references with the | |
3772 // logical ones. If the value is undefined it means that the logical is the | |
3773 // same as the visual. | |
3774 | |
3775 /** | |
3776 * @type {Node|undefined} | |
3777 * @private | |
3778 */ | |
3779 this.parentNode_ = undefined; | |
3780 | |
3781 /** | |
3782 * @type {Node|undefined} | |
3783 * @private | |
3784 */ | |
3785 this.firstChild_ = undefined; | |
3786 | |
3787 /** | |
3788 * @type {Node|undefined} | |
3789 * @private | |
3790 */ | |
3791 this.lastChild_ = undefined; | |
3792 | |
3793 /** | |
3794 * @type {Node|undefined} | |
3795 * @private | |
3796 */ | |
3797 this.nextSibling_ = undefined; | |
3798 | |
3799 /** | |
3800 * @type {Node|undefined} | |
3801 * @private | |
3802 */ | |
3803 this.previousSibling_ = undefined; | |
3804 | |
3805 this.treeScope_ = undefined; | |
3806 } | |
3807 | |
3808 var OriginalDocumentFragment = window.DocumentFragment; | |
3809 var originalAppendChild = OriginalNode.prototype.appendChild; | |
3810 var originalCompareDocumentPosition = | |
3811 OriginalNode.prototype.compareDocumentPosition; | |
3812 var originalInsertBefore = OriginalNode.prototype.insertBefore; | |
3813 var originalRemoveChild = OriginalNode.prototype.removeChild; | |
3814 var originalReplaceChild = OriginalNode.prototype.replaceChild; | |
3815 | |
3816 var isIe = /Trident/.test(navigator.userAgent); | |
3817 | |
3818 var removeChildOriginalHelper = isIe ? | |
3819 function(parent, child) { | |
3820 try { | |
3821 originalRemoveChild.call(parent, child); | |
3822 } catch (ex) { | |
3823 if (!(parent instanceof OriginalDocumentFragment)) | |
3824 throw ex; | |
3825 } | |
3826 } : | |
3827 function(parent, child) { | |
3828 originalRemoveChild.call(parent, child); | |
3829 }; | |
3830 | |
3831 Node.prototype = Object.create(EventTarget.prototype); | |
3832 mixin(Node.prototype, { | |
3833 appendChild: function(childWrapper) { | |
3834 return this.insertBefore(childWrapper, null); | |
3835 }, | |
3836 | |
3837 insertBefore: function(childWrapper, refWrapper) { | |
3838 assertIsNodeWrapper(childWrapper); | |
3839 | |
3840 var refNode; | |
3841 if (refWrapper) { | |
3842 if (isWrapper(refWrapper)) { | |
3843 refNode = unwrap(refWrapper); | |
3844 } else { | |
3845 refNode = refWrapper; | |
3846 refWrapper = wrap(refNode); | |
3847 } | |
3848 } else { | |
3849 refWrapper = null; | |
3850 refNode = null; | |
3851 } | |
3852 | |
3853 refWrapper && assert(refWrapper.parentNode === this); | |
3854 | |
3855 var nodes; | |
3856 var previousNode = | |
3857 refWrapper ? refWrapper.previousSibling : this.lastChild; | |
3858 | |
3859 var useNative = !this.invalidateShadowRenderer() && | |
3860 !invalidateParent(childWrapper); | |
3861 | |
3862 if (useNative) | |
3863 nodes = collectNodesNative(childWrapper); | |
3864 else | |
3865 nodes = collectNodes(childWrapper, this, previousNode, refWrapper); | |
3866 | |
3867 if (useNative) { | |
3868 ensureSameOwnerDocument(this, childWrapper); | |
3869 clearChildNodes(this); | |
3870 originalInsertBefore.call(this.impl, unwrap(childWrapper), refNode); | |
3871 } else { | |
3872 if (!previousNode) | |
3873 this.firstChild_ = nodes[0]; | |
3874 if (!refWrapper) | |
3875 this.lastChild_ = nodes[nodes.length - 1]; | |
3876 | |
3877 var parentNode = refNode ? refNode.parentNode : this.impl; | |
3878 | |
3879 // insertBefore refWrapper no matter what the parent is? | |
3880 if (parentNode) { | |
3881 originalInsertBefore.call(parentNode, | |
3882 unwrapNodesForInsertion(this, nodes), refNode); | |
3883 } else { | |
3884 adoptNodesIfNeeded(this, nodes); | |
3885 } | |
3886 } | |
3887 | |
3888 enqueueMutation(this, 'childList', { | |
3889 addedNodes: nodes, | |
3890 nextSibling: refWrapper, | |
3891 previousSibling: previousNode | |
3892 }); | |
3893 | |
3894 nodesWereAdded(nodes, this); | |
3895 | |
3896 return childWrapper; | |
3897 }, | |
3898 | |
3899 removeChild: function(childWrapper) { | |
3900 assertIsNodeWrapper(childWrapper); | |
3901 if (childWrapper.parentNode !== this) { | |
3902 // IE has invalid DOM trees at times. | |
3903 var found = false; | |
3904 var childNodes = this.childNodes; | |
3905 for (var ieChild = this.firstChild; ieChild; | |
3906 ieChild = ieChild.nextSibling) { | |
3907 if (ieChild === childWrapper) { | |
3908 found = true; | |
3909 break; | |
3910 } | |
3911 } | |
3912 if (!found) { | |
3913 // TODO(arv): DOMException | |
3914 throw new Error('NotFoundError'); | |
3915 } | |
3916 } | |
3917 | |
3918 var childNode = unwrap(childWrapper); | |
3919 var childWrapperNextSibling = childWrapper.nextSibling; | |
3920 var childWrapperPreviousSibling = childWrapper.previousSibling; | |
3921 | |
3922 if (this.invalidateShadowRenderer()) { | |
3923 // We need to remove the real node from the DOM before updating the | |
3924 // pointers. This is so that that mutation event is dispatched before | |
3925 // the pointers have changed. | |
3926 var thisFirstChild = this.firstChild; | |
3927 var thisLastChild = this.lastChild; | |
3928 | |
3929 var parentNode = childNode.parentNode; | |
3930 if (parentNode) | |
3931 removeChildOriginalHelper(parentNode, childNode); | |
3932 | |
3933 if (thisFirstChild === childWrapper) | |
3934 this.firstChild_ = childWrapperNextSibling; | |
3935 if (thisLastChild === childWrapper) | |
3936 this.lastChild_ = childWrapperPreviousSibling; | |
3937 if (childWrapperPreviousSibling) | |
3938 childWrapperPreviousSibling.nextSibling_ = childWrapperNextSibling; | |
3939 if (childWrapperNextSibling) { | |
3940 childWrapperNextSibling.previousSibling_ = | |
3941 childWrapperPreviousSibling; | |
3942 } | |
3943 | |
3944 childWrapper.previousSibling_ = childWrapper.nextSibling_ = | |
3945 childWrapper.parentNode_ = undefined; | |
3946 } else { | |
3947 clearChildNodes(this); | |
3948 removeChildOriginalHelper(this.impl, childNode); | |
3949 } | |
3950 | |
3951 if (!surpressMutations) { | |
3952 enqueueMutation(this, 'childList', { | |
3953 removedNodes: createOneElementNodeList(childWrapper), | |
3954 nextSibling: childWrapperNextSibling, | |
3955 previousSibling: childWrapperPreviousSibling | |
3956 }); | |
3957 } | |
3958 | |
3959 registerTransientObservers(this, childWrapper); | |
3960 | |
3961 return childWrapper; | |
3962 }, | |
3963 | |
3964 replaceChild: function(newChildWrapper, oldChildWrapper) { | |
3965 assertIsNodeWrapper(newChildWrapper); | |
3966 | |
3967 var oldChildNode; | |
3968 if (isWrapper(oldChildWrapper)) { | |
3969 oldChildNode = unwrap(oldChildWrapper); | |
3970 } else { | |
3971 oldChildNode = oldChildWrapper; | |
3972 oldChildWrapper = wrap(oldChildNode); | |
3973 } | |
3974 | |
3975 if (oldChildWrapper.parentNode !== this) { | |
3976 // TODO(arv): DOMException | |
3977 throw new Error('NotFoundError'); | |
3978 } | |
3979 | |
3980 var nextNode = oldChildWrapper.nextSibling; | |
3981 var previousNode = oldChildWrapper.previousSibling; | |
3982 var nodes; | |
3983 | |
3984 var useNative = !this.invalidateShadowRenderer() && | |
3985 !invalidateParent(newChildWrapper); | |
3986 | |
3987 if (useNative) { | |
3988 nodes = collectNodesNative(newChildWrapper); | |
3989 } else { | |
3990 if (nextNode === newChildWrapper) | |
3991 nextNode = newChildWrapper.nextSibling; | |
3992 nodes = collectNodes(newChildWrapper, this, previousNode, nextNode); | |
3993 } | |
3994 | |
3995 if (!useNative) { | |
3996 if (this.firstChild === oldChildWrapper) | |
3997 this.firstChild_ = nodes[0]; | |
3998 if (this.lastChild === oldChildWrapper) | |
3999 this.lastChild_ = nodes[nodes.length - 1]; | |
4000 | |
4001 oldChildWrapper.previousSibling_ = oldChildWrapper.nextSibling_ = | |
4002 oldChildWrapper.parentNode_ = undefined; | |
4003 | |
4004 // replaceChild no matter what the parent is? | |
4005 if (oldChildNode.parentNode) { | |
4006 originalReplaceChild.call( | |
4007 oldChildNode.parentNode, | |
4008 unwrapNodesForInsertion(this, nodes), | |
4009 oldChildNode); | |
4010 } | |
4011 } else { | |
4012 ensureSameOwnerDocument(this, newChildWrapper); | |
4013 clearChildNodes(this); | |
4014 originalReplaceChild.call(this.impl, unwrap(newChildWrapper), | |
4015 oldChildNode); | |
4016 } | |
4017 | |
4018 enqueueMutation(this, 'childList', { | |
4019 addedNodes: nodes, | |
4020 removedNodes: createOneElementNodeList(oldChildWrapper), | |
4021 nextSibling: nextNode, | |
4022 previousSibling: previousNode | |
4023 }); | |
4024 | |
4025 nodeWasRemoved(oldChildWrapper); | |
4026 nodesWereAdded(nodes, this); | |
4027 | |
4028 return oldChildWrapper; | |
4029 }, | |
4030 | |
4031 /** | |
4032 * Called after a node was inserted. Subclasses override this to invalidate | |
4033 * the renderer as needed. | |
4034 * @private | |
4035 */ | |
4036 nodeIsInserted_: function() { | |
4037 for (var child = this.firstChild; child; child = child.nextSibling) { | |
4038 child.nodeIsInserted_(); | |
4039 } | |
4040 }, | |
4041 | |
4042 hasChildNodes: function() { | |
4043 return this.firstChild !== null; | |
4044 }, | |
4045 | |
4046 /** @type {Node} */ | |
4047 get parentNode() { | |
4048 // If the parentNode has not been overridden, use the original parentNode. | |
4049 return this.parentNode_ !== undefined ? | |
4050 this.parentNode_ : wrap(this.impl.parentNode); | |
4051 }, | |
4052 | |
4053 /** @type {Node} */ | |
4054 get firstChild() { | |
4055 return this.firstChild_ !== undefined ? | |
4056 this.firstChild_ : wrap(this.impl.firstChild); | |
4057 }, | |
4058 | |
4059 /** @type {Node} */ | |
4060 get lastChild() { | |
4061 return this.lastChild_ !== undefined ? | |
4062 this.lastChild_ : wrap(this.impl.lastChild); | |
4063 }, | |
4064 | |
4065 /** @type {Node} */ | |
4066 get nextSibling() { | |
4067 return this.nextSibling_ !== undefined ? | |
4068 this.nextSibling_ : wrap(this.impl.nextSibling); | |
4069 }, | |
4070 | |
4071 /** @type {Node} */ | |
4072 get previousSibling() { | |
4073 return this.previousSibling_ !== undefined ? | |
4074 this.previousSibling_ : wrap(this.impl.previousSibling); | |
4075 }, | |
4076 | |
4077 get parentElement() { | |
4078 var p = this.parentNode; | |
4079 while (p && p.nodeType !== Node.ELEMENT_NODE) { | |
4080 p = p.parentNode; | |
4081 } | |
4082 return p; | |
4083 }, | |
4084 | |
4085 get textContent() { | |
4086 // TODO(arv): This should fallback to this.impl.textContent if there | |
4087 // are no shadow trees below or above the context node. | |
4088 var s = ''; | |
4089 for (var child = this.firstChild; child; child = child.nextSibling) { | |
4090 if (child.nodeType != Node.COMMENT_NODE) { | |
4091 s += child.textContent; | |
4092 } | |
4093 } | |
4094 return s; | |
4095 }, | |
4096 set textContent(textContent) { | |
4097 var removedNodes = snapshotNodeList(this.childNodes); | |
4098 | |
4099 if (this.invalidateShadowRenderer()) { | |
4100 removeAllChildNodes(this); | |
4101 if (textContent !== '') { | |
4102 var textNode = this.impl.ownerDocument.createTextNode(textContent); | |
4103 this.appendChild(textNode); | |
4104 } | |
4105 } else { | |
4106 clearChildNodes(this); | |
4107 this.impl.textContent = textContent; | |
4108 } | |
4109 | |
4110 var addedNodes = snapshotNodeList(this.childNodes); | |
4111 | |
4112 enqueueMutation(this, 'childList', { | |
4113 addedNodes: addedNodes, | |
4114 removedNodes: removedNodes | |
4115 }); | |
4116 | |
4117 nodesWereRemoved(removedNodes); | |
4118 nodesWereAdded(addedNodes, this); | |
4119 }, | |
4120 | |
4121 get childNodes() { | |
4122 var wrapperList = new NodeList(); | |
4123 var i = 0; | |
4124 for (var child = this.firstChild; child; child = child.nextSibling) { | |
4125 wrapperList[i++] = child; | |
4126 } | |
4127 wrapperList.length = i; | |
4128 return wrapperList; | |
4129 }, | |
4130 | |
4131 cloneNode: function(deep) { | |
4132 return cloneNode(this, deep); | |
4133 }, | |
4134 | |
4135 contains: function(child) { | |
4136 return contains(this, wrapIfNeeded(child)); | |
4137 }, | |
4138 | |
4139 compareDocumentPosition: function(otherNode) { | |
4140 // This only wraps, it therefore only operates on the composed DOM and not | |
4141 // the logical DOM. | |
4142 return originalCompareDocumentPosition.call(this.impl, | |
4143 unwrapIfNeeded(otherNode)); | |
4144 }, | |
4145 | |
4146 normalize: function() { | |
4147 var nodes = snapshotNodeList(this.childNodes); | |
4148 var remNodes = []; | |
4149 var s = ''; | |
4150 var modNode; | |
4151 | |
4152 for (var i = 0, n; i < nodes.length; i++) { | |
4153 n = nodes[i]; | |
4154 if (n.nodeType === Node.TEXT_NODE) { | |
4155 if (!modNode && !n.data.length) | |
4156 this.removeNode(n); | |
4157 else if (!modNode) | |
4158 modNode = n; | |
4159 else { | |
4160 s += n.data; | |
4161 remNodes.push(n); | |
4162 } | |
4163 } else { | |
4164 if (modNode && remNodes.length) { | |
4165 modNode.data += s; | |
4166 cleanUpNodes(remNodes); | |
4167 } | |
4168 remNodes = []; | |
4169 s = ''; | |
4170 modNode = null; | |
4171 if (n.childNodes.length) | |
4172 n.normalize(); | |
4173 } | |
4174 } | |
4175 | |
4176 // handle case where >1 text nodes are the last children | |
4177 if (modNode && remNodes.length) { | |
4178 modNode.data += s; | |
4179 cleanupNodes(remNodes); | |
4180 } | |
4181 } | |
4182 }); | |
4183 | |
4184 defineWrapGetter(Node, 'ownerDocument'); | |
4185 | |
4186 // We use a DocumentFragment as a base and then delete the properties of | |
4187 // DocumentFragment.prototype from the wrapper Node. Since delete makes | |
4188 // objects slow in some JS engines we recreate the prototype object. | |
4189 registerWrapper(OriginalNode, Node, document.createDocumentFragment()); | |
4190 delete Node.prototype.querySelector; | |
4191 delete Node.prototype.querySelectorAll; | |
4192 Node.prototype = mixin(Object.create(EventTarget.prototype), Node.prototype); | |
4193 | |
4194 scope.cloneNode = cloneNode; | |
4195 scope.nodeWasAdded = nodeWasAdded; | |
4196 scope.nodeWasRemoved = nodeWasRemoved; | |
4197 scope.nodesWereAdded = nodesWereAdded; | |
4198 scope.nodesWereRemoved = nodesWereRemoved; | |
4199 scope.snapshotNodeList = snapshotNodeList; | |
4200 scope.wrappers.Node = Node; | |
4201 | |
4202 })(window.ShadowDOMPolyfill); | |
4203 | |
4204 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4205 // Use of this source code is governed by a BSD-style | |
4206 // license that can be found in the LICENSE file. | |
4207 | |
4208 (function(scope) { | |
4209 'use strict'; | |
4210 | |
4211 function findOne(node, selector) { | |
4212 var m, el = node.firstElementChild; | |
4213 while (el) { | |
4214 if (el.matches(selector)) | |
4215 return el; | |
4216 m = findOne(el, selector); | |
4217 if (m) | |
4218 return m; | |
4219 el = el.nextElementSibling; | |
4220 } | |
4221 return null; | |
4222 } | |
4223 | |
4224 function findAll(node, selector, results) { | |
4225 var el = node.firstElementChild; | |
4226 while (el) { | |
4227 if (el.matches(selector)) | |
4228 results[results.length++] = el; | |
4229 findAll(el, selector, results); | |
4230 el = el.nextElementSibling; | |
4231 } | |
4232 return results; | |
4233 } | |
4234 | |
4235 // find and findAll will only match Simple Selectors, | |
4236 // Structural Pseudo Classes are not guarenteed to be correct | |
4237 // http://www.w3.org/TR/css3-selectors/#simple-selectors | |
4238 | |
4239 var SelectorsInterface = { | |
4240 querySelector: function(selector) { | |
4241 return findOne(this, selector); | |
4242 }, | |
4243 querySelectorAll: function(selector) { | |
4244 return findAll(this, selector, new NodeList()) | |
4245 } | |
4246 }; | |
4247 | |
4248 var GetElementsByInterface = { | |
4249 getElementsByTagName: function(tagName) { | |
4250 // TODO(arv): Check tagName? | |
4251 return this.querySelectorAll(tagName); | |
4252 }, | |
4253 getElementsByClassName: function(className) { | |
4254 // TODO(arv): Check className? | |
4255 return this.querySelectorAll('.' + className); | |
4256 }, | |
4257 getElementsByTagNameNS: function(ns, tagName) { | |
4258 if (ns === '*') | |
4259 return this.getElementsByTagName(tagName); | |
4260 | |
4261 // TODO(arv): Check tagName? | |
4262 var result = new NodeList; | |
4263 var els = this.getElementsByTagName(tagName); | |
4264 for (var i = 0, j = 0; i < els.length; i++) { | |
4265 if (els[i].namespaceURI === ns) | |
4266 result[j++] = els[i]; | |
4267 } | |
4268 result.length = j; | |
4269 return result; | |
4270 } | |
4271 }; | |
4272 | |
4273 scope.GetElementsByInterface = GetElementsByInterface; | |
4274 scope.SelectorsInterface = SelectorsInterface; | |
4275 | |
4276 })(window.ShadowDOMPolyfill); | |
4277 | |
4278 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4279 // Use of this source code is goverened by a BSD-style | |
4280 // license that can be found in the LICENSE file. | |
4281 | |
4282 (function(scope) { | |
4283 'use strict'; | |
4284 | |
4285 var NodeList = scope.wrappers.NodeList; | |
4286 | |
4287 function forwardElement(node) { | |
4288 while (node && node.nodeType !== Node.ELEMENT_NODE) { | |
4289 node = node.nextSibling; | |
4290 } | |
4291 return node; | |
4292 } | |
4293 | |
4294 function backwardsElement(node) { | |
4295 while (node && node.nodeType !== Node.ELEMENT_NODE) { | |
4296 node = node.previousSibling; | |
4297 } | |
4298 return node; | |
4299 } | |
4300 | |
4301 var ParentNodeInterface = { | |
4302 get firstElementChild() { | |
4303 return forwardElement(this.firstChild); | |
4304 }, | |
4305 | |
4306 get lastElementChild() { | |
4307 return backwardsElement(this.lastChild); | |
4308 }, | |
4309 | |
4310 get childElementCount() { | |
4311 var count = 0; | |
4312 for (var child = this.firstElementChild; | |
4313 child; | |
4314 child = child.nextElementSibling) { | |
4315 count++; | |
4316 } | |
4317 return count; | |
4318 }, | |
4319 | |
4320 get children() { | |
4321 var wrapperList = new NodeList(); | |
4322 var i = 0; | |
4323 for (var child = this.firstElementChild; | |
4324 child; | |
4325 child = child.nextElementSibling) { | |
4326 wrapperList[i++] = child; | |
4327 } | |
4328 wrapperList.length = i; | |
4329 return wrapperList; | |
4330 }, | |
4331 | |
4332 remove: function() { | |
4333 var p = this.parentNode; | |
4334 if (p) | |
4335 p.removeChild(this); | |
4336 } | |
4337 }; | |
4338 | |
4339 var ChildNodeInterface = { | |
4340 get nextElementSibling() { | |
4341 return forwardElement(this.nextSibling); | |
4342 }, | |
4343 | |
4344 get previousElementSibling() { | |
4345 return backwardsElement(this.previousSibling); | |
4346 } | |
4347 }; | |
4348 | |
4349 scope.ChildNodeInterface = ChildNodeInterface; | |
4350 scope.ParentNodeInterface = ParentNodeInterface; | |
4351 | |
4352 })(window.ShadowDOMPolyfill); | |
4353 | |
4354 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4355 // Use of this source code is goverened by a BSD-style | |
4356 // license that can be found in the LICENSE file. | |
4357 | |
4358 (function(scope) { | |
4359 'use strict'; | |
4360 | |
4361 var ChildNodeInterface = scope.ChildNodeInterface; | |
4362 var Node = scope.wrappers.Node; | |
4363 var enqueueMutation = scope.enqueueMutation; | |
4364 var mixin = scope.mixin; | |
4365 var registerWrapper = scope.registerWrapper; | |
4366 | |
4367 var OriginalCharacterData = window.CharacterData; | |
4368 | |
4369 function CharacterData(node) { | |
4370 Node.call(this, node); | |
4371 } | |
4372 CharacterData.prototype = Object.create(Node.prototype); | |
4373 mixin(CharacterData.prototype, { | |
4374 get textContent() { | |
4375 return this.data; | |
4376 }, | |
4377 set textContent(value) { | |
4378 this.data = value; | |
4379 }, | |
4380 get data() { | |
4381 return this.impl.data; | |
4382 }, | |
4383 set data(value) { | |
4384 var oldValue = this.impl.data; | |
4385 enqueueMutation(this, 'characterData', { | |
4386 oldValue: oldValue | |
4387 }); | |
4388 this.impl.data = value; | |
4389 } | |
4390 }); | |
4391 | |
4392 mixin(CharacterData.prototype, ChildNodeInterface); | |
4393 | |
4394 registerWrapper(OriginalCharacterData, CharacterData, | |
4395 document.createTextNode('')); | |
4396 | |
4397 scope.wrappers.CharacterData = CharacterData; | |
4398 })(window.ShadowDOMPolyfill); | |
4399 | |
4400 // Copyright 2014 The Polymer Authors. All rights reserved. | |
4401 // Use of this source code is goverened by a BSD-style | |
4402 // license that can be found in the LICENSE file. | |
4403 | |
4404 (function(scope) { | |
4405 'use strict'; | |
4406 | |
4407 var CharacterData = scope.wrappers.CharacterData; | |
4408 var enqueueMutation = scope.enqueueMutation; | |
4409 var mixin = scope.mixin; | |
4410 var registerWrapper = scope.registerWrapper; | |
4411 | |
4412 function toUInt32(x) { | |
4413 return x >>> 0; | |
4414 } | |
4415 | |
4416 var OriginalText = window.Text; | |
4417 | |
4418 function Text(node) { | |
4419 CharacterData.call(this, node); | |
4420 } | |
4421 Text.prototype = Object.create(CharacterData.prototype); | |
4422 mixin(Text.prototype, { | |
4423 splitText: function(offset) { | |
4424 offset = toUInt32(offset); | |
4425 var s = this.data; | |
4426 if (offset > s.length) | |
4427 throw new Error('IndexSizeError'); | |
4428 var head = s.slice(0, offset); | |
4429 var tail = s.slice(offset); | |
4430 this.data = head; | |
4431 var newTextNode = this.ownerDocument.createTextNode(tail); | |
4432 if (this.parentNode) | |
4433 this.parentNode.insertBefore(newTextNode, this.nextSibling); | |
4434 return newTextNode; | |
4435 } | |
4436 }); | |
4437 | |
4438 registerWrapper(OriginalText, Text, document.createTextNode('')); | |
4439 | |
4440 scope.wrappers.Text = Text; | |
4441 })(window.ShadowDOMPolyfill); | |
4442 | |
4443 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4444 // Use of this source code is goverened by a BSD-style | |
4445 // license that can be found in the LICENSE file. | |
4446 | |
4447 (function(scope) { | |
4448 'use strict'; | |
4449 | |
4450 var ChildNodeInterface = scope.ChildNodeInterface; | |
4451 var GetElementsByInterface = scope.GetElementsByInterface; | |
4452 var Node = scope.wrappers.Node; | |
4453 var ParentNodeInterface = scope.ParentNodeInterface; | |
4454 var SelectorsInterface = scope.SelectorsInterface; | |
4455 var addWrapNodeListMethod = scope.addWrapNodeListMethod; | |
4456 var enqueueMutation = scope.enqueueMutation; | |
4457 var mixin = scope.mixin; | |
4458 var oneOf = scope.oneOf; | |
4459 var registerWrapper = scope.registerWrapper; | |
4460 var wrappers = scope.wrappers; | |
4461 | |
4462 var OriginalElement = window.Element; | |
4463 | |
4464 var matchesNames = [ | |
4465 'matches', // needs to come first. | |
4466 'mozMatchesSelector', | |
4467 'msMatchesSelector', | |
4468 'webkitMatchesSelector', | |
4469 ].filter(function(name) { | |
4470 return OriginalElement.prototype[name]; | |
4471 }); | |
4472 | |
4473 var matchesName = matchesNames[0]; | |
4474 | |
4475 var originalMatches = OriginalElement.prototype[matchesName]; | |
4476 | |
4477 function invalidateRendererBasedOnAttribute(element, name) { | |
4478 // Only invalidate if parent node is a shadow host. | |
4479 var p = element.parentNode; | |
4480 if (!p || !p.shadowRoot) | |
4481 return; | |
4482 | |
4483 var renderer = scope.getRendererForHost(p); | |
4484 if (renderer.dependsOnAttribute(name)) | |
4485 renderer.invalidate(); | |
4486 } | |
4487 | |
4488 function enqueAttributeChange(element, name, oldValue) { | |
4489 // This is not fully spec compliant. We should use localName (which might | |
4490 // have a different case than name) and the namespace (which requires us | |
4491 // to get the Attr object). | |
4492 enqueueMutation(element, 'attributes', { | |
4493 name: name, | |
4494 namespace: null, | |
4495 oldValue: oldValue | |
4496 }); | |
4497 } | |
4498 | |
4499 function Element(node) { | |
4500 Node.call(this, node); | |
4501 } | |
4502 Element.prototype = Object.create(Node.prototype); | |
4503 mixin(Element.prototype, { | |
4504 createShadowRoot: function() { | |
4505 var newShadowRoot = new wrappers.ShadowRoot(this); | |
4506 this.impl.polymerShadowRoot_ = newShadowRoot; | |
4507 | |
4508 var renderer = scope.getRendererForHost(this); | |
4509 renderer.invalidate(); | |
4510 | |
4511 return newShadowRoot; | |
4512 }, | |
4513 | |
4514 get shadowRoot() { | |
4515 return this.impl.polymerShadowRoot_ || null; | |
4516 }, | |
4517 | |
4518 setAttribute: function(name, value) { | |
4519 var oldValue = this.impl.getAttribute(name); | |
4520 this.impl.setAttribute(name, value); | |
4521 enqueAttributeChange(this, name, oldValue); | |
4522 invalidateRendererBasedOnAttribute(this, name); | |
4523 }, | |
4524 | |
4525 removeAttribute: function(name) { | |
4526 var oldValue = this.impl.getAttribute(name); | |
4527 this.impl.removeAttribute(name); | |
4528 enqueAttributeChange(this, name, oldValue); | |
4529 invalidateRendererBasedOnAttribute(this, name); | |
4530 }, | |
4531 | |
4532 matches: function(selector) { | |
4533 return originalMatches.call(this.impl, selector); | |
4534 } | |
4535 }); | |
4536 | |
4537 matchesNames.forEach(function(name) { | |
4538 if (name !== 'matches') { | |
4539 Element.prototype[name] = function(selector) { | |
4540 return this.matches(selector); | |
4541 }; | |
4542 } | |
4543 }); | |
4544 | |
4545 if (OriginalElement.prototype.webkitCreateShadowRoot) { | |
4546 Element.prototype.webkitCreateShadowRoot = | |
4547 Element.prototype.createShadowRoot; | |
4548 } | |
4549 | |
4550 /** | |
4551 * Useful for generating the accessor pair for a property that reflects an | |
4552 * attribute. | |
4553 */ | |
4554 function setterDirtiesAttribute(prototype, propertyName, opt_attrName) { | |
4555 var attrName = opt_attrName || propertyName; | |
4556 Object.defineProperty(prototype, propertyName, { | |
4557 get: function() { | |
4558 return this.impl[propertyName]; | |
4559 }, | |
4560 set: function(v) { | |
4561 this.impl[propertyName] = v; | |
4562 invalidateRendererBasedOnAttribute(this, attrName); | |
4563 }, | |
4564 configurable: true, | |
4565 enumerable: true | |
4566 }); | |
4567 } | |
4568 | |
4569 setterDirtiesAttribute(Element.prototype, 'id'); | |
4570 setterDirtiesAttribute(Element.prototype, 'className', 'class'); | |
4571 | |
4572 mixin(Element.prototype, ChildNodeInterface); | |
4573 mixin(Element.prototype, GetElementsByInterface); | |
4574 mixin(Element.prototype, ParentNodeInterface); | |
4575 mixin(Element.prototype, SelectorsInterface); | |
4576 | |
4577 registerWrapper(OriginalElement, Element, | |
4578 document.createElementNS(null, 'x')); | |
4579 | |
4580 // TODO(arv): Export setterDirtiesAttribute and apply it to more bindings | |
4581 // that reflect attributes. | |
4582 scope.matchesNames = matchesNames; | |
4583 scope.wrappers.Element = Element; | |
4584 })(window.ShadowDOMPolyfill); | |
4585 | |
4586 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4587 // Use of this source code is goverened by a BSD-style | |
4588 // license that can be found in the LICENSE file. | |
4589 | |
4590 (function(scope) { | |
4591 'use strict'; | |
4592 | |
4593 var Element = scope.wrappers.Element; | |
4594 var defineGetter = scope.defineGetter; | |
4595 var enqueueMutation = scope.enqueueMutation; | |
4596 var mixin = scope.mixin; | |
4597 var nodesWereAdded = scope.nodesWereAdded; | |
4598 var nodesWereRemoved = scope.nodesWereRemoved; | |
4599 var registerWrapper = scope.registerWrapper; | |
4600 var snapshotNodeList = scope.snapshotNodeList; | |
4601 var unwrap = scope.unwrap; | |
4602 var wrap = scope.wrap; | |
4603 var wrappers = scope.wrappers; | |
4604 | |
4605 ///////////////////////////////////////////////////////////////////////////// | |
4606 // innerHTML and outerHTML | |
4607 | |
4608 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#es
capingString | |
4609 var escapeAttrRegExp = /[&\u00A0"]/g; | |
4610 var escapeDataRegExp = /[&\u00A0<>]/g; | |
4611 | |
4612 function escapeReplace(c) { | |
4613 switch (c) { | |
4614 case '&': | |
4615 return '&'; | |
4616 case '<': | |
4617 return '<'; | |
4618 case '>': | |
4619 return '>'; | |
4620 case '"': | |
4621 return '"' | |
4622 case '\u00A0': | |
4623 return ' '; | |
4624 } | |
4625 } | |
4626 | |
4627 function escapeAttr(s) { | |
4628 return s.replace(escapeAttrRegExp, escapeReplace); | |
4629 } | |
4630 | |
4631 function escapeData(s) { | |
4632 return s.replace(escapeDataRegExp, escapeReplace); | |
4633 } | |
4634 | |
4635 function makeSet(arr) { | |
4636 var set = {}; | |
4637 for (var i = 0; i < arr.length; i++) { | |
4638 set[arr[i]] = true; | |
4639 } | |
4640 return set; | |
4641 } | |
4642 | |
4643 // http://www.whatwg.org/specs/web-apps/current-work/#void-elements | |
4644 var voidElements = makeSet([ | |
4645 'area', | |
4646 'base', | |
4647 'br', | |
4648 'col', | |
4649 'command', | |
4650 'embed', | |
4651 'hr', | |
4652 'img', | |
4653 'input', | |
4654 'keygen', | |
4655 'link', | |
4656 'meta', | |
4657 'param', | |
4658 'source', | |
4659 'track', | |
4660 'wbr' | |
4661 ]); | |
4662 | |
4663 var plaintextParents = makeSet([ | |
4664 'style', | |
4665 'script', | |
4666 'xmp', | |
4667 'iframe', | |
4668 'noembed', | |
4669 'noframes', | |
4670 'plaintext', | |
4671 'noscript' | |
4672 ]); | |
4673 | |
4674 function getOuterHTML(node, parentNode) { | |
4675 switch (node.nodeType) { | |
4676 case Node.ELEMENT_NODE: | |
4677 var tagName = node.tagName.toLowerCase(); | |
4678 var s = '<' + tagName; | |
4679 var attrs = node.attributes; | |
4680 for (var i = 0, attr; attr = attrs[i]; i++) { | |
4681 s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; | |
4682 } | |
4683 s += '>'; | |
4684 if (voidElements[tagName]) | |
4685 return s; | |
4686 | |
4687 return s + getInnerHTML(node) + '</' + tagName + '>'; | |
4688 | |
4689 case Node.TEXT_NODE: | |
4690 var data = node.data; | |
4691 if (parentNode && plaintextParents[parentNode.localName]) | |
4692 return data; | |
4693 return escapeData(data); | |
4694 | |
4695 case Node.COMMENT_NODE: | |
4696 return '<!--' + node.data + '-->'; | |
4697 | |
4698 default: | |
4699 console.error(node); | |
4700 throw new Error('not implemented'); | |
4701 } | |
4702 } | |
4703 | |
4704 function getInnerHTML(node) { | |
4705 if (node instanceof wrappers.HTMLTemplateElement) | |
4706 node = node.content; | |
4707 | |
4708 var s = ''; | |
4709 for (var child = node.firstChild; child; child = child.nextSibling) { | |
4710 s += getOuterHTML(child, node); | |
4711 } | |
4712 return s; | |
4713 } | |
4714 | |
4715 function setInnerHTML(node, value, opt_tagName) { | |
4716 var tagName = opt_tagName || 'div'; | |
4717 node.textContent = ''; | |
4718 var tempElement = unwrap(node.ownerDocument.createElement(tagName)); | |
4719 tempElement.innerHTML = value; | |
4720 var firstChild; | |
4721 while (firstChild = tempElement.firstChild) { | |
4722 node.appendChild(wrap(firstChild)); | |
4723 } | |
4724 } | |
4725 | |
4726 // IE11 does not have MSIE in the user agent string. | |
4727 var oldIe = /MSIE/.test(navigator.userAgent); | |
4728 | |
4729 var OriginalHTMLElement = window.HTMLElement; | |
4730 var OriginalHTMLTemplateElement = window.HTMLTemplateElement; | |
4731 | |
4732 function HTMLElement(node) { | |
4733 Element.call(this, node); | |
4734 } | |
4735 HTMLElement.prototype = Object.create(Element.prototype); | |
4736 mixin(HTMLElement.prototype, { | |
4737 get innerHTML() { | |
4738 return getInnerHTML(this); | |
4739 }, | |
4740 set innerHTML(value) { | |
4741 // IE9 does not handle set innerHTML correctly on plaintextParents. It | |
4742 // creates element children. For example | |
4743 // | |
4744 // scriptElement.innerHTML = '<a>test</a>' | |
4745 // | |
4746 // Creates a single HTMLAnchorElement child. | |
4747 if (oldIe && plaintextParents[this.localName]) { | |
4748 this.textContent = value; | |
4749 return; | |
4750 } | |
4751 | |
4752 var removedNodes = snapshotNodeList(this.childNodes); | |
4753 | |
4754 if (this.invalidateShadowRenderer()) { | |
4755 if (this instanceof wrappers.HTMLTemplateElement) | |
4756 setInnerHTML(this.content, value); | |
4757 else | |
4758 setInnerHTML(this, value, this.tagName); | |
4759 | |
4760 // If we have a non native template element we need to handle this | |
4761 // manually since setting impl.innerHTML would add the html as direct | |
4762 // children and not be moved over to the content fragment. | |
4763 } else if (!OriginalHTMLTemplateElement && | |
4764 this instanceof wrappers.HTMLTemplateElement) { | |
4765 setInnerHTML(this.content, value); | |
4766 } else { | |
4767 this.impl.innerHTML = value; | |
4768 } | |
4769 | |
4770 var addedNodes = snapshotNodeList(this.childNodes); | |
4771 | |
4772 enqueueMutation(this, 'childList', { | |
4773 addedNodes: addedNodes, | |
4774 removedNodes: removedNodes | |
4775 }); | |
4776 | |
4777 nodesWereRemoved(removedNodes); | |
4778 nodesWereAdded(addedNodes, this); | |
4779 }, | |
4780 | |
4781 get outerHTML() { | |
4782 return getOuterHTML(this, this.parentNode); | |
4783 }, | |
4784 set outerHTML(value) { | |
4785 var p = this.parentNode; | |
4786 if (p) { | |
4787 p.invalidateShadowRenderer(); | |
4788 var df = frag(p, value); | |
4789 p.replaceChild(df, this); | |
4790 } | |
4791 }, | |
4792 | |
4793 insertAdjacentHTML: function(position, text) { | |
4794 var contextElement, refNode; | |
4795 switch (String(position).toLowerCase()) { | |
4796 case 'beforebegin': | |
4797 contextElement = this.parentNode; | |
4798 refNode = this; | |
4799 break; | |
4800 case 'afterend': | |
4801 contextElement = this.parentNode; | |
4802 refNode = this.nextSibling; | |
4803 break; | |
4804 case 'afterbegin': | |
4805 contextElement = this; | |
4806 refNode = this.firstChild; | |
4807 break; | |
4808 case 'beforeend': | |
4809 contextElement = this; | |
4810 refNode = null; | |
4811 break; | |
4812 default: | |
4813 return; | |
4814 } | |
4815 | |
4816 var df = frag(contextElement, text); | |
4817 contextElement.insertBefore(df, refNode); | |
4818 } | |
4819 }); | |
4820 | |
4821 function frag(contextElement, html) { | |
4822 // TODO(arv): This does not work with SVG and other non HTML elements. | |
4823 var p = unwrap(contextElement.cloneNode(false)); | |
4824 p.innerHTML = html; | |
4825 var df = unwrap(document.createDocumentFragment()); | |
4826 var c; | |
4827 while (c = p.firstChild) { | |
4828 df.appendChild(c); | |
4829 } | |
4830 return wrap(df); | |
4831 } | |
4832 | |
4833 function getter(name) { | |
4834 return function() { | |
4835 scope.renderAllPending(); | |
4836 return this.impl[name]; | |
4837 }; | |
4838 } | |
4839 | |
4840 function getterRequiresRendering(name) { | |
4841 defineGetter(HTMLElement, name, getter(name)); | |
4842 } | |
4843 | |
4844 [ | |
4845 'clientHeight', | |
4846 'clientLeft', | |
4847 'clientTop', | |
4848 'clientWidth', | |
4849 'offsetHeight', | |
4850 'offsetLeft', | |
4851 'offsetTop', | |
4852 'offsetWidth', | |
4853 'scrollHeight', | |
4854 'scrollWidth', | |
4855 ].forEach(getterRequiresRendering); | |
4856 | |
4857 function getterAndSetterRequiresRendering(name) { | |
4858 Object.defineProperty(HTMLElement.prototype, name, { | |
4859 get: getter(name), | |
4860 set: function(v) { | |
4861 scope.renderAllPending(); | |
4862 this.impl[name] = v; | |
4863 }, | |
4864 configurable: true, | |
4865 enumerable: true | |
4866 }); | |
4867 } | |
4868 | |
4869 [ | |
4870 'scrollLeft', | |
4871 'scrollTop', | |
4872 ].forEach(getterAndSetterRequiresRendering); | |
4873 | |
4874 function methodRequiresRendering(name) { | |
4875 Object.defineProperty(HTMLElement.prototype, name, { | |
4876 value: function() { | |
4877 scope.renderAllPending(); | |
4878 return this.impl[name].apply(this.impl, arguments); | |
4879 }, | |
4880 configurable: true, | |
4881 enumerable: true | |
4882 }); | |
4883 } | |
4884 | |
4885 [ | |
4886 'getBoundingClientRect', | |
4887 'getClientRects', | |
4888 'scrollIntoView' | |
4889 ].forEach(methodRequiresRendering); | |
4890 | |
4891 // HTMLElement is abstract so we use a subclass that has no members. | |
4892 registerWrapper(OriginalHTMLElement, HTMLElement, | |
4893 document.createElement('b')); | |
4894 | |
4895 scope.wrappers.HTMLElement = HTMLElement; | |
4896 | |
4897 // TODO: Find a better way to share these two with WrapperShadowRoot. | |
4898 scope.getInnerHTML = getInnerHTML; | |
4899 scope.setInnerHTML = setInnerHTML | |
4900 })(window.ShadowDOMPolyfill); | |
4901 | |
4902 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4903 // Use of this source code is goverened by a BSD-style | |
4904 // license that can be found in the LICENSE file. | |
4905 | |
4906 (function(scope) { | |
4907 'use strict'; | |
4908 | |
4909 var HTMLElement = scope.wrappers.HTMLElement; | |
4910 var mixin = scope.mixin; | |
4911 var registerWrapper = scope.registerWrapper; | |
4912 var wrap = scope.wrap; | |
4913 | |
4914 var OriginalHTMLCanvasElement = window.HTMLCanvasElement; | |
4915 | |
4916 function HTMLCanvasElement(node) { | |
4917 HTMLElement.call(this, node); | |
4918 } | |
4919 HTMLCanvasElement.prototype = Object.create(HTMLElement.prototype); | |
4920 | |
4921 mixin(HTMLCanvasElement.prototype, { | |
4922 getContext: function() { | |
4923 var context = this.impl.getContext.apply(this.impl, arguments); | |
4924 return context && wrap(context); | |
4925 } | |
4926 }); | |
4927 | |
4928 registerWrapper(OriginalHTMLCanvasElement, HTMLCanvasElement, | |
4929 document.createElement('canvas')); | |
4930 | |
4931 scope.wrappers.HTMLCanvasElement = HTMLCanvasElement; | |
4932 })(window.ShadowDOMPolyfill); | |
4933 | |
4934 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4935 // Use of this source code is goverened by a BSD-style | |
4936 // license that can be found in the LICENSE file. | |
4937 | |
4938 (function(scope) { | |
4939 'use strict'; | |
4940 | |
4941 var HTMLElement = scope.wrappers.HTMLElement; | |
4942 var mixin = scope.mixin; | |
4943 var registerWrapper = scope.registerWrapper; | |
4944 | |
4945 var OriginalHTMLContentElement = window.HTMLContentElement; | |
4946 | |
4947 function HTMLContentElement(node) { | |
4948 HTMLElement.call(this, node); | |
4949 } | |
4950 HTMLContentElement.prototype = Object.create(HTMLElement.prototype); | |
4951 mixin(HTMLContentElement.prototype, { | |
4952 get select() { | |
4953 return this.getAttribute('select'); | |
4954 }, | |
4955 set select(value) { | |
4956 this.setAttribute('select', value); | |
4957 }, | |
4958 | |
4959 setAttribute: function(n, v) { | |
4960 HTMLElement.prototype.setAttribute.call(this, n, v); | |
4961 if (String(n).toLowerCase() === 'select') | |
4962 this.invalidateShadowRenderer(true); | |
4963 } | |
4964 | |
4965 // getDistributedNodes is added in ShadowRenderer | |
4966 | |
4967 // TODO: attribute boolean resetStyleInheritance; | |
4968 }); | |
4969 | |
4970 if (OriginalHTMLContentElement) | |
4971 registerWrapper(OriginalHTMLContentElement, HTMLContentElement); | |
4972 | |
4973 scope.wrappers.HTMLContentElement = HTMLContentElement; | |
4974 })(window.ShadowDOMPolyfill); | |
4975 | |
4976 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4977 // Use of this source code is goverened by a BSD-style | |
4978 // license that can be found in the LICENSE file. | |
4979 | |
4980 (function(scope) { | |
4981 'use strict'; | |
4982 | |
4983 var HTMLElement = scope.wrappers.HTMLElement; | |
4984 var registerWrapper = scope.registerWrapper; | |
4985 var unwrap = scope.unwrap; | |
4986 var rewrap = scope.rewrap; | |
4987 | |
4988 var OriginalHTMLImageElement = window.HTMLImageElement; | |
4989 | |
4990 function HTMLImageElement(node) { | |
4991 HTMLElement.call(this, node); | |
4992 } | |
4993 HTMLImageElement.prototype = Object.create(HTMLElement.prototype); | |
4994 | |
4995 registerWrapper(OriginalHTMLImageElement, HTMLImageElement, | |
4996 document.createElement('img')); | |
4997 | |
4998 function Image(width, height) { | |
4999 if (!(this instanceof Image)) { | |
5000 throw new TypeError( | |
5001 'DOM object constructor cannot be called as a function.'); | |
5002 } | |
5003 | |
5004 var node = unwrap(document.createElement('img')); | |
5005 HTMLElement.call(this, node); | |
5006 rewrap(node, this); | |
5007 | |
5008 if (width !== undefined) | |
5009 node.width = width; | |
5010 if (height !== undefined) | |
5011 node.height = height; | |
5012 } | |
5013 | |
5014 Image.prototype = HTMLImageElement.prototype; | |
5015 | |
5016 scope.wrappers.HTMLImageElement = HTMLImageElement; | |
5017 scope.wrappers.Image = Image; | |
5018 })(window.ShadowDOMPolyfill); | |
5019 | |
5020 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5021 // Use of this source code is goverened by a BSD-style | |
5022 // license that can be found in the LICENSE file. | |
5023 | |
5024 (function(scope) { | |
5025 'use strict'; | |
5026 | |
5027 var HTMLElement = scope.wrappers.HTMLElement; | |
5028 var mixin = scope.mixin; | |
5029 var registerWrapper = scope.registerWrapper; | |
5030 | |
5031 var OriginalHTMLShadowElement = window.HTMLShadowElement; | |
5032 | |
5033 function HTMLShadowElement(node) { | |
5034 HTMLElement.call(this, node); | |
5035 } | |
5036 HTMLShadowElement.prototype = Object.create(HTMLElement.prototype); | |
5037 mixin(HTMLShadowElement.prototype, { | |
5038 // TODO: attribute boolean resetStyleInheritance; | |
5039 }); | |
5040 | |
5041 if (OriginalHTMLShadowElement) | |
5042 registerWrapper(OriginalHTMLShadowElement, HTMLShadowElement); | |
5043 | |
5044 scope.wrappers.HTMLShadowElement = HTMLShadowElement; | |
5045 })(window.ShadowDOMPolyfill); | |
5046 | |
5047 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5048 // Use of this source code is goverened by a BSD-style | |
5049 // license that can be found in the LICENSE file. | |
5050 | |
5051 (function(scope) { | |
5052 'use strict'; | |
5053 | |
5054 var HTMLElement = scope.wrappers.HTMLElement; | |
5055 var mixin = scope.mixin; | |
5056 var registerWrapper = scope.registerWrapper; | |
5057 var unwrap = scope.unwrap; | |
5058 var wrap = scope.wrap; | |
5059 | |
5060 var contentTable = new WeakMap(); | |
5061 var templateContentsOwnerTable = new WeakMap(); | |
5062 | |
5063 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner | |
5064 function getTemplateContentsOwner(doc) { | |
5065 if (!doc.defaultView) | |
5066 return doc; | |
5067 var d = templateContentsOwnerTable.get(doc); | |
5068 if (!d) { | |
5069 // TODO(arv): This should either be a Document or HTMLDocument depending | |
5070 // on doc. | |
5071 d = doc.implementation.createHTMLDocument(''); | |
5072 while (d.lastChild) { | |
5073 d.removeChild(d.lastChild); | |
5074 } | |
5075 templateContentsOwnerTable.set(doc, d); | |
5076 } | |
5077 return d; | |
5078 } | |
5079 | |
5080 function extractContent(templateElement) { | |
5081 // templateElement is not a wrapper here. | |
5082 var doc = getTemplateContentsOwner(templateElement.ownerDocument); | |
5083 var df = unwrap(doc.createDocumentFragment()); | |
5084 var child; | |
5085 while (child = templateElement.firstChild) { | |
5086 df.appendChild(child); | |
5087 } | |
5088 return df; | |
5089 } | |
5090 | |
5091 var OriginalHTMLTemplateElement = window.HTMLTemplateElement; | |
5092 | |
5093 function HTMLTemplateElement(node) { | |
5094 HTMLElement.call(this, node); | |
5095 if (!OriginalHTMLTemplateElement) { | |
5096 var content = extractContent(node); | |
5097 contentTable.set(this, wrap(content)); | |
5098 } | |
5099 } | |
5100 HTMLTemplateElement.prototype = Object.create(HTMLElement.prototype); | |
5101 | |
5102 mixin(HTMLTemplateElement.prototype, { | |
5103 get content() { | |
5104 if (OriginalHTMLTemplateElement) | |
5105 return wrap(this.impl.content); | |
5106 return contentTable.get(this); | |
5107 }, | |
5108 | |
5109 // TODO(arv): cloneNode needs to clone content. | |
5110 | |
5111 }); | |
5112 | |
5113 if (OriginalHTMLTemplateElement) | |
5114 registerWrapper(OriginalHTMLTemplateElement, HTMLTemplateElement); | |
5115 | |
5116 scope.wrappers.HTMLTemplateElement = HTMLTemplateElement; | |
5117 })(window.ShadowDOMPolyfill); | |
5118 | |
5119 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5120 // Use of this source code is goverened by a BSD-style | |
5121 // license that can be found in the LICENSE file. | |
5122 | |
5123 (function(scope) { | |
5124 'use strict'; | |
5125 | |
5126 var HTMLElement = scope.wrappers.HTMLElement; | |
5127 var registerWrapper = scope.registerWrapper; | |
5128 | |
5129 var OriginalHTMLMediaElement = window.HTMLMediaElement; | |
5130 | |
5131 function HTMLMediaElement(node) { | |
5132 HTMLElement.call(this, node); | |
5133 } | |
5134 HTMLMediaElement.prototype = Object.create(HTMLElement.prototype); | |
5135 | |
5136 registerWrapper(OriginalHTMLMediaElement, HTMLMediaElement, | |
5137 document.createElement('audio')); | |
5138 | |
5139 scope.wrappers.HTMLMediaElement = HTMLMediaElement; | |
5140 })(window.ShadowDOMPolyfill); | |
5141 | |
5142 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5143 // Use of this source code is goverened by a BSD-style | |
5144 // license that can be found in the LICENSE file. | |
5145 | |
5146 (function(scope) { | |
5147 'use strict'; | |
5148 | |
5149 var HTMLMediaElement = scope.wrappers.HTMLMediaElement; | |
5150 var registerWrapper = scope.registerWrapper; | |
5151 var unwrap = scope.unwrap; | |
5152 var rewrap = scope.rewrap; | |
5153 | |
5154 var OriginalHTMLAudioElement = window.HTMLAudioElement; | |
5155 | |
5156 function HTMLAudioElement(node) { | |
5157 HTMLMediaElement.call(this, node); | |
5158 } | |
5159 HTMLAudioElement.prototype = Object.create(HTMLMediaElement.prototype); | |
5160 | |
5161 registerWrapper(OriginalHTMLAudioElement, HTMLAudioElement, | |
5162 document.createElement('audio')); | |
5163 | |
5164 function Audio(src) { | |
5165 if (!(this instanceof Audio)) { | |
5166 throw new TypeError( | |
5167 'DOM object constructor cannot be called as a function.'); | |
5168 } | |
5169 | |
5170 var node = unwrap(document.createElement('audio')); | |
5171 HTMLMediaElement.call(this, node); | |
5172 rewrap(node, this); | |
5173 | |
5174 node.setAttribute('preload', 'auto'); | |
5175 if (src !== undefined) | |
5176 node.setAttribute('src', src); | |
5177 } | |
5178 | |
5179 Audio.prototype = HTMLAudioElement.prototype; | |
5180 | |
5181 scope.wrappers.HTMLAudioElement = HTMLAudioElement; | |
5182 scope.wrappers.Audio = Audio; | |
5183 })(window.ShadowDOMPolyfill); | |
5184 | |
5185 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5186 // Use of this source code is goverened by a BSD-style | |
5187 // license that can be found in the LICENSE file. | |
5188 | |
5189 (function(scope) { | |
5190 'use strict'; | |
5191 | |
5192 var HTMLElement = scope.wrappers.HTMLElement; | |
5193 var mixin = scope.mixin; | |
5194 var registerWrapper = scope.registerWrapper; | |
5195 var rewrap = scope.rewrap; | |
5196 var unwrap = scope.unwrap; | |
5197 var wrap = scope.wrap; | |
5198 | |
5199 var OriginalHTMLOptionElement = window.HTMLOptionElement; | |
5200 | |
5201 function trimText(s) { | |
5202 return s.replace(/\s+/g, ' ').trim(); | |
5203 } | |
5204 | |
5205 function HTMLOptionElement(node) { | |
5206 HTMLElement.call(this, node); | |
5207 } | |
5208 HTMLOptionElement.prototype = Object.create(HTMLElement.prototype); | |
5209 mixin(HTMLOptionElement.prototype, { | |
5210 get text() { | |
5211 return trimText(this.textContent); | |
5212 }, | |
5213 set text(value) { | |
5214 this.textContent = trimText(String(value)); | |
5215 }, | |
5216 get form() { | |
5217 return wrap(unwrap(this).form); | |
5218 } | |
5219 }); | |
5220 | |
5221 registerWrapper(OriginalHTMLOptionElement, HTMLOptionElement, | |
5222 document.createElement('option')); | |
5223 | |
5224 function Option(text, value, defaultSelected, selected) { | |
5225 if (!(this instanceof Option)) { | |
5226 throw new TypeError( | |
5227 'DOM object constructor cannot be called as a function.'); | |
5228 } | |
5229 | |
5230 var node = unwrap(document.createElement('option')); | |
5231 HTMLElement.call(this, node); | |
5232 rewrap(node, this); | |
5233 | |
5234 if (text !== undefined) | |
5235 node.text = text; | |
5236 if (value !== undefined) | |
5237 node.setAttribute('value', value); | |
5238 if (defaultSelected === true) | |
5239 node.setAttribute('selected', ''); | |
5240 node.selected = selected === true; | |
5241 } | |
5242 | |
5243 Option.prototype = HTMLOptionElement.prototype; | |
5244 | |
5245 scope.wrappers.HTMLOptionElement = HTMLOptionElement; | |
5246 scope.wrappers.Option = Option; | |
5247 })(window.ShadowDOMPolyfill); | |
5248 | |
5249 // Copyright 2014 The Polymer Authors. All rights reserved. | |
5250 // Use of this source code is goverened by a BSD-style | |
5251 // license that can be found in the LICENSE file. | |
5252 | |
5253 (function(scope) { | |
5254 'use strict'; | |
5255 | |
5256 var HTMLElement = scope.wrappers.HTMLElement; | |
5257 var mixin = scope.mixin; | |
5258 var registerWrapper = scope.registerWrapper; | |
5259 var unwrap = scope.unwrap; | |
5260 var wrap = scope.wrap; | |
5261 | |
5262 var OriginalHTMLSelectElement = window.HTMLSelectElement; | |
5263 | |
5264 function HTMLSelectElement(node) { | |
5265 HTMLElement.call(this, node); | |
5266 } | |
5267 HTMLSelectElement.prototype = Object.create(HTMLElement.prototype); | |
5268 mixin(HTMLSelectElement.prototype, { | |
5269 add: function(element, before) { | |
5270 if (typeof before === 'object') // also includes null | |
5271 before = unwrap(before); | |
5272 unwrap(this).add(unwrap(element), before); | |
5273 }, | |
5274 | |
5275 remove: function(indexOrNode) { | |
5276 // Spec only allows index but implementations allow index or node. | |
5277 // remove() is also allowed which is same as remove(undefined) | |
5278 if (indexOrNode === undefined) { | |
5279 HTMLElement.prototype.remove.call(this); | |
5280 return; | |
5281 } | |
5282 | |
5283 if (typeof indexOrNode === 'object') | |
5284 indexOrNode = unwrap(indexOrNode); | |
5285 | |
5286 unwrap(this).remove(indexOrNode); | |
5287 }, | |
5288 | |
5289 get form() { | |
5290 return wrap(unwrap(this).form); | |
5291 } | |
5292 }); | |
5293 | |
5294 registerWrapper(OriginalHTMLSelectElement, HTMLSelectElement, | |
5295 document.createElement('select')); | |
5296 | |
5297 scope.wrappers.HTMLSelectElement = HTMLSelectElement; | |
5298 })(window.ShadowDOMPolyfill); | |
5299 | |
5300 /* | |
5301 * Copyright 2014 The Polymer Authors. All rights reserved. | |
5302 * Use of this source code is goverened by a BSD-style | |
5303 * license that can be found in the LICENSE file. | |
5304 */ | |
5305 | |
5306 (function(scope) { | |
5307 'use strict'; | |
5308 | |
5309 var HTMLElement = scope.wrappers.HTMLElement; | |
5310 var mixin = scope.mixin; | |
5311 var registerWrapper = scope.registerWrapper; | |
5312 var unwrap = scope.unwrap; | |
5313 var wrap = scope.wrap; | |
5314 var wrapHTMLCollection = scope.wrapHTMLCollection; | |
5315 | |
5316 var OriginalHTMLTableElement = window.HTMLTableElement; | |
5317 | |
5318 function HTMLTableElement(node) { | |
5319 HTMLElement.call(this, node); | |
5320 } | |
5321 HTMLTableElement.prototype = Object.create(HTMLElement.prototype); | |
5322 mixin(HTMLTableElement.prototype, { | |
5323 get caption() { | |
5324 return wrap(unwrap(this).caption); | |
5325 }, | |
5326 createCaption: function() { | |
5327 return wrap(unwrap(this).createCaption()); | |
5328 }, | |
5329 | |
5330 get tHead() { | |
5331 return wrap(unwrap(this).tHead); | |
5332 }, | |
5333 createTHead: function() { | |
5334 return wrap(unwrap(this).createTHead()); | |
5335 }, | |
5336 | |
5337 createTFoot: function() { | |
5338 return wrap(unwrap(this).createTFoot()); | |
5339 }, | |
5340 get tFoot() { | |
5341 return wrap(unwrap(this).tFoot); | |
5342 }, | |
5343 | |
5344 get tBodies() { | |
5345 return wrapHTMLCollection(unwrap(this).tBodies); | |
5346 }, | |
5347 createTBody: function() { | |
5348 return wrap(unwrap(this).createTBody()); | |
5349 }, | |
5350 | |
5351 get rows() { | |
5352 return wrapHTMLCollection(unwrap(this).rows); | |
5353 }, | |
5354 insertRow: function(index) { | |
5355 return wrap(unwrap(this).insertRow(index)); | |
5356 } | |
5357 }); | |
5358 | |
5359 registerWrapper(OriginalHTMLTableElement, HTMLTableElement, | |
5360 document.createElement('table')); | |
5361 | |
5362 scope.wrappers.HTMLTableElement = HTMLTableElement; | |
5363 })(window.ShadowDOMPolyfill); | |
5364 | |
5365 /* | |
5366 * Copyright 2014 The Polymer Authors. All rights reserved. | |
5367 * Use of this source code is goverened by a BSD-style | |
5368 * license that can be found in the LICENSE file. | |
5369 */ | |
5370 | |
5371 (function(scope) { | |
5372 'use strict'; | |
5373 | |
5374 var HTMLElement = scope.wrappers.HTMLElement; | |
5375 var mixin = scope.mixin; | |
5376 var registerWrapper = scope.registerWrapper; | |
5377 var wrapHTMLCollection = scope.wrapHTMLCollection; | |
5378 var unwrap = scope.unwrap; | |
5379 var wrap = scope.wrap; | |
5380 | |
5381 var OriginalHTMLTableSectionElement = window.HTMLTableSectionElement; | |
5382 | |
5383 function HTMLTableSectionElement(node) { | |
5384 HTMLElement.call(this, node); | |
5385 } | |
5386 HTMLTableSectionElement.prototype = Object.create(HTMLElement.prototype); | |
5387 mixin(HTMLTableSectionElement.prototype, { | |
5388 get rows() { | |
5389 return wrapHTMLCollection(unwrap(this).rows); | |
5390 }, | |
5391 insertRow: function(index) { | |
5392 return wrap(unwrap(this).insertRow(index)); | |
5393 } | |
5394 }); | |
5395 | |
5396 registerWrapper(OriginalHTMLTableSectionElement, HTMLTableSectionElement, | |
5397 document.createElement('thead')); | |
5398 | |
5399 scope.wrappers.HTMLTableSectionElement = HTMLTableSectionElement; | |
5400 })(window.ShadowDOMPolyfill); | |
5401 | |
5402 /* | |
5403 * Copyright 2014 The Polymer Authors. All rights reserved. | |
5404 * Use of this source code is goverened by a BSD-style | |
5405 * license that can be found in the LICENSE file. | |
5406 */ | |
5407 | |
5408 (function(scope) { | |
5409 'use strict'; | |
5410 | |
5411 var HTMLElement = scope.wrappers.HTMLElement; | |
5412 var mixin = scope.mixin; | |
5413 var registerWrapper = scope.registerWrapper; | |
5414 var wrapHTMLCollection = scope.wrapHTMLCollection; | |
5415 var unwrap = scope.unwrap; | |
5416 var wrap = scope.wrap; | |
5417 | |
5418 var OriginalHTMLTableRowElement = window.HTMLTableRowElement; | |
5419 | |
5420 function HTMLTableRowElement(node) { | |
5421 HTMLElement.call(this, node); | |
5422 } | |
5423 HTMLTableRowElement.prototype = Object.create(HTMLElement.prototype); | |
5424 mixin(HTMLTableRowElement.prototype, { | |
5425 get cells() { | |
5426 return wrapHTMLCollection(unwrap(this).cells); | |
5427 }, | |
5428 | |
5429 insertCell: function(index) { | |
5430 return wrap(unwrap(this).insertCell(index)); | |
5431 } | |
5432 }); | |
5433 | |
5434 registerWrapper(OriginalHTMLTableRowElement, HTMLTableRowElement, | |
5435 document.createElement('tr')); | |
5436 | |
5437 scope.wrappers.HTMLTableRowElement = HTMLTableRowElement; | |
5438 })(window.ShadowDOMPolyfill); | |
5439 | |
5440 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5441 // Use of this source code is goverened by a BSD-style | |
5442 // license that can be found in the LICENSE file. | |
5443 | |
5444 (function(scope) { | |
5445 'use strict'; | |
5446 | |
5447 var HTMLContentElement = scope.wrappers.HTMLContentElement; | |
5448 var HTMLElement = scope.wrappers.HTMLElement; | |
5449 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; | |
5450 var HTMLTemplateElement = scope.wrappers.HTMLTemplateElement; | |
5451 var mixin = scope.mixin; | |
5452 var registerWrapper = scope.registerWrapper; | |
5453 | |
5454 var OriginalHTMLUnknownElement = window.HTMLUnknownElement; | |
5455 | |
5456 function HTMLUnknownElement(node) { | |
5457 switch (node.localName) { | |
5458 case 'content': | |
5459 return new HTMLContentElement(node); | |
5460 case 'shadow': | |
5461 return new HTMLShadowElement(node); | |
5462 case 'template': | |
5463 return new HTMLTemplateElement(node); | |
5464 } | |
5465 HTMLElement.call(this, node); | |
5466 } | |
5467 HTMLUnknownElement.prototype = Object.create(HTMLElement.prototype); | |
5468 registerWrapper(OriginalHTMLUnknownElement, HTMLUnknownElement); | |
5469 scope.wrappers.HTMLUnknownElement = HTMLUnknownElement; | |
5470 })(window.ShadowDOMPolyfill); | |
5471 | |
5472 // Copyright 2014 The Polymer Authors. All rights reserved. | |
5473 // Use of this source code is goverened by a BSD-style | |
5474 // license that can be found in the LICENSE file. | |
5475 | |
5476 (function(scope) { | |
5477 'use strict'; | |
5478 | |
5479 var registerObject = scope.registerObject; | |
5480 | |
5481 var SVG_NS = 'http://www.w3.org/2000/svg'; | |
5482 var svgTitleElement = document.createElementNS(SVG_NS, 'title'); | |
5483 var SVGTitleElement = registerObject(svgTitleElement); | |
5484 var SVGElement = Object.getPrototypeOf(SVGTitleElement.prototype).constructor; | |
5485 | |
5486 scope.wrappers.SVGElement = SVGElement; | |
5487 })(window.ShadowDOMPolyfill); | |
5488 | |
5489 // Copyright 2014 The Polymer Authors. All rights reserved. | |
5490 // Use of this source code is goverened by a BSD-style | |
5491 // license that can be found in the LICENSE file. | |
5492 | |
5493 (function(scope) { | |
5494 'use strict'; | |
5495 | |
5496 var mixin = scope.mixin; | |
5497 var registerWrapper = scope.registerWrapper; | |
5498 var unwrap = scope.unwrap; | |
5499 var wrap = scope.wrap; | |
5500 | |
5501 var OriginalSVGUseElement = window.SVGUseElement; | |
5502 | |
5503 // IE uses SVGElement as parent interface, SVG2 (Blink & Gecko) uses | |
5504 // SVGGraphicsElement. Use the <g> element to get the right prototype. | |
5505 | |
5506 var SVG_NS = 'http://www.w3.org/2000/svg'; | |
5507 var gWrapper = wrap(document.createElementNS(SVG_NS, 'g')); | |
5508 var useElement = document.createElementNS(SVG_NS, 'use'); | |
5509 var SVGGElement = gWrapper.constructor; | |
5510 var parentInterfacePrototype = Object.getPrototypeOf(SVGGElement.prototype); | |
5511 var parentInterface = parentInterfacePrototype.constructor; | |
5512 | |
5513 function SVGUseElement(impl) { | |
5514 parentInterface.call(this, impl); | |
5515 } | |
5516 | |
5517 SVGUseElement.prototype = Object.create(parentInterfacePrototype); | |
5518 | |
5519 // Firefox does not expose instanceRoot. | |
5520 if ('instanceRoot' in useElement) { | |
5521 mixin(SVGUseElement.prototype, { | |
5522 get instanceRoot() { | |
5523 return wrap(unwrap(this).instanceRoot); | |
5524 }, | |
5525 get animatedInstanceRoot() { | |
5526 return wrap(unwrap(this).animatedInstanceRoot); | |
5527 }, | |
5528 }); | |
5529 } | |
5530 | |
5531 registerWrapper(OriginalSVGUseElement, SVGUseElement, useElement); | |
5532 | |
5533 scope.wrappers.SVGUseElement = SVGUseElement; | |
5534 })(window.ShadowDOMPolyfill); | |
5535 | |
5536 // Copyright 2014 The Polymer Authors. All rights reserved. | |
5537 // Use of this source code is goverened by a BSD-style | |
5538 // license that can be found in the LICENSE file. | |
5539 | |
5540 (function(scope) { | |
5541 'use strict'; | |
5542 | |
5543 var EventTarget = scope.wrappers.EventTarget; | |
5544 var mixin = scope.mixin; | |
5545 var registerWrapper = scope.registerWrapper; | |
5546 var wrap = scope.wrap; | |
5547 | |
5548 var OriginalSVGElementInstance = window.SVGElementInstance; | |
5549 if (!OriginalSVGElementInstance) | |
5550 return; | |
5551 | |
5552 function SVGElementInstance(impl) { | |
5553 EventTarget.call(this, impl); | |
5554 } | |
5555 | |
5556 SVGElementInstance.prototype = Object.create(EventTarget.prototype); | |
5557 mixin(SVGElementInstance.prototype, { | |
5558 /** @type {SVGElement} */ | |
5559 get correspondingElement() { | |
5560 return wrap(this.impl.correspondingElement); | |
5561 }, | |
5562 | |
5563 /** @type {SVGUseElement} */ | |
5564 get correspondingUseElement() { | |
5565 return wrap(this.impl.correspondingUseElement); | |
5566 }, | |
5567 | |
5568 /** @type {SVGElementInstance} */ | |
5569 get parentNode() { | |
5570 return wrap(this.impl.parentNode); | |
5571 }, | |
5572 | |
5573 /** @type {SVGElementInstanceList} */ | |
5574 get childNodes() { | |
5575 throw new Error('Not implemented'); | |
5576 }, | |
5577 | |
5578 /** @type {SVGElementInstance} */ | |
5579 get firstChild() { | |
5580 return wrap(this.impl.firstChild); | |
5581 }, | |
5582 | |
5583 /** @type {SVGElementInstance} */ | |
5584 get lastChild() { | |
5585 return wrap(this.impl.lastChild); | |
5586 }, | |
5587 | |
5588 /** @type {SVGElementInstance} */ | |
5589 get previousSibling() { | |
5590 return wrap(this.impl.previousSibling); | |
5591 }, | |
5592 | |
5593 /** @type {SVGElementInstance} */ | |
5594 get nextSibling() { | |
5595 return wrap(this.impl.nextSibling); | |
5596 } | |
5597 }); | |
5598 | |
5599 registerWrapper(OriginalSVGElementInstance, SVGElementInstance); | |
5600 | |
5601 scope.wrappers.SVGElementInstance = SVGElementInstance; | |
5602 })(window.ShadowDOMPolyfill); | |
5603 | |
5604 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5605 // Use of this source code is goverened by a BSD-style | |
5606 // license that can be found in the LICENSE file. | |
5607 | |
5608 (function(scope) { | |
5609 'use strict'; | |
5610 | |
5611 var mixin = scope.mixin; | |
5612 var registerWrapper = scope.registerWrapper; | |
5613 var unwrap = scope.unwrap; | |
5614 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
5615 var wrap = scope.wrap; | |
5616 | |
5617 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; | |
5618 | |
5619 function CanvasRenderingContext2D(impl) { | |
5620 this.impl = impl; | |
5621 } | |
5622 | |
5623 mixin(CanvasRenderingContext2D.prototype, { | |
5624 get canvas() { | |
5625 return wrap(this.impl.canvas); | |
5626 }, | |
5627 | |
5628 drawImage: function() { | |
5629 arguments[0] = unwrapIfNeeded(arguments[0]); | |
5630 this.impl.drawImage.apply(this.impl, arguments); | |
5631 }, | |
5632 | |
5633 createPattern: function() { | |
5634 arguments[0] = unwrap(arguments[0]); | |
5635 return this.impl.createPattern.apply(this.impl, arguments); | |
5636 } | |
5637 }); | |
5638 | |
5639 registerWrapper(OriginalCanvasRenderingContext2D, CanvasRenderingContext2D, | |
5640 document.createElement('canvas').getContext('2d')); | |
5641 | |
5642 scope.wrappers.CanvasRenderingContext2D = CanvasRenderingContext2D; | |
5643 })(window.ShadowDOMPolyfill); | |
5644 | |
5645 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5646 // Use of this source code is goverened by a BSD-style | |
5647 // license that can be found in the LICENSE file. | |
5648 | |
5649 (function(scope) { | |
5650 'use strict'; | |
5651 | |
5652 var mixin = scope.mixin; | |
5653 var registerWrapper = scope.registerWrapper; | |
5654 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
5655 var wrap = scope.wrap; | |
5656 | |
5657 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; | |
5658 | |
5659 // IE10 does not have WebGL. | |
5660 if (!OriginalWebGLRenderingContext) | |
5661 return; | |
5662 | |
5663 function WebGLRenderingContext(impl) { | |
5664 this.impl = impl; | |
5665 } | |
5666 | |
5667 mixin(WebGLRenderingContext.prototype, { | |
5668 get canvas() { | |
5669 return wrap(this.impl.canvas); | |
5670 }, | |
5671 | |
5672 texImage2D: function() { | |
5673 arguments[5] = unwrapIfNeeded(arguments[5]); | |
5674 this.impl.texImage2D.apply(this.impl, arguments); | |
5675 }, | |
5676 | |
5677 texSubImage2D: function() { | |
5678 arguments[6] = unwrapIfNeeded(arguments[6]); | |
5679 this.impl.texSubImage2D.apply(this.impl, arguments); | |
5680 } | |
5681 }); | |
5682 | |
5683 // Blink/WebKit has broken DOM bindings. Usually we would create an instance | |
5684 // of the object and pass it into registerWrapper as a "blueprint" but | |
5685 // creating WebGL contexts is expensive and might fail so we use a dummy | |
5686 // object with dummy instance properties for these broken browsers. | |
5687 var instanceProperties = /WebKit/.test(navigator.userAgent) ? | |
5688 {drawingBufferHeight: null, drawingBufferWidth: null} : {}; | |
5689 | |
5690 registerWrapper(OriginalWebGLRenderingContext, WebGLRenderingContext, | |
5691 instanceProperties); | |
5692 | |
5693 scope.wrappers.WebGLRenderingContext = WebGLRenderingContext; | |
5694 })(window.ShadowDOMPolyfill); | |
5695 | |
5696 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5697 // Use of this source code is goverened by a BSD-style | |
5698 // license that can be found in the LICENSE file. | |
5699 | |
5700 (function(scope) { | |
5701 'use strict'; | |
5702 | |
5703 var registerWrapper = scope.registerWrapper; | |
5704 var unwrap = scope.unwrap; | |
5705 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
5706 var wrap = scope.wrap; | |
5707 | |
5708 var OriginalRange = window.Range; | |
5709 | |
5710 function Range(impl) { | |
5711 this.impl = impl; | |
5712 } | |
5713 Range.prototype = { | |
5714 get startContainer() { | |
5715 return wrap(this.impl.startContainer); | |
5716 }, | |
5717 get endContainer() { | |
5718 return wrap(this.impl.endContainer); | |
5719 }, | |
5720 get commonAncestorContainer() { | |
5721 return wrap(this.impl.commonAncestorContainer); | |
5722 }, | |
5723 setStart: function(refNode,offset) { | |
5724 this.impl.setStart(unwrapIfNeeded(refNode), offset); | |
5725 }, | |
5726 setEnd: function(refNode,offset) { | |
5727 this.impl.setEnd(unwrapIfNeeded(refNode), offset); | |
5728 }, | |
5729 setStartBefore: function(refNode) { | |
5730 this.impl.setStartBefore(unwrapIfNeeded(refNode)); | |
5731 }, | |
5732 setStartAfter: function(refNode) { | |
5733 this.impl.setStartAfter(unwrapIfNeeded(refNode)); | |
5734 }, | |
5735 setEndBefore: function(refNode) { | |
5736 this.impl.setEndBefore(unwrapIfNeeded(refNode)); | |
5737 }, | |
5738 setEndAfter: function(refNode) { | |
5739 this.impl.setEndAfter(unwrapIfNeeded(refNode)); | |
5740 }, | |
5741 selectNode: function(refNode) { | |
5742 this.impl.selectNode(unwrapIfNeeded(refNode)); | |
5743 }, | |
5744 selectNodeContents: function(refNode) { | |
5745 this.impl.selectNodeContents(unwrapIfNeeded(refNode)); | |
5746 }, | |
5747 compareBoundaryPoints: function(how, sourceRange) { | |
5748 return this.impl.compareBoundaryPoints(how, unwrap(sourceRange)); | |
5749 }, | |
5750 extractContents: function() { | |
5751 return wrap(this.impl.extractContents()); | |
5752 }, | |
5753 cloneContents: function() { | |
5754 return wrap(this.impl.cloneContents()); | |
5755 }, | |
5756 insertNode: function(node) { | |
5757 this.impl.insertNode(unwrapIfNeeded(node)); | |
5758 }, | |
5759 surroundContents: function(newParent) { | |
5760 this.impl.surroundContents(unwrapIfNeeded(newParent)); | |
5761 }, | |
5762 cloneRange: function() { | |
5763 return wrap(this.impl.cloneRange()); | |
5764 }, | |
5765 isPointInRange: function(node, offset) { | |
5766 return this.impl.isPointInRange(unwrapIfNeeded(node), offset); | |
5767 }, | |
5768 comparePoint: function(node, offset) { | |
5769 return this.impl.comparePoint(unwrapIfNeeded(node), offset); | |
5770 }, | |
5771 intersectsNode: function(node) { | |
5772 return this.impl.intersectsNode(unwrapIfNeeded(node)); | |
5773 }, | |
5774 toString: function() { | |
5775 return this.impl.toString(); | |
5776 } | |
5777 }; | |
5778 | |
5779 // IE9 does not have createContextualFragment. | |
5780 if (OriginalRange.prototype.createContextualFragment) { | |
5781 Range.prototype.createContextualFragment = function(html) { | |
5782 return wrap(this.impl.createContextualFragment(html)); | |
5783 }; | |
5784 } | |
5785 | |
5786 registerWrapper(window.Range, Range, document.createRange()); | |
5787 | |
5788 scope.wrappers.Range = Range; | |
5789 | |
5790 })(window.ShadowDOMPolyfill); | |
5791 | |
5792 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5793 // Use of this source code is goverened by a BSD-style | |
5794 // license that can be found in the LICENSE file. | |
5795 | |
5796 (function(scope) { | |
5797 'use strict'; | |
5798 | |
5799 var GetElementsByInterface = scope.GetElementsByInterface; | |
5800 var ParentNodeInterface = scope.ParentNodeInterface; | |
5801 var SelectorsInterface = scope.SelectorsInterface; | |
5802 var mixin = scope.mixin; | |
5803 var registerObject = scope.registerObject; | |
5804 | |
5805 var DocumentFragment = registerObject(document.createDocumentFragment()); | |
5806 mixin(DocumentFragment.prototype, ParentNodeInterface); | |
5807 mixin(DocumentFragment.prototype, SelectorsInterface); | |
5808 mixin(DocumentFragment.prototype, GetElementsByInterface); | |
5809 | |
5810 var Comment = registerObject(document.createComment('')); | |
5811 | |
5812 scope.wrappers.Comment = Comment; | |
5813 scope.wrappers.DocumentFragment = DocumentFragment; | |
5814 | |
5815 })(window.ShadowDOMPolyfill); | |
5816 | |
5817 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5818 // Use of this source code is goverened by a BSD-style | |
5819 // license that can be found in the LICENSE file. | |
5820 | |
5821 (function(scope) { | |
5822 'use strict'; | |
5823 | |
5824 var DocumentFragment = scope.wrappers.DocumentFragment; | |
5825 var TreeScope = scope.TreeScope; | |
5826 var elementFromPoint = scope.elementFromPoint; | |
5827 var getInnerHTML = scope.getInnerHTML; | |
5828 var getTreeScope = scope.getTreeScope; | |
5829 var mixin = scope.mixin; | |
5830 var rewrap = scope.rewrap; | |
5831 var setInnerHTML = scope.setInnerHTML; | |
5832 var unwrap = scope.unwrap; | |
5833 | |
5834 var shadowHostTable = new WeakMap(); | |
5835 var nextOlderShadowTreeTable = new WeakMap(); | |
5836 | |
5837 var spaceCharRe = /[ \t\n\r\f]/; | |
5838 | |
5839 function ShadowRoot(hostWrapper) { | |
5840 var node = unwrap(hostWrapper.impl.ownerDocument.createDocumentFragment()); | |
5841 DocumentFragment.call(this, node); | |
5842 | |
5843 // createDocumentFragment associates the node with a wrapper | |
5844 // DocumentFragment instance. Override that. | |
5845 rewrap(node, this); | |
5846 | |
5847 this.treeScope_ = new TreeScope(this, getTreeScope(hostWrapper)); | |
5848 | |
5849 var oldShadowRoot = hostWrapper.shadowRoot; | |
5850 nextOlderShadowTreeTable.set(this, oldShadowRoot); | |
5851 | |
5852 shadowHostTable.set(this, hostWrapper); | |
5853 } | |
5854 ShadowRoot.prototype = Object.create(DocumentFragment.prototype); | |
5855 mixin(ShadowRoot.prototype, { | |
5856 get innerHTML() { | |
5857 return getInnerHTML(this); | |
5858 }, | |
5859 set innerHTML(value) { | |
5860 setInnerHTML(this, value); | |
5861 this.invalidateShadowRenderer(); | |
5862 }, | |
5863 | |
5864 get olderShadowRoot() { | |
5865 return nextOlderShadowTreeTable.get(this) || null; | |
5866 }, | |
5867 | |
5868 get host() { | |
5869 return shadowHostTable.get(this) || null; | |
5870 }, | |
5871 | |
5872 invalidateShadowRenderer: function() { | |
5873 return shadowHostTable.get(this).invalidateShadowRenderer(); | |
5874 }, | |
5875 | |
5876 elementFromPoint: function(x, y) { | |
5877 return elementFromPoint(this, this.ownerDocument, x, y); | |
5878 }, | |
5879 | |
5880 getElementById: function(id) { | |
5881 if (spaceCharRe.test(id)) | |
5882 return null; | |
5883 return this.querySelector('[id="' + id + '"]'); | |
5884 } | |
5885 }); | |
5886 | |
5887 scope.wrappers.ShadowRoot = ShadowRoot; | |
5888 | |
5889 })(window.ShadowDOMPolyfill); | |
5890 | |
5891 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5892 // Use of this source code is governed by a BSD-style | |
5893 // license that can be found in the LICENSE file. | |
5894 | |
5895 (function(scope) { | |
5896 'use strict'; | |
5897 | |
5898 var Element = scope.wrappers.Element; | |
5899 var HTMLContentElement = scope.wrappers.HTMLContentElement; | |
5900 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; | |
5901 var Node = scope.wrappers.Node; | |
5902 var ShadowRoot = scope.wrappers.ShadowRoot; | |
5903 var assert = scope.assert; | |
5904 var getTreeScope = scope.getTreeScope; | |
5905 var mixin = scope.mixin; | |
5906 var oneOf = scope.oneOf; | |
5907 var unwrap = scope.unwrap; | |
5908 var wrap = scope.wrap; | |
5909 | |
5910 /** | |
5911 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. | |
5912 * Up means parentNode | |
5913 * Sideways means previous and next sibling. | |
5914 * @param {!Node} wrapper | |
5915 */ | |
5916 function updateWrapperUpAndSideways(wrapper) { | |
5917 wrapper.previousSibling_ = wrapper.previousSibling; | |
5918 wrapper.nextSibling_ = wrapper.nextSibling; | |
5919 wrapper.parentNode_ = wrapper.parentNode; | |
5920 } | |
5921 | |
5922 /** | |
5923 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. | |
5924 * Down means first and last child | |
5925 * @param {!Node} wrapper | |
5926 */ | |
5927 function updateWrapperDown(wrapper) { | |
5928 wrapper.firstChild_ = wrapper.firstChild; | |
5929 wrapper.lastChild_ = wrapper.lastChild; | |
5930 } | |
5931 | |
5932 function updateAllChildNodes(parentNodeWrapper) { | |
5933 assert(parentNodeWrapper instanceof Node); | |
5934 for (var childWrapper = parentNodeWrapper.firstChild; | |
5935 childWrapper; | |
5936 childWrapper = childWrapper.nextSibling) { | |
5937 updateWrapperUpAndSideways(childWrapper); | |
5938 } | |
5939 updateWrapperDown(parentNodeWrapper); | |
5940 } | |
5941 | |
5942 function insertBefore(parentNodeWrapper, newChildWrapper, refChildWrapper) { | |
5943 var parentNode = unwrap(parentNodeWrapper); | |
5944 var newChild = unwrap(newChildWrapper); | |
5945 var refChild = refChildWrapper ? unwrap(refChildWrapper) : null; | |
5946 | |
5947 remove(newChildWrapper); | |
5948 updateWrapperUpAndSideways(newChildWrapper); | |
5949 | |
5950 if (!refChildWrapper) { | |
5951 parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild; | |
5952 if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild) | |
5953 parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild; | |
5954 | |
5955 var lastChildWrapper = wrap(parentNode.lastChild); | |
5956 if (lastChildWrapper) | |
5957 lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling; | |
5958 } else { | |
5959 if (parentNodeWrapper.firstChild === refChildWrapper) | |
5960 parentNodeWrapper.firstChild_ = refChildWrapper; | |
5961 | |
5962 refChildWrapper.previousSibling_ = refChildWrapper.previousSibling; | |
5963 } | |
5964 | |
5965 parentNode.insertBefore(newChild, refChild); | |
5966 } | |
5967 | |
5968 function remove(nodeWrapper) { | |
5969 var node = unwrap(nodeWrapper) | |
5970 var parentNode = node.parentNode; | |
5971 if (!parentNode) | |
5972 return; | |
5973 | |
5974 var parentNodeWrapper = wrap(parentNode); | |
5975 updateWrapperUpAndSideways(nodeWrapper); | |
5976 | |
5977 if (nodeWrapper.previousSibling) | |
5978 nodeWrapper.previousSibling.nextSibling_ = nodeWrapper; | |
5979 if (nodeWrapper.nextSibling) | |
5980 nodeWrapper.nextSibling.previousSibling_ = nodeWrapper; | |
5981 | |
5982 if (parentNodeWrapper.lastChild === nodeWrapper) | |
5983 parentNodeWrapper.lastChild_ = nodeWrapper; | |
5984 if (parentNodeWrapper.firstChild === nodeWrapper) | |
5985 parentNodeWrapper.firstChild_ = nodeWrapper; | |
5986 | |
5987 parentNode.removeChild(node); | |
5988 } | |
5989 | |
5990 var distributedChildNodesTable = new WeakMap(); | |
5991 var eventParentsTable = new WeakMap(); | |
5992 var insertionParentTable = new WeakMap(); | |
5993 var rendererForHostTable = new WeakMap(); | |
5994 | |
5995 function distributeChildToInsertionPoint(child, insertionPoint) { | |
5996 getDistributedChildNodes(insertionPoint).push(child); | |
5997 assignToInsertionPoint(child, insertionPoint); | |
5998 | |
5999 var eventParents = eventParentsTable.get(child); | |
6000 if (!eventParents) | |
6001 eventParentsTable.set(child, eventParents = []); | |
6002 eventParents.push(insertionPoint); | |
6003 } | |
6004 | |
6005 function resetDistributedChildNodes(insertionPoint) { | |
6006 distributedChildNodesTable.set(insertionPoint, []); | |
6007 } | |
6008 | |
6009 function getDistributedChildNodes(insertionPoint) { | |
6010 var rv = distributedChildNodesTable.get(insertionPoint); | |
6011 if (!rv) | |
6012 distributedChildNodesTable.set(insertionPoint, rv = []); | |
6013 return rv; | |
6014 } | |
6015 | |
6016 function getChildNodesSnapshot(node) { | |
6017 var result = [], i = 0; | |
6018 for (var child = node.firstChild; child; child = child.nextSibling) { | |
6019 result[i++] = child; | |
6020 } | |
6021 return result; | |
6022 } | |
6023 | |
6024 /** | |
6025 * Visits all nodes in the tree that fulfils the |predicate|. If the |visitor| | |
6026 * function returns |false| the traversal is aborted. | |
6027 * @param {!Node} tree | |
6028 * @param {function(!Node) : boolean} predicate | |
6029 * @param {function(!Node) : *} visitor | |
6030 */ | |
6031 function visit(tree, predicate, visitor) { | |
6032 // This operates on logical DOM. | |
6033 for (var node = tree.firstChild; node; node = node.nextSibling) { | |
6034 if (predicate(node)) { | |
6035 if (visitor(node) === false) | |
6036 return; | |
6037 } else { | |
6038 visit(node, predicate, visitor); | |
6039 } | |
6040 } | |
6041 } | |
6042 | |
6043 // Matching Insertion Points | |
6044 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#mat
ching-insertion-points | |
6045 | |
6046 // TODO(arv): Verify this... I don't remember why I picked this regexp. | |
6047 var selectorMatchRegExp = /^[*.:#[a-zA-Z_|]/; | |
6048 | |
6049 var allowedPseudoRegExp = new RegExp('^:(' + [ | |
6050 'link', | |
6051 'visited', | |
6052 'target', | |
6053 'enabled', | |
6054 'disabled', | |
6055 'checked', | |
6056 'indeterminate', | |
6057 'nth-child', | |
6058 'nth-last-child', | |
6059 'nth-of-type', | |
6060 'nth-last-of-type', | |
6061 'first-child', | |
6062 'last-child', | |
6063 'first-of-type', | |
6064 'last-of-type', | |
6065 'only-of-type', | |
6066 ].join('|') + ')'); | |
6067 | |
6068 | |
6069 /** | |
6070 * @param {Element} node | |
6071 * @oaram {Element} point The insertion point element. | |
6072 * @return {boolean} Whether the node matches the insertion point. | |
6073 */ | |
6074 function matchesCriteria(node, point) { | |
6075 var select = point.getAttribute('select'); | |
6076 if (!select) | |
6077 return true; | |
6078 | |
6079 // Here we know the select attribute is a non empty string. | |
6080 select = select.trim(); | |
6081 if (!select) | |
6082 return true; | |
6083 | |
6084 if (!(node instanceof Element)) | |
6085 return false; | |
6086 | |
6087 // The native matches function in IE9 does not correctly work with elements | |
6088 // that are not in the document. | |
6089 // TODO(arv): Implement matching in JS. | |
6090 // https://github.com/Polymer/ShadowDOM/issues/361 | |
6091 if (select === '*' || select === node.localName) | |
6092 return true; | |
6093 | |
6094 // TODO(arv): This does not seem right. Need to check for a simple selector. | |
6095 if (!selectorMatchRegExp.test(select)) | |
6096 return false; | |
6097 | |
6098 // TODO(arv): This no longer matches the spec. | |
6099 if (select[0] === ':' && !allowedPseudoRegExp.test(select)) | |
6100 return false; | |
6101 | |
6102 try { | |
6103 return node.matches(select); | |
6104 } catch (ex) { | |
6105 // Invalid selector. | |
6106 return false; | |
6107 } | |
6108 } | |
6109 | |
6110 var request = oneOf(window, [ | |
6111 'requestAnimationFrame', | |
6112 'mozRequestAnimationFrame', | |
6113 'webkitRequestAnimationFrame', | |
6114 'setTimeout' | |
6115 ]); | |
6116 | |
6117 var pendingDirtyRenderers = []; | |
6118 var renderTimer; | |
6119 | |
6120 function renderAllPending() { | |
6121 // TODO(arv): Order these in document order. That way we do not have to | |
6122 // render something twice. | |
6123 for (var i = 0; i < pendingDirtyRenderers.length; i++) { | |
6124 var renderer = pendingDirtyRenderers[i]; | |
6125 var parentRenderer = renderer.parentRenderer; | |
6126 if (parentRenderer && parentRenderer.dirty) | |
6127 continue; | |
6128 renderer.render(); | |
6129 } | |
6130 | |
6131 pendingDirtyRenderers = []; | |
6132 } | |
6133 | |
6134 function handleRequestAnimationFrame() { | |
6135 renderTimer = null; | |
6136 renderAllPending(); | |
6137 } | |
6138 | |
6139 /** | |
6140 * Returns existing shadow renderer for a host or creates it if it is needed. | |
6141 * @params {!Element} host | |
6142 * @return {!ShadowRenderer} | |
6143 */ | |
6144 function getRendererForHost(host) { | |
6145 var renderer = rendererForHostTable.get(host); | |
6146 if (!renderer) { | |
6147 renderer = new ShadowRenderer(host); | |
6148 rendererForHostTable.set(host, renderer); | |
6149 } | |
6150 return renderer; | |
6151 } | |
6152 | |
6153 function getShadowRootAncestor(node) { | |
6154 var root = getTreeScope(node).root; | |
6155 if (root instanceof ShadowRoot) | |
6156 return root; | |
6157 return null; | |
6158 } | |
6159 | |
6160 function getRendererForShadowRoot(shadowRoot) { | |
6161 return getRendererForHost(shadowRoot.host); | |
6162 } | |
6163 | |
6164 var spliceDiff = new ArraySplice(); | |
6165 spliceDiff.equals = function(renderNode, rawNode) { | |
6166 return unwrap(renderNode.node) === rawNode; | |
6167 }; | |
6168 | |
6169 /** | |
6170 * RenderNode is used as an in memory "render tree". When we render the | |
6171 * composed tree we create a tree of RenderNodes, then we diff this against | |
6172 * the real DOM tree and make minimal changes as needed. | |
6173 */ | |
6174 function RenderNode(node) { | |
6175 this.skip = false; | |
6176 this.node = node; | |
6177 this.childNodes = []; | |
6178 } | |
6179 | |
6180 RenderNode.prototype = { | |
6181 append: function(node) { | |
6182 var rv = new RenderNode(node); | |
6183 this.childNodes.push(rv); | |
6184 return rv; | |
6185 }, | |
6186 | |
6187 sync: function(opt_added) { | |
6188 if (this.skip) | |
6189 return; | |
6190 | |
6191 var nodeWrapper = this.node; | |
6192 // plain array of RenderNodes | |
6193 var newChildren = this.childNodes; | |
6194 // plain array of real nodes. | |
6195 var oldChildren = getChildNodesSnapshot(unwrap(nodeWrapper)); | |
6196 var added = opt_added || new WeakMap(); | |
6197 | |
6198 var splices = spliceDiff.calculateSplices(newChildren, oldChildren); | |
6199 | |
6200 var newIndex = 0, oldIndex = 0; | |
6201 var lastIndex = 0; | |
6202 for (var i = 0; i < splices.length; i++) { | |
6203 var splice = splices[i]; | |
6204 for (; lastIndex < splice.index; lastIndex++) { | |
6205 oldIndex++; | |
6206 newChildren[newIndex++].sync(added); | |
6207 } | |
6208 | |
6209 var removedCount = splice.removed.length; | |
6210 for (var j = 0; j < removedCount; j++) { | |
6211 var wrapper = wrap(oldChildren[oldIndex++]); | |
6212 if (!added.get(wrapper)) | |
6213 remove(wrapper); | |
6214 } | |
6215 | |
6216 var addedCount = splice.addedCount; | |
6217 var refNode = oldChildren[oldIndex] && wrap(oldChildren[oldIndex]); | |
6218 for (var j = 0; j < addedCount; j++) { | |
6219 var newChildRenderNode = newChildren[newIndex++]; | |
6220 var newChildWrapper = newChildRenderNode.node; | |
6221 insertBefore(nodeWrapper, newChildWrapper, refNode); | |
6222 | |
6223 // Keep track of added so that we do not remove the node after it | |
6224 // has been added. | |
6225 added.set(newChildWrapper, true); | |
6226 | |
6227 newChildRenderNode.sync(added); | |
6228 } | |
6229 | |
6230 lastIndex += addedCount; | |
6231 } | |
6232 | |
6233 for (var i = lastIndex; i < newChildren.length; i++) { | |
6234 newChildren[i].sync(added); | |
6235 } | |
6236 } | |
6237 }; | |
6238 | |
6239 function ShadowRenderer(host) { | |
6240 this.host = host; | |
6241 this.dirty = false; | |
6242 this.invalidateAttributes(); | |
6243 this.associateNode(host); | |
6244 } | |
6245 | |
6246 ShadowRenderer.prototype = { | |
6247 | |
6248 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#r
endering-shadow-trees | |
6249 render: function(opt_renderNode) { | |
6250 if (!this.dirty) | |
6251 return; | |
6252 | |
6253 this.invalidateAttributes(); | |
6254 this.treeComposition(); | |
6255 | |
6256 var host = this.host; | |
6257 var shadowRoot = host.shadowRoot; | |
6258 | |
6259 this.associateNode(host); | |
6260 var topMostRenderer = !renderNode; | |
6261 var renderNode = opt_renderNode || new RenderNode(host); | |
6262 | |
6263 for (var node = shadowRoot.firstChild; node; node = node.nextSibling) { | |
6264 this.renderNode(shadowRoot, renderNode, node, false); | |
6265 } | |
6266 | |
6267 if (topMostRenderer) | |
6268 renderNode.sync(); | |
6269 | |
6270 this.dirty = false; | |
6271 }, | |
6272 | |
6273 get parentRenderer() { | |
6274 return getTreeScope(this.host).renderer; | |
6275 }, | |
6276 | |
6277 invalidate: function() { | |
6278 if (!this.dirty) { | |
6279 this.dirty = true; | |
6280 pendingDirtyRenderers.push(this); | |
6281 if (renderTimer) | |
6282 return; | |
6283 renderTimer = window[request](handleRequestAnimationFrame, 0); | |
6284 } | |
6285 }, | |
6286 | |
6287 renderNode: function(shadowRoot, renderNode, node, isNested) { | |
6288 if (isShadowHost(node)) { | |
6289 renderNode = renderNode.append(node); | |
6290 var renderer = getRendererForHost(node); | |
6291 renderer.dirty = true; // Need to rerender due to reprojection. | |
6292 renderer.render(renderNode); | |
6293 } else if (isInsertionPoint(node)) { | |
6294 this.renderInsertionPoint(shadowRoot, renderNode, node, isNested); | |
6295 } else if (isShadowInsertionPoint(node)) { | |
6296 this.renderShadowInsertionPoint(shadowRoot, renderNode, node); | |
6297 } else { | |
6298 this.renderAsAnyDomTree(shadowRoot, renderNode, node, isNested); | |
6299 } | |
6300 }, | |
6301 | |
6302 renderAsAnyDomTree: function(shadowRoot, renderNode, node, isNested) { | |
6303 renderNode = renderNode.append(node); | |
6304 | |
6305 if (isShadowHost(node)) { | |
6306 var renderer = getRendererForHost(node); | |
6307 renderNode.skip = !renderer.dirty; | |
6308 renderer.render(renderNode); | |
6309 } else { | |
6310 for (var child = node.firstChild; child; child = child.nextSibling) { | |
6311 this.renderNode(shadowRoot, renderNode, child, isNested); | |
6312 } | |
6313 } | |
6314 }, | |
6315 | |
6316 renderInsertionPoint: function(shadowRoot, renderNode, insertionPoint, | |
6317 isNested) { | |
6318 var distributedChildNodes = getDistributedChildNodes(insertionPoint); | |
6319 if (distributedChildNodes.length) { | |
6320 this.associateNode(insertionPoint); | |
6321 | |
6322 for (var i = 0; i < distributedChildNodes.length; i++) { | |
6323 var child = distributedChildNodes[i]; | |
6324 if (isInsertionPoint(child) && isNested) | |
6325 this.renderInsertionPoint(shadowRoot, renderNode, child, isNested); | |
6326 else | |
6327 this.renderAsAnyDomTree(shadowRoot, renderNode, child, isNested); | |
6328 } | |
6329 } else { | |
6330 this.renderFallbackContent(shadowRoot, renderNode, insertionPoint); | |
6331 } | |
6332 this.associateNode(insertionPoint.parentNode); | |
6333 }, | |
6334 | |
6335 renderShadowInsertionPoint: function(shadowRoot, renderNode, | |
6336 shadowInsertionPoint) { | |
6337 var nextOlderTree = shadowRoot.olderShadowRoot; | |
6338 if (nextOlderTree) { | |
6339 assignToInsertionPoint(nextOlderTree, shadowInsertionPoint); | |
6340 this.associateNode(shadowInsertionPoint.parentNode); | |
6341 for (var node = nextOlderTree.firstChild; | |
6342 node; | |
6343 node = node.nextSibling) { | |
6344 this.renderNode(nextOlderTree, renderNode, node, true); | |
6345 } | |
6346 } else { | |
6347 this.renderFallbackContent(shadowRoot, renderNode, | |
6348 shadowInsertionPoint); | |
6349 } | |
6350 }, | |
6351 | |
6352 renderFallbackContent: function(shadowRoot, renderNode, fallbackHost) { | |
6353 this.associateNode(fallbackHost); | |
6354 this.associateNode(fallbackHost.parentNode); | |
6355 for (var node = fallbackHost.firstChild; node; node = node.nextSibling) { | |
6356 this.renderAsAnyDomTree(shadowRoot, renderNode, node, false); | |
6357 } | |
6358 }, | |
6359 | |
6360 /** | |
6361 * Invalidates the attributes used to keep track of which attributes may | |
6362 * cause the renderer to be invalidated. | |
6363 */ | |
6364 invalidateAttributes: function() { | |
6365 this.attributes = Object.create(null); | |
6366 }, | |
6367 | |
6368 /** | |
6369 * Parses the selector and makes this renderer dependent on the attribute | |
6370 * being used in the selector. | |
6371 * @param {string} selector | |
6372 */ | |
6373 updateDependentAttributes: function(selector) { | |
6374 if (!selector) | |
6375 return; | |
6376 | |
6377 var attributes = this.attributes; | |
6378 | |
6379 // .class | |
6380 if (/\.\w+/.test(selector)) | |
6381 attributes['class'] = true; | |
6382 | |
6383 // #id | |
6384 if (/#\w+/.test(selector)) | |
6385 attributes['id'] = true; | |
6386 | |
6387 selector.replace(/\[\s*([^\s=\|~\]]+)/g, function(_, name) { | |
6388 attributes[name] = true; | |
6389 }); | |
6390 | |
6391 // Pseudo selectors have been removed from the spec. | |
6392 }, | |
6393 | |
6394 dependsOnAttribute: function(name) { | |
6395 return this.attributes[name]; | |
6396 }, | |
6397 | |
6398 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-distribution-algorithm | |
6399 distribute: function(tree, pool) { | |
6400 var self = this; | |
6401 | |
6402 visit(tree, isActiveInsertionPoint, | |
6403 function(insertionPoint) { | |
6404 resetDistributedChildNodes(insertionPoint); | |
6405 self.updateDependentAttributes( | |
6406 insertionPoint.getAttribute('select')); | |
6407 | |
6408 for (var i = 0; i < pool.length; i++) { // 1.2 | |
6409 var node = pool[i]; // 1.2.1 | |
6410 if (node === undefined) // removed | |
6411 continue; | |
6412 if (matchesCriteria(node, insertionPoint)) { // 1.2.2 | |
6413 distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2
.1 | |
6414 pool[i] = undefined; // 1.2.2.2 | |
6415 } | |
6416 } | |
6417 }); | |
6418 }, | |
6419 | |
6420 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-tree-composition | |
6421 treeComposition: function () { | |
6422 var shadowHost = this.host; | |
6423 var tree = shadowHost.shadowRoot; // 1. | |
6424 var pool = []; // 2. | |
6425 | |
6426 for (var child = shadowHost.firstChild; | |
6427 child; | |
6428 child = child.nextSibling) { // 3. | |
6429 if (isInsertionPoint(child)) { // 3.2. | |
6430 var reprojected = getDistributedChildNodes(child); // 3.2.1. | |
6431 // if reprojected is undef... reset it? | |
6432 if (!reprojected || !reprojected.length) // 3.2.2. | |
6433 reprojected = getChildNodesSnapshot(child); | |
6434 pool.push.apply(pool, reprojected); // 3.2.3. | |
6435 } else { | |
6436 pool.push(child); // 3.3. | |
6437 } | |
6438 } | |
6439 | |
6440 var shadowInsertionPoint, point; | |
6441 while (tree) { // 4. | |
6442 // 4.1. | |
6443 shadowInsertionPoint = undefined; // Reset every iteration. | |
6444 visit(tree, isActiveShadowInsertionPoint, function(point) { | |
6445 shadowInsertionPoint = point; | |
6446 return false; | |
6447 }); | |
6448 point = shadowInsertionPoint; | |
6449 | |
6450 this.distribute(tree, pool); // 4.2. | |
6451 if (point) { // 4.3. | |
6452 var nextOlderTree = tree.olderShadowRoot; // 4.3.1. | |
6453 if (!nextOlderTree) { | |
6454 break; // 4.3.1.1. | |
6455 } else { | |
6456 tree = nextOlderTree; // 4.3.2.2. | |
6457 assignToInsertionPoint(tree, point); // 4.3.2.2. | |
6458 continue; // 4.3.2.3. | |
6459 } | |
6460 } else { | |
6461 break; // 4.4. | |
6462 } | |
6463 } | |
6464 }, | |
6465 | |
6466 associateNode: function(node) { | |
6467 node.impl.polymerShadowRenderer_ = this; | |
6468 } | |
6469 }; | |
6470 | |
6471 function isInsertionPoint(node) { | |
6472 // Should this include <shadow>? | |
6473 return node instanceof HTMLContentElement; | |
6474 } | |
6475 | |
6476 function isActiveInsertionPoint(node) { | |
6477 // <content> inside another <content> or <shadow> is considered inactive. | |
6478 return node instanceof HTMLContentElement; | |
6479 } | |
6480 | |
6481 function isShadowInsertionPoint(node) { | |
6482 return node instanceof HTMLShadowElement; | |
6483 } | |
6484 | |
6485 function isActiveShadowInsertionPoint(node) { | |
6486 // <shadow> inside another <content> or <shadow> is considered inactive. | |
6487 return node instanceof HTMLShadowElement; | |
6488 } | |
6489 | |
6490 function isShadowHost(shadowHost) { | |
6491 return shadowHost.shadowRoot; | |
6492 } | |
6493 | |
6494 function getShadowTrees(host) { | |
6495 var trees = []; | |
6496 | |
6497 for (var tree = host.shadowRoot; tree; tree = tree.olderShadowRoot) { | |
6498 trees.push(tree); | |
6499 } | |
6500 return trees; | |
6501 } | |
6502 | |
6503 function assignToInsertionPoint(tree, point) { | |
6504 insertionParentTable.set(tree, point); | |
6505 } | |
6506 | |
6507 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ren
dering-shadow-trees | |
6508 function render(host) { | |
6509 new ShadowRenderer(host).render(); | |
6510 }; | |
6511 | |
6512 // Need to rerender shadow host when: | |
6513 // | |
6514 // - a direct child to the ShadowRoot is added or removed | |
6515 // - a direct child to the host is added or removed | |
6516 // - a new shadow root is created | |
6517 // - a direct child to a content/shadow element is added or removed | |
6518 // - a sibling to a content/shadow element is added or removed | |
6519 // - content[select] is changed | |
6520 // - an attribute in a direct child to a host is modified | |
6521 | |
6522 /** | |
6523 * This gets called when a node was added or removed to it. | |
6524 */ | |
6525 Node.prototype.invalidateShadowRenderer = function(force) { | |
6526 var renderer = this.impl.polymerShadowRenderer_; | |
6527 if (renderer) { | |
6528 renderer.invalidate(); | |
6529 return true; | |
6530 } | |
6531 | |
6532 return false; | |
6533 }; | |
6534 | |
6535 HTMLContentElement.prototype.getDistributedNodes = function() { | |
6536 // TODO(arv): We should only rerender the dirty ancestor renderers (from | |
6537 // the root and down). | |
6538 renderAllPending(); | |
6539 return getDistributedChildNodes(this); | |
6540 }; | |
6541 | |
6542 HTMLShadowElement.prototype.nodeIsInserted_ = | |
6543 HTMLContentElement.prototype.nodeIsInserted_ = function() { | |
6544 // Invalidate old renderer if any. | |
6545 this.invalidateShadowRenderer(); | |
6546 | |
6547 var shadowRoot = getShadowRootAncestor(this); | |
6548 var renderer; | |
6549 if (shadowRoot) | |
6550 renderer = getRendererForShadowRoot(shadowRoot); | |
6551 this.impl.polymerShadowRenderer_ = renderer; | |
6552 if (renderer) | |
6553 renderer.invalidate(); | |
6554 }; | |
6555 | |
6556 scope.eventParentsTable = eventParentsTable; | |
6557 scope.getRendererForHost = getRendererForHost; | |
6558 scope.getShadowTrees = getShadowTrees; | |
6559 scope.insertionParentTable = insertionParentTable; | |
6560 scope.renderAllPending = renderAllPending; | |
6561 | |
6562 // Exposed for testing | |
6563 scope.visual = { | |
6564 insertBefore: insertBefore, | |
6565 remove: remove, | |
6566 }; | |
6567 | |
6568 })(window.ShadowDOMPolyfill); | |
6569 | |
6570 // Copyright 2013 The Polymer Authors. All rights reserved. | |
6571 // Use of this source code is goverened by a BSD-style | |
6572 // license that can be found in the LICENSE file. | |
6573 | |
6574 (function(scope) { | |
6575 'use strict'; | |
6576 | |
6577 var HTMLElement = scope.wrappers.HTMLElement; | |
6578 var assert = scope.assert; | |
6579 var mixin = scope.mixin; | |
6580 var registerWrapper = scope.registerWrapper; | |
6581 var unwrap = scope.unwrap; | |
6582 var wrap = scope.wrap; | |
6583 | |
6584 var elementsWithFormProperty = [ | |
6585 'HTMLButtonElement', | |
6586 'HTMLFieldSetElement', | |
6587 'HTMLInputElement', | |
6588 'HTMLKeygenElement', | |
6589 'HTMLLabelElement', | |
6590 'HTMLLegendElement', | |
6591 'HTMLObjectElement', | |
6592 // HTMLOptionElement is handled in HTMLOptionElement.js | |
6593 'HTMLOutputElement', | |
6594 // HTMLSelectElement is handled in HTMLSelectElement.js | |
6595 'HTMLTextAreaElement', | |
6596 ]; | |
6597 | |
6598 function createWrapperConstructor(name) { | |
6599 if (!window[name]) | |
6600 return; | |
6601 | |
6602 // Ensure we are not overriding an already existing constructor. | |
6603 assert(!scope.wrappers[name]); | |
6604 | |
6605 var GeneratedWrapper = function(node) { | |
6606 // At this point all of them extend HTMLElement. | |
6607 HTMLElement.call(this, node); | |
6608 } | |
6609 GeneratedWrapper.prototype = Object.create(HTMLElement.prototype); | |
6610 mixin(GeneratedWrapper.prototype, { | |
6611 get form() { | |
6612 return wrap(unwrap(this).form); | |
6613 }, | |
6614 }); | |
6615 | |
6616 registerWrapper(window[name], GeneratedWrapper, | |
6617 document.createElement(name.slice(4, -7))); | |
6618 scope.wrappers[name] = GeneratedWrapper; | |
6619 } | |
6620 | |
6621 elementsWithFormProperty.forEach(createWrapperConstructor); | |
6622 | |
6623 })(window.ShadowDOMPolyfill); | |
6624 | |
6625 // Copyright 2014 The Polymer Authors. All rights reserved. | |
6626 // Use of this source code is goverened by a BSD-style | |
6627 // license that can be found in the LICENSE file. | |
6628 | |
6629 (function(scope) { | |
6630 'use strict'; | |
6631 | |
6632 var registerWrapper = scope.registerWrapper; | |
6633 var unwrap = scope.unwrap; | |
6634 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
6635 var wrap = scope.wrap; | |
6636 | |
6637 var OriginalSelection = window.Selection; | |
6638 | |
6639 function Selection(impl) { | |
6640 this.impl = impl; | |
6641 } | |
6642 Selection.prototype = { | |
6643 get anchorNode() { | |
6644 return wrap(this.impl.anchorNode); | |
6645 }, | |
6646 get focusNode() { | |
6647 return wrap(this.impl.focusNode); | |
6648 }, | |
6649 addRange: function(range) { | |
6650 this.impl.addRange(unwrap(range)); | |
6651 }, | |
6652 collapse: function(node, index) { | |
6653 this.impl.collapse(unwrapIfNeeded(node), index); | |
6654 }, | |
6655 containsNode: function(node, allowPartial) { | |
6656 return this.impl.containsNode(unwrapIfNeeded(node), allowPartial); | |
6657 }, | |
6658 extend: function(node, offset) { | |
6659 this.impl.extend(unwrapIfNeeded(node), offset); | |
6660 }, | |
6661 getRangeAt: function(index) { | |
6662 return wrap(this.impl.getRangeAt(index)); | |
6663 }, | |
6664 removeRange: function(range) { | |
6665 this.impl.removeRange(unwrap(range)); | |
6666 }, | |
6667 selectAllChildren: function(node) { | |
6668 this.impl.selectAllChildren(unwrapIfNeeded(node)); | |
6669 }, | |
6670 toString: function() { | |
6671 return this.impl.toString(); | |
6672 } | |
6673 }; | |
6674 | |
6675 // WebKit extensions. Not implemented. | |
6676 // readonly attribute Node baseNode; | |
6677 // readonly attribute long baseOffset; | |
6678 // readonly attribute Node extentNode; | |
6679 // readonly attribute long extentOffset; | |
6680 // [RaisesException] void setBaseAndExtent([Default=Undefined] optional Node b
aseNode, | |
6681 // [Default=Undefined] optional long baseOffset, | |
6682 // [Default=Undefined] optional Node extentNode, | |
6683 // [Default=Undefined] optional long extentOffset); | |
6684 // [RaisesException, ImplementedAs=collapse] void setPosition([Default=Undefin
ed] optional Node node, | |
6685 // [Default=Undefined] optional long offset); | |
6686 | |
6687 registerWrapper(window.Selection, Selection, window.getSelection()); | |
6688 | |
6689 scope.wrappers.Selection = Selection; | |
6690 | |
6691 })(window.ShadowDOMPolyfill); | |
6692 | |
6693 // Copyright 2013 The Polymer Authors. All rights reserved. | |
6694 // Use of this source code is goverened by a BSD-style | |
6695 // license that can be found in the LICENSE file. | |
6696 | |
6697 (function(scope) { | |
6698 'use strict'; | |
6699 | |
6700 var GetElementsByInterface = scope.GetElementsByInterface; | |
6701 var Node = scope.wrappers.Node; | |
6702 var ParentNodeInterface = scope.ParentNodeInterface; | |
6703 var Selection = scope.wrappers.Selection; | |
6704 var SelectorsInterface = scope.SelectorsInterface; | |
6705 var ShadowRoot = scope.wrappers.ShadowRoot; | |
6706 var TreeScope = scope.TreeScope; | |
6707 var cloneNode = scope.cloneNode; | |
6708 var defineWrapGetter = scope.defineWrapGetter; | |
6709 var elementFromPoint = scope.elementFromPoint; | |
6710 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; | |
6711 var matchesNames = scope.matchesNames; | |
6712 var mixin = scope.mixin; | |
6713 var registerWrapper = scope.registerWrapper; | |
6714 var renderAllPending = scope.renderAllPending; | |
6715 var rewrap = scope.rewrap; | |
6716 var unwrap = scope.unwrap; | |
6717 var wrap = scope.wrap; | |
6718 var wrapEventTargetMethods = scope.wrapEventTargetMethods; | |
6719 var wrapNodeList = scope.wrapNodeList; | |
6720 | |
6721 var implementationTable = new WeakMap(); | |
6722 | |
6723 function Document(node) { | |
6724 Node.call(this, node); | |
6725 this.treeScope_ = new TreeScope(this, null); | |
6726 } | |
6727 Document.prototype = Object.create(Node.prototype); | |
6728 | |
6729 defineWrapGetter(Document, 'documentElement'); | |
6730 | |
6731 // Conceptually both body and head can be in a shadow but suporting that seems | |
6732 // overkill at this point. | |
6733 defineWrapGetter(Document, 'body'); | |
6734 defineWrapGetter(Document, 'head'); | |
6735 | |
6736 // document cannot be overridden so we override a bunch of its methods | |
6737 // directly on the instance. | |
6738 | |
6739 function wrapMethod(name) { | |
6740 var original = document[name]; | |
6741 Document.prototype[name] = function() { | |
6742 return wrap(original.apply(this.impl, arguments)); | |
6743 }; | |
6744 } | |
6745 | |
6746 [ | |
6747 'createComment', | |
6748 'createDocumentFragment', | |
6749 'createElement', | |
6750 'createElementNS', | |
6751 'createEvent', | |
6752 'createEventNS', | |
6753 'createRange', | |
6754 'createTextNode', | |
6755 'getElementById' | |
6756 ].forEach(wrapMethod); | |
6757 | |
6758 var originalAdoptNode = document.adoptNode; | |
6759 | |
6760 function adoptNodeNoRemove(node, doc) { | |
6761 originalAdoptNode.call(doc.impl, unwrap(node)); | |
6762 adoptSubtree(node, doc); | |
6763 } | |
6764 | |
6765 function adoptSubtree(node, doc) { | |
6766 if (node.shadowRoot) | |
6767 doc.adoptNode(node.shadowRoot); | |
6768 if (node instanceof ShadowRoot) | |
6769 adoptOlderShadowRoots(node, doc); | |
6770 for (var child = node.firstChild; child; child = child.nextSibling) { | |
6771 adoptSubtree(child, doc); | |
6772 } | |
6773 } | |
6774 | |
6775 function adoptOlderShadowRoots(shadowRoot, doc) { | |
6776 var oldShadowRoot = shadowRoot.olderShadowRoot; | |
6777 if (oldShadowRoot) | |
6778 doc.adoptNode(oldShadowRoot); | |
6779 } | |
6780 | |
6781 var originalGetSelection = document.getSelection; | |
6782 | |
6783 mixin(Document.prototype, { | |
6784 adoptNode: function(node) { | |
6785 if (node.parentNode) | |
6786 node.parentNode.removeChild(node); | |
6787 adoptNodeNoRemove(node, this); | |
6788 return node; | |
6789 }, | |
6790 elementFromPoint: function(x, y) { | |
6791 return elementFromPoint(this, this, x, y); | |
6792 }, | |
6793 importNode: function(node, deep) { | |
6794 return cloneNode(node, deep, this.impl); | |
6795 }, | |
6796 getSelection: function() { | |
6797 renderAllPending(); | |
6798 return new Selection(originalGetSelection.call(unwrap(this))); | |
6799 } | |
6800 }); | |
6801 | |
6802 if (document.registerElement) { | |
6803 var originalRegisterElement = document.registerElement; | |
6804 Document.prototype.registerElement = function(tagName, object) { | |
6805 var prototype, extendsOption; | |
6806 if (object !== undefined) { | |
6807 prototype = object.prototype; | |
6808 extendsOption = object.extends; | |
6809 } | |
6810 | |
6811 if (!prototype) | |
6812 prototype = Object.create(HTMLElement.prototype); | |
6813 | |
6814 | |
6815 // If we already used the object as a prototype for another custom | |
6816 // element. | |
6817 if (scope.nativePrototypeTable.get(prototype)) { | |
6818 // TODO(arv): DOMException | |
6819 throw new Error('NotSupportedError'); | |
6820 } | |
6821 | |
6822 // Find first object on the prototype chain that already have a native | |
6823 // prototype. Keep track of all the objects before that so we can create | |
6824 // a similar structure for the native case. | |
6825 var proto = Object.getPrototypeOf(prototype); | |
6826 var nativePrototype; | |
6827 var prototypes = []; | |
6828 while (proto) { | |
6829 nativePrototype = scope.nativePrototypeTable.get(proto); | |
6830 if (nativePrototype) | |
6831 break; | |
6832 prototypes.push(proto); | |
6833 proto = Object.getPrototypeOf(proto); | |
6834 } | |
6835 | |
6836 if (!nativePrototype) { | |
6837 // TODO(arv): DOMException | |
6838 throw new Error('NotSupportedError'); | |
6839 } | |
6840 | |
6841 // This works by creating a new prototype object that is empty, but has | |
6842 // the native prototype as its proto. The original prototype object | |
6843 // passed into register is used as the wrapper prototype. | |
6844 | |
6845 var newPrototype = Object.create(nativePrototype); | |
6846 for (var i = prototypes.length - 1; i >= 0; i--) { | |
6847 newPrototype = Object.create(newPrototype); | |
6848 } | |
6849 | |
6850 // Add callbacks if present. | |
6851 // Names are taken from: | |
6852 // https://code.google.com/p/chromium/codesearch#chromium/src/third_part
y/WebKit/Source/bindings/v8/CustomElementConstructorBuilder.cpp&sq=package:chrom
ium&type=cs&l=156 | |
6853 // and not from the spec since the spec is out of date. | |
6854 [ | |
6855 'createdCallback', | |
6856 'attachedCallback', | |
6857 'detachedCallback', | |
6858 'attributeChangedCallback', | |
6859 ].forEach(function(name) { | |
6860 var f = prototype[name]; | |
6861 if (!f) | |
6862 return; | |
6863 newPrototype[name] = function() { | |
6864 // if this element has been wrapped prior to registration, | |
6865 // the wrapper is stale; in this case rewrap | |
6866 if (!(wrap(this) instanceof CustomElementConstructor)) { | |
6867 rewrap(this); | |
6868 } | |
6869 f.apply(wrap(this), arguments); | |
6870 }; | |
6871 }); | |
6872 | |
6873 var p = {prototype: newPrototype}; | |
6874 if (extendsOption) | |
6875 p.extends = extendsOption; | |
6876 | |
6877 function CustomElementConstructor(node) { | |
6878 if (!node) { | |
6879 if (extendsOption) { | |
6880 return document.createElement(extendsOption, tagName); | |
6881 } else { | |
6882 return document.createElement(tagName); | |
6883 } | |
6884 } | |
6885 this.impl = node; | |
6886 } | |
6887 CustomElementConstructor.prototype = prototype; | |
6888 CustomElementConstructor.prototype.constructor = CustomElementConstructor; | |
6889 | |
6890 scope.constructorTable.set(newPrototype, CustomElementConstructor); | |
6891 scope.nativePrototypeTable.set(prototype, newPrototype); | |
6892 | |
6893 // registration is synchronous so do it last | |
6894 var nativeConstructor = originalRegisterElement.call(unwrap(this), | |
6895 tagName, p); | |
6896 return CustomElementConstructor; | |
6897 }; | |
6898 | |
6899 forwardMethodsToWrapper([ | |
6900 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocume
nt | |
6901 ], [ | |
6902 'registerElement', | |
6903 ]); | |
6904 } | |
6905 | |
6906 // We also override some of the methods on document.body and document.head | |
6907 // for convenience. | |
6908 forwardMethodsToWrapper([ | |
6909 window.HTMLBodyElement, | |
6910 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument | |
6911 window.HTMLHeadElement, | |
6912 window.HTMLHtmlElement, | |
6913 ], [ | |
6914 'appendChild', | |
6915 'compareDocumentPosition', | |
6916 'contains', | |
6917 'getElementsByClassName', | |
6918 'getElementsByTagName', | |
6919 'getElementsByTagNameNS', | |
6920 'insertBefore', | |
6921 'querySelector', | |
6922 'querySelectorAll', | |
6923 'removeChild', | |
6924 'replaceChild', | |
6925 ].concat(matchesNames)); | |
6926 | |
6927 forwardMethodsToWrapper([ | |
6928 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument | |
6929 ], [ | |
6930 'adoptNode', | |
6931 'importNode', | |
6932 'contains', | |
6933 'createComment', | |
6934 'createDocumentFragment', | |
6935 'createElement', | |
6936 'createElementNS', | |
6937 'createEvent', | |
6938 'createEventNS', | |
6939 'createRange', | |
6940 'createTextNode', | |
6941 'elementFromPoint', | |
6942 'getElementById', | |
6943 'getSelection', | |
6944 ]); | |
6945 | |
6946 mixin(Document.prototype, GetElementsByInterface); | |
6947 mixin(Document.prototype, ParentNodeInterface); | |
6948 mixin(Document.prototype, SelectorsInterface); | |
6949 | |
6950 mixin(Document.prototype, { | |
6951 get implementation() { | |
6952 var implementation = implementationTable.get(this); | |
6953 if (implementation) | |
6954 return implementation; | |
6955 implementation = | |
6956 new DOMImplementation(unwrap(this).implementation); | |
6957 implementationTable.set(this, implementation); | |
6958 return implementation; | |
6959 } | |
6960 }); | |
6961 | |
6962 registerWrapper(window.Document, Document, | |
6963 document.implementation.createHTMLDocument('')); | |
6964 | |
6965 // Both WebKit and Gecko uses HTMLDocument for document. HTML5/DOM only has | |
6966 // one Document interface and IE implements the standard correctly. | |
6967 if (window.HTMLDocument) | |
6968 registerWrapper(window.HTMLDocument, Document); | |
6969 | |
6970 wrapEventTargetMethods([ | |
6971 window.HTMLBodyElement, | |
6972 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument | |
6973 window.HTMLHeadElement, | |
6974 ]); | |
6975 | |
6976 function DOMImplementation(impl) { | |
6977 this.impl = impl; | |
6978 } | |
6979 | |
6980 function wrapImplMethod(constructor, name) { | |
6981 var original = document.implementation[name]; | |
6982 constructor.prototype[name] = function() { | |
6983 return wrap(original.apply(this.impl, arguments)); | |
6984 }; | |
6985 } | |
6986 | |
6987 function forwardImplMethod(constructor, name) { | |
6988 var original = document.implementation[name]; | |
6989 constructor.prototype[name] = function() { | |
6990 return original.apply(this.impl, arguments); | |
6991 }; | |
6992 } | |
6993 | |
6994 wrapImplMethod(DOMImplementation, 'createDocumentType'); | |
6995 wrapImplMethod(DOMImplementation, 'createDocument'); | |
6996 wrapImplMethod(DOMImplementation, 'createHTMLDocument'); | |
6997 forwardImplMethod(DOMImplementation, 'hasFeature'); | |
6998 | |
6999 registerWrapper(window.DOMImplementation, DOMImplementation); | |
7000 | |
7001 forwardMethodsToWrapper([ | |
7002 window.DOMImplementation, | |
7003 ], [ | |
7004 'createDocumentType', | |
7005 'createDocument', | |
7006 'createHTMLDocument', | |
7007 'hasFeature', | |
7008 ]); | |
7009 | |
7010 scope.adoptNodeNoRemove = adoptNodeNoRemove; | |
7011 scope.wrappers.DOMImplementation = DOMImplementation; | |
7012 scope.wrappers.Document = Document; | |
7013 | |
7014 })(window.ShadowDOMPolyfill); | |
7015 | |
7016 // Copyright 2013 The Polymer Authors. All rights reserved. | |
7017 // Use of this source code is goverened by a BSD-style | |
7018 // license that can be found in the LICENSE file. | |
7019 | |
7020 (function(scope) { | |
7021 'use strict'; | |
7022 | |
7023 var EventTarget = scope.wrappers.EventTarget; | |
7024 var Selection = scope.wrappers.Selection; | |
7025 var mixin = scope.mixin; | |
7026 var registerWrapper = scope.registerWrapper; | |
7027 var renderAllPending = scope.renderAllPending; | |
7028 var unwrap = scope.unwrap; | |
7029 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
7030 var wrap = scope.wrap; | |
7031 | |
7032 var OriginalWindow = window.Window; | |
7033 var originalGetComputedStyle = window.getComputedStyle; | |
7034 var originalGetSelection = window.getSelection; | |
7035 | |
7036 function Window(impl) { | |
7037 EventTarget.call(this, impl); | |
7038 } | |
7039 Window.prototype = Object.create(EventTarget.prototype); | |
7040 | |
7041 OriginalWindow.prototype.getComputedStyle = function(el, pseudo) { | |
7042 return wrap(this || window).getComputedStyle(unwrapIfNeeded(el), pseudo); | |
7043 }; | |
7044 | |
7045 OriginalWindow.prototype.getSelection = function() { | |
7046 return wrap(this || window).getSelection(); | |
7047 }; | |
7048 | |
7049 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 | |
7050 delete window.getComputedStyle; | |
7051 delete window.getSelection; | |
7052 | |
7053 ['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach( | |
7054 function(name) { | |
7055 OriginalWindow.prototype[name] = function() { | |
7056 var w = wrap(this || window); | |
7057 return w[name].apply(w, arguments); | |
7058 }; | |
7059 | |
7060 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 | |
7061 delete window[name]; | |
7062 }); | |
7063 | |
7064 mixin(Window.prototype, { | |
7065 getComputedStyle: function(el, pseudo) { | |
7066 renderAllPending(); | |
7067 return originalGetComputedStyle.call(unwrap(this), unwrapIfNeeded(el), | |
7068 pseudo); | |
7069 }, | |
7070 getSelection: function() { | |
7071 renderAllPending(); | |
7072 return new Selection(originalGetSelection.call(unwrap(this))); | |
7073 }, | |
7074 }); | |
7075 | |
7076 registerWrapper(OriginalWindow, Window); | |
7077 | |
7078 scope.wrappers.Window = Window; | |
7079 | |
7080 })(window.ShadowDOMPolyfill); | |
7081 | |
7082 /** | |
7083 * Copyright 2014 The Polymer Authors. All rights reserved. | |
7084 * Use of this source code is goverened by a BSD-style | |
7085 * license that can be found in the LICENSE file. | |
7086 */ | |
7087 | |
7088 (function(scope) { | |
7089 'use strict'; | |
7090 | |
7091 var unwrap = scope.unwrap; | |
7092 | |
7093 // DataTransfer (Clipboard in old Blink/WebKit) has a single method that | |
7094 // requires wrapping. Since it is only a method we do not need a real wrapper, | |
7095 // we can just override the method. | |
7096 | |
7097 var OriginalDataTransfer = window.DataTransfer || window.Clipboard; | |
7098 var OriginalDataTransferSetDragImage = | |
7099 OriginalDataTransfer.prototype.setDragImage; | |
7100 | |
7101 OriginalDataTransfer.prototype.setDragImage = function(image, x, y) { | |
7102 OriginalDataTransferSetDragImage.call(this, unwrap(image), x, y); | |
7103 }; | |
7104 | |
7105 })(window.ShadowDOMPolyfill); | |
7106 | |
7107 // Copyright 2013 The Polymer Authors. All rights reserved. | |
7108 // Use of this source code is goverened by a BSD-style | |
7109 // license that can be found in the LICENSE file. | |
7110 | |
7111 (function(scope) { | |
7112 'use strict'; | |
7113 | |
7114 var isWrapperFor = scope.isWrapperFor; | |
7115 | |
7116 // This is a list of the elements we currently override the global constructor | |
7117 // for. | |
7118 var elements = { | |
7119 'a': 'HTMLAnchorElement', | |
7120 // Do not create an applet element by default since it shows a warning in | |
7121 // IE. | |
7122 // https://github.com/Polymer/polymer/issues/217 | |
7123 // 'applet': 'HTMLAppletElement', | |
7124 'area': 'HTMLAreaElement', | |
7125 'audio': 'HTMLAudioElement', | |
7126 'base': 'HTMLBaseElement', | |
7127 'body': 'HTMLBodyElement', | |
7128 'br': 'HTMLBRElement', | |
7129 'button': 'HTMLButtonElement', | |
7130 'canvas': 'HTMLCanvasElement', | |
7131 'caption': 'HTMLTableCaptionElement', | |
7132 'col': 'HTMLTableColElement', | |
7133 // 'command': 'HTMLCommandElement', // Not fully implemented in Gecko. | |
7134 'content': 'HTMLContentElement', | |
7135 'data': 'HTMLDataElement', | |
7136 'datalist': 'HTMLDataListElement', | |
7137 'del': 'HTMLModElement', | |
7138 'dir': 'HTMLDirectoryElement', | |
7139 'div': 'HTMLDivElement', | |
7140 'dl': 'HTMLDListElement', | |
7141 'embed': 'HTMLEmbedElement', | |
7142 'fieldset': 'HTMLFieldSetElement', | |
7143 'font': 'HTMLFontElement', | |
7144 'form': 'HTMLFormElement', | |
7145 'frame': 'HTMLFrameElement', | |
7146 'frameset': 'HTMLFrameSetElement', | |
7147 'h1': 'HTMLHeadingElement', | |
7148 'head': 'HTMLHeadElement', | |
7149 'hr': 'HTMLHRElement', | |
7150 'html': 'HTMLHtmlElement', | |
7151 'iframe': 'HTMLIFrameElement', | |
7152 'img': 'HTMLImageElement', | |
7153 'input': 'HTMLInputElement', | |
7154 'keygen': 'HTMLKeygenElement', | |
7155 'label': 'HTMLLabelElement', | |
7156 'legend': 'HTMLLegendElement', | |
7157 'li': 'HTMLLIElement', | |
7158 'link': 'HTMLLinkElement', | |
7159 'map': 'HTMLMapElement', | |
7160 'marquee': 'HTMLMarqueeElement', | |
7161 'menu': 'HTMLMenuElement', | |
7162 'menuitem': 'HTMLMenuItemElement', | |
7163 'meta': 'HTMLMetaElement', | |
7164 'meter': 'HTMLMeterElement', | |
7165 'object': 'HTMLObjectElement', | |
7166 'ol': 'HTMLOListElement', | |
7167 'optgroup': 'HTMLOptGroupElement', | |
7168 'option': 'HTMLOptionElement', | |
7169 'output': 'HTMLOutputElement', | |
7170 'p': 'HTMLParagraphElement', | |
7171 'param': 'HTMLParamElement', | |
7172 'pre': 'HTMLPreElement', | |
7173 'progress': 'HTMLProgressElement', | |
7174 'q': 'HTMLQuoteElement', | |
7175 'script': 'HTMLScriptElement', | |
7176 'select': 'HTMLSelectElement', | |
7177 'shadow': 'HTMLShadowElement', | |
7178 'source': 'HTMLSourceElement', | |
7179 'span': 'HTMLSpanElement', | |
7180 'style': 'HTMLStyleElement', | |
7181 'table': 'HTMLTableElement', | |
7182 'tbody': 'HTMLTableSectionElement', | |
7183 // WebKit and Moz are wrong: | |
7184 // https://bugs.webkit.org/show_bug.cgi?id=111469 | |
7185 // https://bugzilla.mozilla.org/show_bug.cgi?id=848096 | |
7186 // 'td': 'HTMLTableCellElement', | |
7187 'template': 'HTMLTemplateElement', | |
7188 'textarea': 'HTMLTextAreaElement', | |
7189 'thead': 'HTMLTableSectionElement', | |
7190 'time': 'HTMLTimeElement', | |
7191 'title': 'HTMLTitleElement', | |
7192 'tr': 'HTMLTableRowElement', | |
7193 'track': 'HTMLTrackElement', | |
7194 'ul': 'HTMLUListElement', | |
7195 'video': 'HTMLVideoElement', | |
7196 }; | |
7197 | |
7198 function overrideConstructor(tagName) { | |
7199 var nativeConstructorName = elements[tagName]; | |
7200 var nativeConstructor = window[nativeConstructorName]; | |
7201 if (!nativeConstructor) | |
7202 return; | |
7203 var element = document.createElement(tagName); | |
7204 var wrapperConstructor = element.constructor; | |
7205 window[nativeConstructorName] = wrapperConstructor; | |
7206 } | |
7207 | |
7208 Object.keys(elements).forEach(overrideConstructor); | |
7209 | |
7210 Object.getOwnPropertyNames(scope.wrappers).forEach(function(name) { | |
7211 window[name] = scope.wrappers[name] | |
7212 }); | |
7213 | |
7214 })(window.ShadowDOMPolyfill); | |
7215 | |
7216 /* | |
7217 * Copyright 2013 The Polymer Authors. All rights reserved. | |
7218 * Use of this source code is governed by a BSD-style | |
7219 * license that can be found in the LICENSE file. | |
7220 */ | |
7221 (function() { | |
7222 | |
7223 // convenient global | |
7224 window.wrap = ShadowDOMPolyfill.wrapIfNeeded; | |
7225 window.unwrap = ShadowDOMPolyfill.unwrapIfNeeded; | |
7226 | |
7227 // users may want to customize other types | |
7228 // TODO(sjmiles): 'button' is now supported by ShadowDOMPolyfill, but | |
7229 // I've left this code here in case we need to temporarily patch another | |
7230 // type | |
7231 /* | |
7232 (function() { | |
7233 var elts = {HTMLButtonElement: 'button'}; | |
7234 for (var c in elts) { | |
7235 window[c] = function() { throw 'Patched Constructor'; }; | |
7236 window[c].prototype = Object.getPrototypeOf( | |
7237 document.createElement(elts[c])); | |
7238 } | |
7239 })(); | |
7240 */ | |
7241 | |
7242 // patch in prefixed name | |
7243 Object.defineProperty(Element.prototype, 'webkitShadowRoot', | |
7244 Object.getOwnPropertyDescriptor(Element.prototype, 'shadowRoot')); | |
7245 | |
7246 var originalCreateShadowRoot = Element.prototype.createShadowRoot; | |
7247 Element.prototype.createShadowRoot = function() { | |
7248 var root = originalCreateShadowRoot.call(this); | |
7249 CustomElements.watchShadow(this); | |
7250 return root; | |
7251 }; | |
7252 | |
7253 Element.prototype.webkitCreateShadowRoot = Element.prototype.createShadowRoot; | |
7254 })(); | |
7255 | |
7256 /* | |
7257 * Copyright 2012 The Polymer Authors. All rights reserved. | |
7258 * Use of this source code is governed by a BSD-style | |
7259 * license that can be found in the LICENSE file. | |
7260 */ | |
7261 | |
7262 /* | |
7263 This is a limited shim for ShadowDOM css styling. | |
7264 https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#style
s | |
7265 | |
7266 The intention here is to support only the styling features which can be | |
7267 relatively simply implemented. The goal is to allow users to avoid the | |
7268 most obvious pitfalls and do so without compromising performance significantly
. | |
7269 For ShadowDOM styling that's not covered here, a set of best practices | |
7270 can be provided that should allow users to accomplish more complex styling. | |
7271 | |
7272 The following is a list of specific ShadowDOM styling features and a brief | |
7273 discussion of the approach used to shim. | |
7274 | |
7275 Shimmed features: | |
7276 | |
7277 * :host, :host-context: ShadowDOM allows styling of the shadowRoot's host | |
7278 element using the :host rule. To shim this feature, the :host styles are | |
7279 reformatted and prefixed with a given scope name and promoted to a | |
7280 document level stylesheet. | |
7281 For example, given a scope name of .foo, a rule like this: | |
7282 | |
7283 :host { | |
7284 background: red; | |
7285 } | |
7286 } | |
7287 | |
7288 becomes: | |
7289 | |
7290 .foo { | |
7291 background: red; | |
7292 } | |
7293 | |
7294 * encapsultion: Styles defined within ShadowDOM, apply only to | |
7295 dom inside the ShadowDOM. Polymer uses one of two techniques to imlement | |
7296 this feature. | |
7297 | |
7298 By default, rules are prefixed with the host element tag name | |
7299 as a descendant selector. This ensures styling does not leak out of the 'top' | |
7300 of the element's ShadowDOM. For example, | |
7301 | |
7302 div { | |
7303 font-weight: bold; | |
7304 } | |
7305 | |
7306 becomes: | |
7307 | |
7308 x-foo div { | |
7309 font-weight: bold; | |
7310 } | |
7311 | |
7312 becomes: | |
7313 | |
7314 | |
7315 Alternatively, if Platform.ShadowCSS.strictStyling is set to true then | |
7316 selectors are scoped by adding an attribute selector suffix to each | |
7317 simple selector that contains the host element tag name. Each element | |
7318 in the element's ShadowDOM template is also given the scope attribute. | |
7319 Thus, these rules match only elements that have the scope attribute. | |
7320 For example, given a scope name of x-foo, a rule like this: | |
7321 | |
7322 div { | |
7323 font-weight: bold; | |
7324 } | |
7325 | |
7326 becomes: | |
7327 | |
7328 div[x-foo] { | |
7329 font-weight: bold; | |
7330 } | |
7331 | |
7332 Note that elements that are dynamically added to a scope must have the scope | |
7333 selector added to them manually. | |
7334 | |
7335 * upper/lower bound encapsulation: Styles which are defined outside a | |
7336 shadowRoot should not cross the ShadowDOM boundary and should not apply | |
7337 inside a shadowRoot. | |
7338 | |
7339 This styling behavior is not emulated. Some possible ways to do this that | |
7340 were rejected due to complexity and/or performance concerns include: (1) reset | |
7341 every possible property for every possible selector for a given scope name; | |
7342 (2) re-implement css in javascript. | |
7343 | |
7344 As an alternative, users should make sure to use selectors | |
7345 specific to the scope in which they are working. | |
7346 | |
7347 * ::distributed: This behavior is not emulated. It's often not necessary | |
7348 to style the contents of a specific insertion point and instead, descendants | |
7349 of the host element can be styled selectively. Users can also create an | |
7350 extra node around an insertion point and style that node's contents | |
7351 via descendent selectors. For example, with a shadowRoot like this: | |
7352 | |
7353 <style> | |
7354 ::content(div) { | |
7355 background: red; | |
7356 } | |
7357 </style> | |
7358 <content></content> | |
7359 | |
7360 could become: | |
7361 | |
7362 <style> | |
7363 / *@polyfill .content-container div * / | |
7364 ::content(div) { | |
7365 background: red; | |
7366 } | |
7367 </style> | |
7368 <div class="content-container"> | |
7369 <content></content> | |
7370 </div> | |
7371 | |
7372 Note the use of @polyfill in the comment above a ShadowDOM specific style | |
7373 declaration. This is a directive to the styling shim to use the selector | |
7374 in comments in lieu of the next selector when running under polyfill. | |
7375 */ | |
7376 (function(scope) { | |
7377 | |
7378 var ShadowCSS = { | |
7379 strictStyling: false, | |
7380 registry: {}, | |
7381 // Shim styles for a given root associated with a name and extendsName | |
7382 // 1. cache root styles by name | |
7383 // 2. optionally tag root nodes with scope name | |
7384 // 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */ | |
7385 // 4. shim :host and scoping | |
7386 shimStyling: function(root, name, extendsName) { | |
7387 var scopeStyles = this.prepareRoot(root, name, extendsName); | |
7388 var typeExtension = this.isTypeExtension(extendsName); | |
7389 var scopeSelector = this.makeScopeSelector(name, typeExtension); | |
7390 // use caching to make working with styles nodes easier and to facilitate | |
7391 // lookup of extendee | |
7392 var cssText = stylesToCssText(scopeStyles, true); | |
7393 cssText = this.scopeCssText(cssText, scopeSelector); | |
7394 // cache shimmed css on root for user extensibility | |
7395 if (root) { | |
7396 root.shimmedStyle = cssText; | |
7397 } | |
7398 // add style to document | |
7399 this.addCssToDocument(cssText, name); | |
7400 }, | |
7401 /* | |
7402 * Shim a style element with the given selector. Returns cssText that can | |
7403 * be included in the document via Platform.ShadowCSS.addCssToDocument(css). | |
7404 */ | |
7405 shimStyle: function(style, selector) { | |
7406 return this.shimCssText(style.textContent, selector); | |
7407 }, | |
7408 /* | |
7409 * Shim some cssText with the given selector. Returns cssText that can | |
7410 * be included in the document via Platform.ShadowCSS.addCssToDocument(css). | |
7411 */ | |
7412 shimCssText: function(cssText, selector) { | |
7413 cssText = this.insertDirectives(cssText); | |
7414 return this.scopeCssText(cssText, selector); | |
7415 }, | |
7416 makeScopeSelector: function(name, typeExtension) { | |
7417 if (name) { | |
7418 return typeExtension ? '[is=' + name + ']' : name; | |
7419 } | |
7420 return ''; | |
7421 }, | |
7422 isTypeExtension: function(extendsName) { | |
7423 return extendsName && extendsName.indexOf('-') < 0; | |
7424 }, | |
7425 prepareRoot: function(root, name, extendsName) { | |
7426 var def = this.registerRoot(root, name, extendsName); | |
7427 this.replaceTextInStyles(def.rootStyles, this.insertDirectives); | |
7428 // remove existing style elements | |
7429 this.removeStyles(root, def.rootStyles); | |
7430 // apply strict attr | |
7431 if (this.strictStyling) { | |
7432 this.applyScopeToContent(root, name); | |
7433 } | |
7434 return def.scopeStyles; | |
7435 }, | |
7436 removeStyles: function(root, styles) { | |
7437 for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) { | |
7438 s.parentNode.removeChild(s); | |
7439 } | |
7440 }, | |
7441 registerRoot: function(root, name, extendsName) { | |
7442 var def = this.registry[name] = { | |
7443 root: root, | |
7444 name: name, | |
7445 extendsName: extendsName | |
7446 } | |
7447 var styles = this.findStyles(root); | |
7448 def.rootStyles = styles; | |
7449 def.scopeStyles = def.rootStyles; | |
7450 var extendee = this.registry[def.extendsName]; | |
7451 if (extendee) { | |
7452 def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles); | |
7453 } | |
7454 return def; | |
7455 }, | |
7456 findStyles: function(root) { | |
7457 if (!root) { | |
7458 return []; | |
7459 } | |
7460 var styles = root.querySelectorAll('style'); | |
7461 return Array.prototype.filter.call(styles, function(s) { | |
7462 return !s.hasAttribute(NO_SHIM_ATTRIBUTE); | |
7463 }); | |
7464 }, | |
7465 applyScopeToContent: function(root, name) { | |
7466 if (root) { | |
7467 // add the name attribute to each node in root. | |
7468 Array.prototype.forEach.call(root.querySelectorAll('*'), | |
7469 function(node) { | |
7470 node.setAttribute(name, ''); | |
7471 }); | |
7472 // and template contents too | |
7473 Array.prototype.forEach.call(root.querySelectorAll('template'), | |
7474 function(template) { | |
7475 this.applyScopeToContent(template.content, name); | |
7476 }, | |
7477 this); | |
7478 } | |
7479 }, | |
7480 insertDirectives: function(cssText) { | |
7481 cssText = this.insertPolyfillDirectivesInCssText(cssText); | |
7482 return this.insertPolyfillRulesInCssText(cssText); | |
7483 }, | |
7484 /* | |
7485 * Process styles to convert native ShadowDOM rules that will trip | |
7486 * up the css parser; we rely on decorating the stylesheet with inert rules. | |
7487 * | |
7488 * For example, we convert this rule: | |
7489 * | |
7490 * polyfill-next-selector { content: ':host menu-item'; } | |
7491 * ::content menu-item { | |
7492 * | |
7493 * to this: | |
7494 * | |
7495 * scopeName menu-item { | |
7496 * | |
7497 **/ | |
7498 insertPolyfillDirectivesInCssText: function(cssText) { | |
7499 // TODO(sorvell): remove either content or comment | |
7500 cssText = cssText.replace(cssCommentNextSelectorRe, function(match, p1) { | |
7501 // remove end comment delimiter and add block start | |
7502 return p1.slice(0, -2) + '{'; | |
7503 }); | |
7504 return cssText.replace(cssContentNextSelectorRe, function(match, p1) { | |
7505 return p1 + ' {'; | |
7506 }); | |
7507 }, | |
7508 /* | |
7509 * Process styles to add rules which will only apply under the polyfill | |
7510 * | |
7511 * For example, we convert this rule: | |
7512 * | |
7513 * polyfill-rule { | |
7514 * content: ':host menu-item'; | |
7515 * ... | |
7516 * } | |
7517 * | |
7518 * to this: | |
7519 * | |
7520 * scopeName menu-item {...} | |
7521 * | |
7522 **/ | |
7523 insertPolyfillRulesInCssText: function(cssText) { | |
7524 // TODO(sorvell): remove either content or comment | |
7525 cssText = cssText.replace(cssCommentRuleRe, function(match, p1) { | |
7526 // remove end comment delimiter | |
7527 return p1.slice(0, -1); | |
7528 }); | |
7529 return cssText.replace(cssContentRuleRe, function(match, p1, p2, p3) { | |
7530 var rule = match.replace(p1, '').replace(p2, ''); | |
7531 return p3 + rule; | |
7532 }); | |
7533 }, | |
7534 /* Ensure styles are scoped. Pseudo-scoping takes a rule like: | |
7535 * | |
7536 * .foo {... } | |
7537 * | |
7538 * and converts this to | |
7539 * | |
7540 * scopeName .foo { ... } | |
7541 */ | |
7542 scopeCssText: function(cssText, scopeSelector) { | |
7543 var unscoped = this.extractUnscopedRulesFromCssText(cssText); | |
7544 cssText = this.insertPolyfillHostInCssText(cssText); | |
7545 cssText = this.convertColonHost(cssText); | |
7546 cssText = this.convertColonHostContext(cssText); | |
7547 cssText = this.convertCombinators(cssText); | |
7548 if (scopeSelector) { | |
7549 var self = this, cssText; | |
7550 withCssRules(cssText, function(rules) { | |
7551 cssText = self.scopeRules(rules, scopeSelector); | |
7552 }); | |
7553 | |
7554 } | |
7555 cssText = cssText + '\n' + unscoped; | |
7556 return cssText.trim(); | |
7557 }, | |
7558 /* | |
7559 * Process styles to add rules which will only apply under the polyfill | |
7560 * and do not process via CSSOM. (CSSOM is destructive to rules on rare | |
7561 * occasions, e.g. -webkit-calc on Safari.) | |
7562 * For example, we convert this rule: | |
7563 * | |
7564 * (comment start) @polyfill-unscoped-rule menu-item { | |
7565 * ... } (comment end) | |
7566 * | |
7567 * to this: | |
7568 * | |
7569 * menu-item {...} | |
7570 * | |
7571 **/ | |
7572 extractUnscopedRulesFromCssText: function(cssText) { | |
7573 // TODO(sorvell): remove either content or comment | |
7574 var r = '', m; | |
7575 while (m = cssCommentUnscopedRuleRe.exec(cssText)) { | |
7576 r += m[1].slice(0, -1) + '\n\n'; | |
7577 } | |
7578 while (m = cssContentUnscopedRuleRe.exec(cssText)) { | |
7579 r += m[0].replace(m[2], '').replace(m[1], m[3]) + '\n\n'; | |
7580 } | |
7581 return r; | |
7582 }, | |
7583 /* | |
7584 * convert a rule like :host(.foo) > .bar { } | |
7585 * | |
7586 * to | |
7587 * | |
7588 * scopeName.foo > .bar | |
7589 */ | |
7590 convertColonHost: function(cssText) { | |
7591 return this.convertColonRule(cssText, cssColonHostRe, | |
7592 this.colonHostPartReplacer); | |
7593 }, | |
7594 /* | |
7595 * convert a rule like :host-context(.foo) > .bar { } | |
7596 * | |
7597 * to | |
7598 * | |
7599 * scopeName.foo > .bar, .foo scopeName > .bar { } | |
7600 * | |
7601 * and | |
7602 * | |
7603 * :host-context(.foo:host) .bar { ... } | |
7604 * | |
7605 * to | |
7606 * | |
7607 * scopeName.foo .bar { ... } | |
7608 */ | |
7609 convertColonHostContext: function(cssText) { | |
7610 return this.convertColonRule(cssText, cssColonHostContextRe, | |
7611 this.colonHostContextPartReplacer); | |
7612 }, | |
7613 convertColonRule: function(cssText, regExp, partReplacer) { | |
7614 // p1 = :host, p2 = contents of (), p3 rest of rule | |
7615 return cssText.replace(regExp, function(m, p1, p2, p3) { | |
7616 p1 = polyfillHostNoCombinator; | |
7617 if (p2) { | |
7618 var parts = p2.split(','), r = []; | |
7619 for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) { | |
7620 p = p.trim(); | |
7621 r.push(partReplacer(p1, p, p3)); | |
7622 } | |
7623 return r.join(','); | |
7624 } else { | |
7625 return p1 + p3; | |
7626 } | |
7627 }); | |
7628 }, | |
7629 colonHostContextPartReplacer: function(host, part, suffix) { | |
7630 if (part.match(polyfillHost)) { | |
7631 return this.colonHostPartReplacer(host, part, suffix); | |
7632 } else { | |
7633 return host + part + suffix + ', ' + part + ' ' + host + suffix; | |
7634 } | |
7635 }, | |
7636 colonHostPartReplacer: function(host, part, suffix) { | |
7637 return host + part.replace(polyfillHost, '') + suffix; | |
7638 }, | |
7639 /* | |
7640 * Convert ^ and ^^ combinators by replacing with space. | |
7641 */ | |
7642 convertCombinators: function(cssText) { | |
7643 for (var i=0; i < combinatorsRe.length; i++) { | |
7644 cssText = cssText.replace(combinatorsRe[i], ' '); | |
7645 } | |
7646 return cssText; | |
7647 }, | |
7648 // change a selector like 'div' to 'name div' | |
7649 scopeRules: function(cssRules, scopeSelector) { | |
7650 var cssText = ''; | |
7651 if (cssRules) { | |
7652 Array.prototype.forEach.call(cssRules, function(rule) { | |
7653 if (rule.selectorText && (rule.style && rule.style.cssText)) { | |
7654 cssText += this.scopeSelector(rule.selectorText, scopeSelector, | |
7655 this.strictStyling) + ' {\n\t'; | |
7656 cssText += this.propertiesFromRule(rule) + '\n}\n\n'; | |
7657 } else if (rule.type === CSSRule.MEDIA_RULE) { | |
7658 cssText += '@media ' + rule.media.mediaText + ' {\n'; | |
7659 cssText += this.scopeRules(rule.cssRules, scopeSelector); | |
7660 cssText += '\n}\n\n'; | |
7661 } else if (rule.cssText) { | |
7662 cssText += rule.cssText + '\n\n'; | |
7663 } | |
7664 }, this); | |
7665 } | |
7666 return cssText; | |
7667 }, | |
7668 scopeSelector: function(selector, scopeSelector, strict) { | |
7669 var r = [], parts = selector.split(','); | |
7670 parts.forEach(function(p) { | |
7671 p = p.trim(); | |
7672 if (this.selectorNeedsScoping(p, scopeSelector)) { | |
7673 p = (strict && !p.match(polyfillHostNoCombinator)) ? | |
7674 this.applyStrictSelectorScope(p, scopeSelector) : | |
7675 this.applySimpleSelectorScope(p, scopeSelector); | |
7676 } | |
7677 r.push(p); | |
7678 }, this); | |
7679 return r.join(', '); | |
7680 }, | |
7681 selectorNeedsScoping: function(selector, scopeSelector) { | |
7682 var re = this.makeScopeMatcher(scopeSelector); | |
7683 return !selector.match(re); | |
7684 }, | |
7685 makeScopeMatcher: function(scopeSelector) { | |
7686 scopeSelector = scopeSelector.replace(/\[/g, '\\[').replace(/\[/g, '\\]'); | |
7687 return new RegExp('^(' + scopeSelector + ')' + selectorReSuffix, 'm'); | |
7688 }, | |
7689 // scope via name and [is=name] | |
7690 applySimpleSelectorScope: function(selector, scopeSelector) { | |
7691 if (selector.match(polyfillHostRe)) { | |
7692 selector = selector.replace(polyfillHostNoCombinator, scopeSelector); | |
7693 return selector.replace(polyfillHostRe, scopeSelector + ' '); | |
7694 } else { | |
7695 return scopeSelector + ' ' + selector; | |
7696 } | |
7697 }, | |
7698 // return a selector with [name] suffix on each simple selector | |
7699 // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] | |
7700 applyStrictSelectorScope: function(selector, scopeSelector) { | |
7701 scopeSelector = scopeSelector.replace(/\[is=([^\]]*)\]/g, '$1'); | |
7702 var splits = [' ', '>', '+', '~'], | |
7703 scoped = selector, | |
7704 attrName = '[' + scopeSelector + ']'; | |
7705 splits.forEach(function(sep) { | |
7706 var parts = scoped.split(sep); | |
7707 scoped = parts.map(function(p) { | |
7708 // remove :host since it should be unnecessary | |
7709 var t = p.trim().replace(polyfillHostRe, ''); | |
7710 if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) { | |
7711 p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3') | |
7712 } | |
7713 return p; | |
7714 }).join(sep); | |
7715 }); | |
7716 return scoped; | |
7717 }, | |
7718 insertPolyfillHostInCssText: function(selector) { | |
7719 return selector.replace(colonHostContextRe, polyfillHostContext).replace( | |
7720 colonHostRe, polyfillHost); | |
7721 }, | |
7722 propertiesFromRule: function(rule) { | |
7723 var cssText = rule.style.cssText; | |
7724 // TODO(sorvell): Safari cssom incorrectly removes quotes from the content | |
7725 // property. (https://bugs.webkit.org/show_bug.cgi?id=118045) | |
7726 // don't replace attr rules | |
7727 if (rule.style.content && !rule.style.content.match(/['"]+|attr/)) { | |
7728 cssText = cssText.replace(/content:[^;]*;/g, 'content: \'' + | |
7729 rule.style.content + '\';'); | |
7730 } | |
7731 // TODO(sorvell): we can workaround this issue here, but we need a list | |
7732 // of troublesome properties to fix https://github.com/Polymer/platform/issu
es/53 | |
7733 // | |
7734 // inherit rules can be omitted from cssText | |
7735 // TODO(sorvell): remove when Blink bug is fixed: | |
7736 // https://code.google.com/p/chromium/issues/detail?id=358273 | |
7737 var style = rule.style; | |
7738 for (var i in style) { | |
7739 if (style[i] === 'initial') { | |
7740 cssText += i + ': initial; '; | |
7741 } | |
7742 } | |
7743 return cssText; | |
7744 }, | |
7745 replaceTextInStyles: function(styles, action) { | |
7746 if (styles && action) { | |
7747 if (!(styles instanceof Array)) { | |
7748 styles = [styles]; | |
7749 } | |
7750 Array.prototype.forEach.call(styles, function(s) { | |
7751 s.textContent = action.call(this, s.textContent); | |
7752 }, this); | |
7753 } | |
7754 }, | |
7755 addCssToDocument: function(cssText, name) { | |
7756 if (cssText.match('@import')) { | |
7757 addOwnSheet(cssText, name); | |
7758 } else { | |
7759 addCssToDocument(cssText); | |
7760 } | |
7761 } | |
7762 }; | |
7763 | |
7764 var selectorRe = /([^{]*)({[\s\S]*?})/gim, | |
7765 cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim, | |
7766 // TODO(sorvell): remove either content or comment | |
7767 cssCommentNextSelectorRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^
{]*?){/gim, | |
7768 cssContentNextSelectorRe = /polyfill-next-selector[^}]*content\:[\s]*'([^']*
)'[^}]*}([^{]*?){/gim, | |
7769 // TODO(sorvell): remove either content or comment | |
7770 cssCommentRuleRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim, | |
7771 cssContentRuleRe = /(polyfill-rule)[^}]*(content\:[\s]*'([^']*)'[^;]*;)[^}]*
}/gim, | |
7772 // TODO(sorvell): remove either content or comment | |
7773 cssCommentUnscopedRuleRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([^/*][^*]
*\*+)*)\//gim, | |
7774 cssContentUnscopedRuleRe = /(polyfill-unscoped-rule)[^}]*(content\:[\s]*'([^
']*)'[^;]*;)[^}]*}/gim, | |
7775 cssPseudoRe = /::(x-[^\s{,(]*)/gim, | |
7776 cssPartRe = /::part\(([^)]*)\)/gim, | |
7777 // note: :host pre-processed to -shadowcsshost. | |
7778 polyfillHost = '-shadowcsshost', | |
7779 // note: :host-context pre-processed to -shadowcsshostcontext. | |
7780 polyfillHostContext = '-shadowcsscontext', | |
7781 parenSuffix = ')(?:\\((' + | |
7782 '(?:\\([^)(]*\\)|[^)(]*)+?' + | |
7783 ')\\))?([^,{]*)'; | |
7784 cssColonHostRe = new RegExp('(' + polyfillHost + parenSuffix, 'gim'), | |
7785 cssColonHostContextRe = new RegExp('(' + polyfillHostContext + parenSuffix,
'gim'), | |
7786 selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$', | |
7787 colonHostRe = /\:host/gim, | |
7788 colonHostContextRe = /\:host-context/gim, | |
7789 /* host name without combinator */ | |
7790 polyfillHostNoCombinator = polyfillHost + '-no-combinator', | |
7791 polyfillHostRe = new RegExp(polyfillHost, 'gim'), | |
7792 polyfillHostContextRe = new RegExp(polyfillHostContext, 'gim'), | |
7793 combinatorsRe = [ | |
7794 /\^\^/g, | |
7795 /\^/g, | |
7796 /\/shadow\//g, | |
7797 /\/shadow-deep\//g, | |
7798 /::shadow/g, | |
7799 /\/deep\//g | |
7800 ]; | |
7801 | |
7802 function stylesToCssText(styles, preserveComments) { | |
7803 var cssText = ''; | |
7804 Array.prototype.forEach.call(styles, function(s) { | |
7805 cssText += s.textContent + '\n\n'; | |
7806 }); | |
7807 // strip comments for easier processing | |
7808 if (!preserveComments) { | |
7809 cssText = cssText.replace(cssCommentRe, ''); | |
7810 } | |
7811 return cssText; | |
7812 } | |
7813 | |
7814 function cssTextToStyle(cssText) { | |
7815 var style = document.createElement('style'); | |
7816 style.textContent = cssText; | |
7817 return style; | |
7818 } | |
7819 | |
7820 function cssToRules(cssText) { | |
7821 var style = cssTextToStyle(cssText); | |
7822 document.head.appendChild(style); | |
7823 var rules = []; | |
7824 if (style.sheet) { | |
7825 // TODO(sorvell): Firefox throws when accessing the rules of a stylesheet | |
7826 // with an @import | |
7827 // https://bugzilla.mozilla.org/show_bug.cgi?id=625013 | |
7828 try { | |
7829 rules = style.sheet.cssRules; | |
7830 } catch(e) { | |
7831 // | |
7832 } | |
7833 } else { | |
7834 console.warn('sheet not found', style); | |
7835 } | |
7836 style.parentNode.removeChild(style); | |
7837 return rules; | |
7838 } | |
7839 | |
7840 var frame = document.createElement('iframe'); | |
7841 frame.style.display = 'none'; | |
7842 | |
7843 function initFrame() { | |
7844 frame.initialized = true; | |
7845 document.body.appendChild(frame); | |
7846 var doc = frame.contentDocument; | |
7847 var base = doc.createElement('base'); | |
7848 base.href = document.baseURI; | |
7849 doc.head.appendChild(base); | |
7850 } | |
7851 | |
7852 function inFrame(fn) { | |
7853 if (!frame.initialized) { | |
7854 initFrame(); | |
7855 } | |
7856 document.body.appendChild(frame); | |
7857 fn(frame.contentDocument); | |
7858 document.body.removeChild(frame); | |
7859 } | |
7860 | |
7861 // TODO(sorvell): use an iframe if the cssText contains an @import to workaround | |
7862 // https://code.google.com/p/chromium/issues/detail?id=345114 | |
7863 var isChrome = navigator.userAgent.match('Chrome'); | |
7864 function withCssRules(cssText, callback) { | |
7865 if (!callback) { | |
7866 return; | |
7867 } | |
7868 var rules; | |
7869 if (cssText.match('@import') && isChrome) { | |
7870 var style = cssTextToStyle(cssText); | |
7871 inFrame(function(doc) { | |
7872 doc.head.appendChild(style.impl); | |
7873 rules = style.sheet.cssRules; | |
7874 callback(rules); | |
7875 }); | |
7876 } else { | |
7877 rules = cssToRules(cssText); | |
7878 callback(rules); | |
7879 } | |
7880 } | |
7881 | |
7882 function rulesToCss(cssRules) { | |
7883 for (var i=0, css=[]; i < cssRules.length; i++) { | |
7884 css.push(cssRules[i].cssText); | |
7885 } | |
7886 return css.join('\n\n'); | |
7887 } | |
7888 | |
7889 function addCssToDocument(cssText) { | |
7890 if (cssText) { | |
7891 getSheet().appendChild(document.createTextNode(cssText)); | |
7892 } | |
7893 } | |
7894 | |
7895 function addOwnSheet(cssText, name) { | |
7896 var style = cssTextToStyle(cssText); | |
7897 style.setAttribute(name, ''); | |
7898 style.setAttribute(SHIMMED_ATTRIBUTE, ''); | |
7899 document.head.appendChild(style); | |
7900 } | |
7901 | |
7902 var SHIM_ATTRIBUTE = 'shim-shadowdom'; | |
7903 var SHIMMED_ATTRIBUTE = 'shim-shadowdom-css'; | |
7904 var NO_SHIM_ATTRIBUTE = 'no-shim'; | |
7905 | |
7906 var sheet; | |
7907 function getSheet() { | |
7908 if (!sheet) { | |
7909 sheet = document.createElement("style"); | |
7910 sheet.setAttribute(SHIMMED_ATTRIBUTE, ''); | |
7911 sheet[SHIMMED_ATTRIBUTE] = true; | |
7912 } | |
7913 return sheet; | |
7914 } | |
7915 | |
7916 // add polyfill stylesheet to document | |
7917 if (window.ShadowDOMPolyfill) { | |
7918 addCssToDocument('style { display: none !important; }\n'); | |
7919 var doc = wrap(document); | |
7920 var head = doc.querySelector('head'); | |
7921 head.insertBefore(getSheet(), head.childNodes[0]); | |
7922 | |
7923 // TODO(sorvell): monkey-patching HTMLImports is abusive; | |
7924 // consider a better solution. | |
7925 document.addEventListener('DOMContentLoaded', function() { | |
7926 var urlResolver = scope.urlResolver; | |
7927 | |
7928 if (window.HTMLImports && !HTMLImports.useNative) { | |
7929 var SHIM_SHEET_SELECTOR = 'link[rel=stylesheet]' + | |
7930 '[' + SHIM_ATTRIBUTE + ']'; | |
7931 var SHIM_STYLE_SELECTOR = 'style[' + SHIM_ATTRIBUTE + ']'; | |
7932 HTMLImports.importer.documentPreloadSelectors += ',' + SHIM_SHEET_SELECTOR
; | |
7933 HTMLImports.importer.importsPreloadSelectors += ',' + SHIM_SHEET_SELECTOR; | |
7934 | |
7935 HTMLImports.parser.documentSelectors = [ | |
7936 HTMLImports.parser.documentSelectors, | |
7937 SHIM_SHEET_SELECTOR, | |
7938 SHIM_STYLE_SELECTOR | |
7939 ].join(','); | |
7940 | |
7941 var originalParseGeneric = HTMLImports.parser.parseGeneric; | |
7942 | |
7943 HTMLImports.parser.parseGeneric = function(elt) { | |
7944 if (elt[SHIMMED_ATTRIBUTE]) { | |
7945 return; | |
7946 } | |
7947 var style = elt.__importElement || elt; | |
7948 if (!style.hasAttribute(SHIM_ATTRIBUTE)) { | |
7949 originalParseGeneric.call(this, elt); | |
7950 return; | |
7951 } | |
7952 if (elt.__resource) { | |
7953 style = elt.ownerDocument.createElement('style'); | |
7954 style.textContent = urlResolver.resolveCssText( | |
7955 elt.__resource, elt.href); | |
7956 } else { | |
7957 urlResolver.resolveStyle(style); | |
7958 } | |
7959 style.textContent = ShadowCSS.shimStyle(style); | |
7960 style.removeAttribute(SHIM_ATTRIBUTE, ''); | |
7961 style.setAttribute(SHIMMED_ATTRIBUTE, ''); | |
7962 style[SHIMMED_ATTRIBUTE] = true; | |
7963 // place in document | |
7964 if (style.parentNode !== head) { | |
7965 // replace links in head | |
7966 if (elt.parentNode === head) { | |
7967 head.replaceChild(style, elt); | |
7968 } else { | |
7969 head.appendChild(style); | |
7970 } | |
7971 } | |
7972 style.__importParsed = true; | |
7973 this.markParsingComplete(elt); | |
7974 } | |
7975 | |
7976 var hasResource = HTMLImports.parser.hasResource; | |
7977 HTMLImports.parser.hasResource = function(node) { | |
7978 if (node.localName === 'link' && node.rel === 'stylesheet' && | |
7979 node.hasAttribute(SHIM_ATTRIBUTE)) { | |
7980 return (node.__resource); | |
7981 } else { | |
7982 return hasResource.call(this, node); | |
7983 } | |
7984 } | |
7985 | |
7986 } | |
7987 }); | |
7988 } | |
7989 | |
7990 // exports | |
7991 scope.ShadowCSS = ShadowCSS; | |
7992 | |
7993 })(window.Platform); | |
7994 } else { | |
7995 /* | |
7996 * Copyright 2013 The Polymer Authors. All rights reserved. | |
7997 * Use of this source code is governed by a BSD-style | |
7998 * license that can be found in the LICENSE file. | |
7999 */ | |
8000 (function() { | |
8001 | |
8002 // poor man's adapter for template.content on various platform scenarios | |
8003 window.templateContent = window.templateContent || function(inTemplate) { | |
8004 return inTemplate.content; | |
8005 }; | |
8006 | |
8007 // so we can call wrap/unwrap without testing for ShadowDOMPolyfill | |
8008 | |
8009 window.wrap = window.unwrap = function(n){ | |
8010 return n; | |
8011 } | |
8012 | |
8013 addEventListener('DOMContentLoaded', function() { | |
8014 if (CustomElements.useNative === false) { | |
8015 var originalCreateShadowRoot = Element.prototype.createShadowRoot; | |
8016 Element.prototype.createShadowRoot = function() { | |
8017 var root = originalCreateShadowRoot.call(this); | |
8018 CustomElements.watchShadow(this); | |
8019 return root; | |
8020 }; | |
8021 } | |
8022 }); | |
8023 | |
8024 window.templateContent = function(inTemplate) { | |
8025 // if MDV exists, it may need to boostrap this template to reveal content | |
8026 if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { | |
8027 HTMLTemplateElement.bootstrap(inTemplate); | |
8028 } | |
8029 // fallback when there is no Shadow DOM polyfill, no MDV polyfill, and no | |
8030 // native template support | |
8031 if (!inTemplate.content && !inTemplate._content) { | |
8032 var frag = document.createDocumentFragment(); | |
8033 while (inTemplate.firstChild) { | |
8034 frag.appendChild(inTemplate.firstChild); | |
8035 } | |
8036 inTemplate._content = frag; | |
8037 } | |
8038 return inTemplate.content || inTemplate._content; | |
8039 }; | |
8040 | |
8041 })(); | |
8042 } | |
8043 /* Any copyright is dedicated to the Public Domain. | |
8044 * http://creativecommons.org/publicdomain/zero/1.0/ */ | |
8045 | |
8046 (function(scope) { | |
8047 'use strict'; | |
8048 | |
8049 // feature detect for URL constructor | |
8050 var hasWorkingUrl = false; | |
8051 if (!scope.forceJURL) { | |
8052 try { | |
8053 var u = new URL('b', 'http://a'); | |
8054 hasWorkingUrl = u.href === 'http://a/b'; | |
8055 } catch(e) {} | |
8056 } | |
8057 | |
8058 if (hasWorkingUrl) | |
8059 return; | |
8060 | |
8061 var relative = Object.create(null); | |
8062 relative['ftp'] = 21; | |
8063 relative['file'] = 0; | |
8064 relative['gopher'] = 70; | |
8065 relative['http'] = 80; | |
8066 relative['https'] = 443; | |
8067 relative['ws'] = 80; | |
8068 relative['wss'] = 443; | |
8069 | |
8070 var relativePathDotMapping = Object.create(null); | |
8071 relativePathDotMapping['%2e'] = '.'; | |
8072 relativePathDotMapping['.%2e'] = '..'; | |
8073 relativePathDotMapping['%2e.'] = '..'; | |
8074 relativePathDotMapping['%2e%2e'] = '..'; | |
8075 | |
8076 function isRelativeScheme(scheme) { | |
8077 return relative[scheme] !== undefined; | |
8078 } | |
8079 | |
8080 function invalid() { | |
8081 clear.call(this); | |
8082 this._isInvalid = true; | |
8083 } | |
8084 | |
8085 function IDNAToASCII(h) { | |
8086 if ('' == h) { | |
8087 invalid.call(this) | |
8088 } | |
8089 // XXX | |
8090 return h.toLowerCase() | |
8091 } | |
8092 | |
8093 function percentEscape(c) { | |
8094 var unicode = c.charCodeAt(0); | |
8095 if (unicode > 0x20 && | |
8096 unicode < 0x7F && | |
8097 // " # < > ? ` | |
8098 [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1 | |
8099 ) { | |
8100 return c; | |
8101 } | |
8102 return encodeURIComponent(c); | |
8103 } | |
8104 | |
8105 function percentEscapeQuery(c) { | |
8106 // XXX This actually needs to encode c using encoding and then | |
8107 // convert the bytes one-by-one. | |
8108 | |
8109 var unicode = c.charCodeAt(0); | |
8110 if (unicode > 0x20 && | |
8111 unicode < 0x7F && | |
8112 // " # < > ` (do not escape '?') | |
8113 [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1 | |
8114 ) { | |
8115 return c; | |
8116 } | |
8117 return encodeURIComponent(c); | |
8118 } | |
8119 | |
8120 var EOF = undefined, | |
8121 ALPHA = /[a-zA-Z]/, | |
8122 ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/; | |
8123 | |
8124 function parse(input, stateOverride, base) { | |
8125 function err(message) { | |
8126 errors.push(message) | |
8127 } | |
8128 | |
8129 var state = stateOverride || 'scheme start', | |
8130 cursor = 0, | |
8131 buffer = '', | |
8132 seenAt = false, | |
8133 seenBracket = false, | |
8134 errors = []; | |
8135 | |
8136 loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid)
{ | |
8137 var c = input[cursor]; | |
8138 switch (state) { | |
8139 case 'scheme start': | |
8140 if (c && ALPHA.test(c)) { | |
8141 buffer += c.toLowerCase(); // ASCII-safe | |
8142 state = 'scheme'; | |
8143 } else if (!stateOverride) { | |
8144 buffer = ''; | |
8145 state = 'no scheme'; | |
8146 continue; | |
8147 } else { | |
8148 err('Invalid scheme.'); | |
8149 break loop; | |
8150 } | |
8151 break; | |
8152 | |
8153 case 'scheme': | |
8154 if (c && ALPHANUMERIC.test(c)) { | |
8155 buffer += c.toLowerCase(); // ASCII-safe | |
8156 } else if (':' == c) { | |
8157 this._scheme = buffer; | |
8158 buffer = ''; | |
8159 if (stateOverride) { | |
8160 break loop; | |
8161 } | |
8162 if (isRelativeScheme(this._scheme)) { | |
8163 this._isRelative = true; | |
8164 } | |
8165 if ('file' == this._scheme) { | |
8166 state = 'relative'; | |
8167 } else if (this._isRelative && base && base._scheme == this._scheme)
{ | |
8168 state = 'relative or authority'; | |
8169 } else if (this._isRelative) { | |
8170 state = 'authority first slash'; | |
8171 } else { | |
8172 state = 'scheme data'; | |
8173 } | |
8174 } else if (!stateOverride) { | |
8175 buffer = ''; | |
8176 cursor = 0; | |
8177 state = 'no scheme'; | |
8178 continue; | |
8179 } else if (EOF == c) { | |
8180 break loop; | |
8181 } else { | |
8182 err('Code point not allowed in scheme: ' + c) | |
8183 break loop; | |
8184 } | |
8185 break; | |
8186 | |
8187 case 'scheme data': | |
8188 if ('?' == c) { | |
8189 query = '?'; | |
8190 state = 'query'; | |
8191 } else if ('#' == c) { | |
8192 this._fragment = '#'; | |
8193 state = 'fragment'; | |
8194 } else { | |
8195 // XXX error handling | |
8196 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
8197 this._schemeData += percentEscape(c); | |
8198 } | |
8199 } | |
8200 break; | |
8201 | |
8202 case 'no scheme': | |
8203 if (!base || !(isRelativeScheme(base._scheme))) { | |
8204 err('Missing scheme.'); | |
8205 invalid.call(this); | |
8206 } else { | |
8207 state = 'relative'; | |
8208 continue; | |
8209 } | |
8210 break; | |
8211 | |
8212 case 'relative or authority': | |
8213 if ('/' == c && '/' == input[cursor+1]) { | |
8214 state = 'authority ignore slashes'; | |
8215 } else { | |
8216 err('Expected /, got: ' + c); | |
8217 state = 'relative'; | |
8218 continue | |
8219 } | |
8220 break; | |
8221 | |
8222 case 'relative': | |
8223 this._isRelative = true; | |
8224 if ('file' != this._scheme) | |
8225 this._scheme = base._scheme; | |
8226 if (EOF == c) { | |
8227 this._host = base._host; | |
8228 this._port = base._port; | |
8229 this._path = base._path.slice(); | |
8230 this._query = base._query; | |
8231 break loop; | |
8232 } else if ('/' == c || '\\' == c) { | |
8233 if ('\\' == c) | |
8234 err('\\ is an invalid code point.'); | |
8235 state = 'relative slash'; | |
8236 } else if ('?' == c) { | |
8237 this._host = base._host; | |
8238 this._port = base._port; | |
8239 this._path = base._path.slice(); | |
8240 this._query = '?'; | |
8241 state = 'query'; | |
8242 } else if ('#' == c) { | |
8243 this._host = base._host; | |
8244 this._port = base._port; | |
8245 this._path = base._path.slice(); | |
8246 this._query = base._query; | |
8247 this._fragment = '#'; | |
8248 state = 'fragment'; | |
8249 } else { | |
8250 var nextC = input[cursor+1] | |
8251 var nextNextC = input[cursor+2] | |
8252 if ( | |
8253 'file' != this._scheme || !ALPHA.test(c) || | |
8254 (nextC != ':' && nextC != '|') || | |
8255 (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?'
!= nextNextC && '#' != nextNextC)) { | |
8256 this._host = base._host; | |
8257 this._port = base._port; | |
8258 this._path = base._path.slice(); | |
8259 this._path.pop(); | |
8260 } | |
8261 state = 'relative path'; | |
8262 continue; | |
8263 } | |
8264 break; | |
8265 | |
8266 case 'relative slash': | |
8267 if ('/' == c || '\\' == c) { | |
8268 if ('\\' == c) { | |
8269 err('\\ is an invalid code point.'); | |
8270 } | |
8271 if ('file' == this._scheme) { | |
8272 state = 'file host'; | |
8273 } else { | |
8274 state = 'authority ignore slashes'; | |
8275 } | |
8276 } else { | |
8277 if ('file' != this._scheme) { | |
8278 this._host = base._host; | |
8279 this._port = base._port; | |
8280 } | |
8281 state = 'relative path'; | |
8282 continue; | |
8283 } | |
8284 break; | |
8285 | |
8286 case 'authority first slash': | |
8287 if ('/' == c) { | |
8288 state = 'authority second slash'; | |
8289 } else { | |
8290 err("Expected '/', got: " + c); | |
8291 state = 'authority ignore slashes'; | |
8292 continue; | |
8293 } | |
8294 break; | |
8295 | |
8296 case 'authority second slash': | |
8297 state = 'authority ignore slashes'; | |
8298 if ('/' != c) { | |
8299 err("Expected '/', got: " + c); | |
8300 continue; | |
8301 } | |
8302 break; | |
8303 | |
8304 case 'authority ignore slashes': | |
8305 if ('/' != c && '\\' != c) { | |
8306 state = 'authority'; | |
8307 continue; | |
8308 } else { | |
8309 err('Expected authority, got: ' + c); | |
8310 } | |
8311 break; | |
8312 | |
8313 case 'authority': | |
8314 if ('@' == c) { | |
8315 if (seenAt) { | |
8316 err('@ already seen.'); | |
8317 buffer += '%40'; | |
8318 } | |
8319 seenAt = true; | |
8320 for (var i = 0; i < buffer.length; i++) { | |
8321 var cp = buffer[i]; | |
8322 if ('\t' == cp || '\n' == cp || '\r' == cp) { | |
8323 err('Invalid whitespace in authority.'); | |
8324 continue; | |
8325 } | |
8326 // XXX check URL code points | |
8327 if (':' == cp && null === this._password) { | |
8328 this._password = ''; | |
8329 continue; | |
8330 } | |
8331 var tempC = percentEscape(cp); | |
8332 (null !== this._password) ? this._password += tempC : this._userna
me += tempC; | |
8333 } | |
8334 buffer = ''; | |
8335 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ | |
8336 cursor -= buffer.length; | |
8337 buffer = ''; | |
8338 state = 'host'; | |
8339 continue; | |
8340 } else { | |
8341 buffer += c; | |
8342 } | |
8343 break; | |
8344 | |
8345 case 'file host': | |
8346 if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { | |
8347 if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':'
|| buffer[1] == '|')) { | |
8348 state = 'relative path'; | |
8349 } else if (buffer.length == 0) { | |
8350 state = 'relative path start'; | |
8351 } else { | |
8352 this._host = IDNAToASCII.call(this, buffer); | |
8353 buffer = ''; | |
8354 state = 'relative path start'; | |
8355 } | |
8356 continue; | |
8357 } else if ('\t' == c || '\n' == c || '\r' == c) { | |
8358 err('Invalid whitespace in file host.'); | |
8359 } else { | |
8360 buffer += c; | |
8361 } | |
8362 break; | |
8363 | |
8364 case 'host': | |
8365 case 'hostname': | |
8366 if (':' == c && !seenBracket) { | |
8367 // XXX host parsing | |
8368 this._host = IDNAToASCII.call(this, buffer); | |
8369 buffer = ''; | |
8370 state = 'port'; | |
8371 if ('hostname' == stateOverride) { | |
8372 break loop; | |
8373 } | |
8374 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ | |
8375 this._host = IDNAToASCII.call(this, buffer); | |
8376 buffer = ''; | |
8377 state = 'relative path start'; | |
8378 if (stateOverride) { | |
8379 break loop; | |
8380 } | |
8381 continue; | |
8382 } else if ('\t' != c && '\n' != c && '\r' != c) { | |
8383 if ('[' == c) { | |
8384 seenBracket = true; | |
8385 } else if (']' == c) { | |
8386 seenBracket = false; | |
8387 } | |
8388 buffer += c; | |
8389 } else { | |
8390 err('Invalid code point in host/hostname: ' + c); | |
8391 } | |
8392 break; | |
8393 | |
8394 case 'port': | |
8395 if (/[0-9]/.test(c)) { | |
8396 buffer += c; | |
8397 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c |
| stateOverride) { | |
8398 if ('' != buffer) { | |
8399 var temp = parseInt(buffer, 10); | |
8400 if (temp != relative[this._scheme]) { | |
8401 this._port = temp + ''; | |
8402 } | |
8403 buffer = ''; | |
8404 } | |
8405 if (stateOverride) { | |
8406 break loop; | |
8407 } | |
8408 state = 'relative path start'; | |
8409 continue; | |
8410 } else if ('\t' == c || '\n' == c || '\r' == c) { | |
8411 err('Invalid code point in port: ' + c); | |
8412 } else { | |
8413 invalid.call(this); | |
8414 } | |
8415 break; | |
8416 | |
8417 case 'relative path start': | |
8418 if ('\\' == c) | |
8419 err("'\\' not allowed in path."); | |
8420 state = 'relative path'; | |
8421 if ('/' != c && '\\' != c) { | |
8422 continue; | |
8423 } | |
8424 break; | |
8425 | |
8426 case 'relative path': | |
8427 if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c
|| '#' == c))) { | |
8428 if ('\\' == c) { | |
8429 err('\\ not allowed in relative path.'); | |
8430 } | |
8431 var tmp; | |
8432 if (tmp = relativePathDotMapping[buffer.toLowerCase()]) { | |
8433 buffer = tmp; | |
8434 } | |
8435 if ('..' == buffer) { | |
8436 this._path.pop(); | |
8437 if ('/' != c && '\\' != c) { | |
8438 this._path.push(''); | |
8439 } | |
8440 } else if ('.' == buffer && '/' != c && '\\' != c) { | |
8441 this._path.push(''); | |
8442 } else if ('.' != buffer) { | |
8443 if ('file' == this._scheme && this._path.length == 0 && buffer.len
gth == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') { | |
8444 buffer = buffer[0] + ':'; | |
8445 } | |
8446 this._path.push(buffer); | |
8447 } | |
8448 buffer = ''; | |
8449 if ('?' == c) { | |
8450 this._query = '?'; | |
8451 state = 'query'; | |
8452 } else if ('#' == c) { | |
8453 this._fragment = '#'; | |
8454 state = 'fragment'; | |
8455 } | |
8456 } else if ('\t' != c && '\n' != c && '\r' != c) { | |
8457 buffer += percentEscape(c); | |
8458 } | |
8459 break; | |
8460 | |
8461 case 'query': | |
8462 if (!stateOverride && '#' == c) { | |
8463 this._fragment = '#'; | |
8464 state = 'fragment'; | |
8465 } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
8466 this._query += percentEscapeQuery(c); | |
8467 } | |
8468 break; | |
8469 | |
8470 case 'fragment': | |
8471 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
8472 this._fragment += c; | |
8473 } | |
8474 break; | |
8475 } | |
8476 | |
8477 cursor++; | |
8478 } | |
8479 } | |
8480 | |
8481 function clear() { | |
8482 this._scheme = ''; | |
8483 this._schemeData = ''; | |
8484 this._username = ''; | |
8485 this._password = null; | |
8486 this._host = ''; | |
8487 this._port = ''; | |
8488 this._path = []; | |
8489 this._query = ''; | |
8490 this._fragment = ''; | |
8491 this._isInvalid = false; | |
8492 this._isRelative = false; | |
8493 } | |
8494 | |
8495 // Does not process domain names or IP addresses. | |
8496 // Does not handle encoding for the query parameter. | |
8497 function jURL(url, base /* , encoding */) { | |
8498 if (base !== undefined && !(base instanceof jURL)) | |
8499 base = new jURL(String(base)); | |
8500 | |
8501 this._url = url; | |
8502 clear.call(this); | |
8503 | |
8504 var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ''); | |
8505 // encoding = encoding || 'utf-8' | |
8506 | |
8507 parse.call(this, input, null, base); | |
8508 } | |
8509 | |
8510 jURL.prototype = { | |
8511 get href() { | |
8512 if (this._isInvalid) | |
8513 return this._url; | |
8514 | |
8515 var authority = ''; | |
8516 if ('' != this._username || null != this._password) { | |
8517 authority = this._username + | |
8518 (null != this._password ? ':' + this._password : '') + '@'; | |
8519 } | |
8520 | |
8521 return this.protocol + | |
8522 (this._isRelative ? '//' + authority + this.host : '') + | |
8523 this.pathname + this._query + this._fragment; | |
8524 }, | |
8525 set href(href) { | |
8526 clear.call(this); | |
8527 parse.call(this, href); | |
8528 }, | |
8529 | |
8530 get protocol() { | |
8531 return this._scheme + ':'; | |
8532 }, | |
8533 set protocol(protocol) { | |
8534 if (this._isInvalid) | |
8535 return; | |
8536 parse.call(this, protocol + ':', 'scheme start'); | |
8537 }, | |
8538 | |
8539 get host() { | |
8540 return this._isInvalid ? '' : this._port ? | |
8541 this._host + ':' + this._port : this._host; | |
8542 }, | |
8543 set host(host) { | |
8544 if (this._isInvalid || !this._isRelative) | |
8545 return; | |
8546 parse.call(this, host, 'host'); | |
8547 }, | |
8548 | |
8549 get hostname() { | |
8550 return this._host; | |
8551 }, | |
8552 set hostname(hostname) { | |
8553 if (this._isInvalid || !this._isRelative) | |
8554 return; | |
8555 parse.call(this, hostname, 'hostname'); | |
8556 }, | |
8557 | |
8558 get port() { | |
8559 return this._port; | |
8560 }, | |
8561 set port(port) { | |
8562 if (this._isInvalid || !this._isRelative) | |
8563 return; | |
8564 parse.call(this, port, 'port'); | |
8565 }, | |
8566 | |
8567 get pathname() { | |
8568 return this._isInvalid ? '' : this._isRelative ? | |
8569 '/' + this._path.join('/') : this._schemeData; | |
8570 }, | |
8571 set pathname(pathname) { | |
8572 if (this._isInvalid || !this._isRelative) | |
8573 return; | |
8574 this._path = []; | |
8575 parse.call(this, pathname, 'relative path start'); | |
8576 }, | |
8577 | |
8578 get search() { | |
8579 return this._isInvalid || !this._query || '?' == this._query ? | |
8580 '' : this._query; | |
8581 }, | |
8582 set search(search) { | |
8583 if (this._isInvalid || !this._isRelative) | |
8584 return; | |
8585 this._query = '?'; | |
8586 if ('?' == search[0]) | |
8587 search = search.slice(1); | |
8588 parse.call(this, search, 'query'); | |
8589 }, | |
8590 | |
8591 get hash() { | |
8592 return this._isInvalid || !this._fragment || '#' == this._fragment ? | |
8593 '' : this._fragment; | |
8594 }, | |
8595 set hash(hash) { | |
8596 if (this._isInvalid) | |
8597 return; | |
8598 this._fragment = '#'; | |
8599 if ('#' == hash[0]) | |
8600 hash = hash.slice(1); | |
8601 parse.call(this, hash, 'fragment'); | |
8602 } | |
8603 }; | |
8604 | |
8605 scope.URL = jURL; | |
8606 | |
8607 })(window); | |
8608 | |
8609 /* | |
8610 * Copyright 2013 The Polymer Authors. All rights reserved. | |
8611 * Use of this source code is governed by a BSD-style | |
8612 * license that can be found in the LICENSE file. | |
8613 */ | |
8614 | |
8615 (function(scope) { | |
8616 | |
8617 // Old versions of iOS do not have bind. | |
8618 | |
8619 if (!Function.prototype.bind) { | |
8620 Function.prototype.bind = function(scope) { | |
8621 var self = this; | |
8622 var args = Array.prototype.slice.call(arguments, 1); | |
8623 return function() { | |
8624 var args2 = args.slice(); | |
8625 args2.push.apply(args2, arguments); | |
8626 return self.apply(scope, args2); | |
8627 }; | |
8628 }; | |
8629 } | |
8630 | |
8631 // mixin | |
8632 | |
8633 // copy all properties from inProps (et al) to inObj | |
8634 function mixin(inObj/*, inProps, inMoreProps, ...*/) { | |
8635 var obj = inObj || {}; | |
8636 for (var i = 1; i < arguments.length; i++) { | |
8637 var p = arguments[i]; | |
8638 try { | |
8639 for (var n in p) { | |
8640 copyProperty(n, p, obj); | |
8641 } | |
8642 } catch(x) { | |
8643 } | |
8644 } | |
8645 return obj; | |
8646 } | |
8647 | |
8648 // copy property inName from inSource object to inTarget object | |
8649 function copyProperty(inName, inSource, inTarget) { | |
8650 var pd = getPropertyDescriptor(inSource, inName); | |
8651 Object.defineProperty(inTarget, inName, pd); | |
8652 } | |
8653 | |
8654 // get property descriptor for inName on inObject, even if | |
8655 // inName exists on some link in inObject's prototype chain | |
8656 function getPropertyDescriptor(inObject, inName) { | |
8657 if (inObject) { | |
8658 var pd = Object.getOwnPropertyDescriptor(inObject, inName); | |
8659 return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName); | |
8660 } | |
8661 } | |
8662 | |
8663 // export | |
8664 | |
8665 scope.mixin = mixin; | |
8666 | |
8667 })(window.Platform); | |
8668 // Copyright 2011 Google Inc. | |
8669 // | |
8670 // Licensed under the Apache License, Version 2.0 (the "License"); | |
8671 // you may not use this file except in compliance with the License. | |
8672 // You may obtain a copy of the License at | |
8673 // | |
8674 // http://www.apache.org/licenses/LICENSE-2.0 | |
8675 // | |
8676 // Unless required by applicable law or agreed to in writing, software | |
8677 // distributed under the License is distributed on an "AS IS" BASIS, | |
8678 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
8679 // See the License for the specific language governing permissions and | |
8680 // limitations under the License. | |
8681 | |
8682 (function(scope) { | |
8683 | |
8684 'use strict'; | |
8685 | |
8686 // polyfill DOMTokenList | |
8687 // * add/remove: allow these methods to take multiple classNames | |
8688 // * toggle: add a 2nd argument which forces the given state rather | |
8689 // than toggling. | |
8690 | |
8691 var add = DOMTokenList.prototype.add; | |
8692 var remove = DOMTokenList.prototype.remove; | |
8693 DOMTokenList.prototype.add = function() { | |
8694 for (var i = 0; i < arguments.length; i++) { | |
8695 add.call(this, arguments[i]); | |
8696 } | |
8697 }; | |
8698 DOMTokenList.prototype.remove = function() { | |
8699 for (var i = 0; i < arguments.length; i++) { | |
8700 remove.call(this, arguments[i]); | |
8701 } | |
8702 }; | |
8703 DOMTokenList.prototype.toggle = function(name, bool) { | |
8704 if (arguments.length == 1) { | |
8705 bool = !this.contains(name); | |
8706 } | |
8707 bool ? this.add(name) : this.remove(name); | |
8708 }; | |
8709 DOMTokenList.prototype.switch = function(oldName, newName) { | |
8710 oldName && this.remove(oldName); | |
8711 newName && this.add(newName); | |
8712 }; | |
8713 | |
8714 // add array() to NodeList, NamedNodeMap, HTMLCollection | |
8715 | |
8716 var ArraySlice = function() { | |
8717 return Array.prototype.slice.call(this); | |
8718 }; | |
8719 | |
8720 var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {}); | |
8721 | |
8722 NodeList.prototype.array = ArraySlice; | |
8723 namedNodeMap.prototype.array = ArraySlice; | |
8724 HTMLCollection.prototype.array = ArraySlice; | |
8725 | |
8726 // polyfill performance.now | |
8727 | |
8728 if (!window.performance) { | |
8729 var start = Date.now(); | |
8730 // only at millisecond precision | |
8731 window.performance = {now: function(){ return Date.now() - start }}; | |
8732 } | |
8733 | |
8734 // polyfill for requestAnimationFrame | |
8735 | |
8736 if (!window.requestAnimationFrame) { | |
8737 window.requestAnimationFrame = (function() { | |
8738 var nativeRaf = window.webkitRequestAnimationFrame || | |
8739 window.mozRequestAnimationFrame; | |
8740 | |
8741 return nativeRaf ? | |
8742 function(callback) { | |
8743 return nativeRaf(function() { | |
8744 callback(performance.now()); | |
8745 }); | |
8746 } : | |
8747 function( callback ){ | |
8748 return window.setTimeout(callback, 1000 / 60); | |
8749 }; | |
8750 })(); | |
8751 } | |
8752 | |
8753 if (!window.cancelAnimationFrame) { | |
8754 window.cancelAnimationFrame = (function() { | |
8755 return window.webkitCancelAnimationFrame || | |
8756 window.mozCancelAnimationFrame || | |
8757 function(id) { | |
8758 clearTimeout(id); | |
8759 }; | |
8760 })(); | |
8761 } | |
8762 | |
8763 // utility | |
8764 | |
8765 function createDOM(inTagOrNode, inHTML, inAttrs) { | |
8766 var dom = typeof inTagOrNode == 'string' ? | |
8767 document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true); | |
8768 dom.innerHTML = inHTML; | |
8769 if (inAttrs) { | |
8770 for (var n in inAttrs) { | |
8771 dom.setAttribute(n, inAttrs[n]); | |
8772 } | |
8773 } | |
8774 return dom; | |
8775 } | |
8776 // Make a stub for Polymer() for polyfill purposes; under the HTMLImports | |
8777 // polyfill, scripts in the main document run before imports. That means | |
8778 // if (1) polymer is imported and (2) Polymer() is called in the main document | |
8779 // in a script after the import, 2 occurs before 1. We correct this here | |
8780 // by specfiically patching Polymer(); this is not necessary under native | |
8781 // HTMLImports. | |
8782 var elementDeclarations = []; | |
8783 | |
8784 var polymerStub = function(name, dictionary) { | |
8785 elementDeclarations.push(arguments); | |
8786 } | |
8787 window.Polymer = polymerStub; | |
8788 | |
8789 // deliver queued delcarations | |
8790 scope.deliverDeclarations = function() { | |
8791 scope.deliverDeclarations = function() { | |
8792 throw 'Possible attempt to load Polymer twice'; | |
8793 }; | |
8794 return elementDeclarations; | |
8795 } | |
8796 | |
8797 // Once DOMContent has loaded, any main document scripts that depend on | |
8798 // Polymer() should have run. Calling Polymer() now is an error until | |
8799 // polymer is imported. | |
8800 window.addEventListener('DOMContentLoaded', function() { | |
8801 if (window.Polymer === polymerStub) { | |
8802 window.Polymer = function() { | |
8803 console.error('You tried to use polymer without loading it first. To ' + | |
8804 'load polymer, <link rel="import" href="' + | |
8805 'components/polymer/polymer.html">'); | |
8806 }; | |
8807 } | |
8808 }); | |
8809 | |
8810 // exports | |
8811 scope.createDOM = createDOM; | |
8812 | |
8813 })(window.Platform); | |
8814 | |
8815 /* | |
8816 * Copyright 2013 The Polymer Authors. All rights reserved. | |
8817 * Use of this source code is governed by a BSD-style | |
8818 * license that can be found in the LICENSE file. | |
8819 */ | |
8820 | |
8821 // poor man's adapter for template.content on various platform scenarios | |
8822 window.templateContent = window.templateContent || function(inTemplate) { | |
8823 return inTemplate.content; | |
8824 }; | |
8825 (function(scope) { | |
8826 | |
8827 scope = scope || (window.Inspector = {}); | |
8828 | |
8829 var inspector; | |
8830 | |
8831 window.sinspect = function(inNode, inProxy) { | |
8832 if (!inspector) { | |
8833 inspector = window.open('', 'ShadowDOM Inspector', null, true); | |
8834 inspector.document.write(inspectorHTML); | |
8835 //inspector.document.close(); | |
8836 inspector.api = { | |
8837 shadowize: shadowize | |
8838 }; | |
8839 } | |
8840 inspect(inNode || wrap(document.body), inProxy); | |
8841 }; | |
8842 | |
8843 var inspectorHTML = [ | |
8844 '<!DOCTYPE html>', | |
8845 '<html>', | |
8846 ' <head>', | |
8847 ' <title>ShadowDOM Inspector</title>', | |
8848 ' <style>', | |
8849 ' body {', | |
8850 ' }', | |
8851 ' pre {', | |
8852 ' font: 9pt "Courier New", monospace;', | |
8853 ' line-height: 1.5em;', | |
8854 ' }', | |
8855 ' tag {', | |
8856 ' color: purple;', | |
8857 ' }', | |
8858 ' ul {', | |
8859 ' margin: 0;', | |
8860 ' padding: 0;', | |
8861 ' list-style: none;', | |
8862 ' }', | |
8863 ' li {', | |
8864 ' display: inline-block;', | |
8865 ' background-color: #f1f1f1;', | |
8866 ' padding: 4px 6px;', | |
8867 ' border-radius: 4px;', | |
8868 ' margin-right: 4px;', | |
8869 ' }', | |
8870 ' </style>', | |
8871 ' </head>', | |
8872 ' <body>', | |
8873 ' <ul id="crumbs">', | |
8874 ' </ul>', | |
8875 ' <div id="tree"></div>', | |
8876 ' </body>', | |
8877 '</html>' | |
8878 ].join('\n'); | |
8879 | |
8880 var crumbs = []; | |
8881 | |
8882 var displayCrumbs = function() { | |
8883 // alias our document | |
8884 var d = inspector.document; | |
8885 // get crumbbar | |
8886 var cb = d.querySelector('#crumbs'); | |
8887 // clear crumbs | |
8888 cb.textContent = ''; | |
8889 // build new crumbs | |
8890 for (var i=0, c; c=crumbs[i]; i++) { | |
8891 var a = d.createElement('a'); | |
8892 a.href = '#'; | |
8893 a.textContent = c.localName; | |
8894 a.idx = i; | |
8895 a.onclick = function(event) { | |
8896 var c; | |
8897 while (crumbs.length > this.idx) { | |
8898 c = crumbs.pop(); | |
8899 } | |
8900 inspect(c.shadow || c, c); | |
8901 event.preventDefault(); | |
8902 }; | |
8903 cb.appendChild(d.createElement('li')).appendChild(a); | |
8904 } | |
8905 }; | |
8906 | |
8907 var inspect = function(inNode, inProxy) { | |
8908 // alias our document | |
8909 var d = inspector.document; | |
8910 // reset list of drillable nodes | |
8911 drillable = []; | |
8912 // memoize our crumb proxy | |
8913 var proxy = inProxy || inNode; | |
8914 crumbs.push(proxy); | |
8915 // update crumbs | |
8916 displayCrumbs(); | |
8917 // reflect local tree | |
8918 d.body.querySelector('#tree').innerHTML = | |
8919 '<pre>' + output(inNode, inNode.childNodes) + '</pre>'; | |
8920 }; | |
8921 | |
8922 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
8923 | |
8924 var blacklisted = {STYLE:1, SCRIPT:1, "#comment": 1, TEMPLATE: 1}; | |
8925 var blacklist = function(inNode) { | |
8926 return blacklisted[inNode.nodeName]; | |
8927 }; | |
8928 | |
8929 var output = function(inNode, inChildNodes, inIndent) { | |
8930 if (blacklist(inNode)) { | |
8931 return ''; | |
8932 } | |
8933 var indent = inIndent || ''; | |
8934 if (inNode.localName || inNode.nodeType == 11) { | |
8935 var name = inNode.localName || 'shadow-root'; | |
8936 //inChildNodes = ShadowDOM.localNodes(inNode); | |
8937 var info = indent + describe(inNode); | |
8938 // if only textNodes | |
8939 // TODO(sjmiles): make correct for ShadowDOM | |
8940 /*if (!inNode.children.length && inNode.localName !== 'content' && inNode.
localName !== 'shadow') { | |
8941 info += catTextContent(inChildNodes); | |
8942 } else*/ { | |
8943 // TODO(sjmiles): native <shadow> has no reference to its projection | |
8944 if (name == 'content' /*|| name == 'shadow'*/) { | |
8945 inChildNodes = inNode.getDistributedNodes(); | |
8946 } | |
8947 info += '<br/>'; | |
8948 var ind = indent + ' '; | |
8949 forEach(inChildNodes, function(n) { | |
8950 info += output(n, n.childNodes, ind); | |
8951 }); | |
8952 info += indent; | |
8953 } | |
8954 if (!({br:1}[name])) { | |
8955 info += '<tag></' + name + '></tag>'; | |
8956 info += '<br/>'; | |
8957 } | |
8958 } else { | |
8959 var text = inNode.textContent.trim(); | |
8960 info = text ? indent + '"' + text + '"' + '<br/>' : ''; | |
8961 } | |
8962 return info; | |
8963 }; | |
8964 | |
8965 var catTextContent = function(inChildNodes) { | |
8966 var info = ''; | |
8967 forEach(inChildNodes, function(n) { | |
8968 info += n.textContent.trim(); | |
8969 }); | |
8970 return info; | |
8971 }; | |
8972 | |
8973 var drillable = []; | |
8974 | |
8975 var describe = function(inNode) { | |
8976 var tag = '<tag>' + '<'; | |
8977 var name = inNode.localName || 'shadow-root'; | |
8978 if (inNode.webkitShadowRoot || inNode.shadowRoot) { | |
8979 tag += ' <button idx="' + drillable.length + | |
8980 '" onclick="api.shadowize.call(this)">' + name + '</button>'; | |
8981 drillable.push(inNode); | |
8982 } else { | |
8983 tag += name || 'shadow-root'; | |
8984 } | |
8985 if (inNode.attributes) { | |
8986 forEach(inNode.attributes, function(a) { | |
8987 tag += ' ' + a.name + (a.value ? '="' + a.value + '"' : ''); | |
8988 }); | |
8989 } | |
8990 tag += '>'+ '</tag>'; | |
8991 return tag; | |
8992 }; | |
8993 | |
8994 // remote api | |
8995 | |
8996 shadowize = function() { | |
8997 var idx = Number(this.attributes.idx.value); | |
8998 //alert(idx); | |
8999 var node = drillable[idx]; | |
9000 if (node) { | |
9001 inspect(node.webkitShadowRoot || node.shadowRoot, node) | |
9002 } else { | |
9003 console.log("bad shadowize node"); | |
9004 console.dir(this); | |
9005 } | |
9006 }; | |
9007 | |
9008 // export | |
9009 | |
9010 scope.output = output; | |
9011 | |
9012 })(window.Inspector); | |
9013 | |
9014 | |
9015 | |
9016 /* | |
9017 * Copyright 2013 The Polymer Authors. All rights reserved. | |
9018 * Use of this source code is governed by a BSD-style | |
9019 * license that can be found in the LICENSE file. | |
9020 */ | |
9021 (function(scope) { | |
9022 | |
9023 // TODO(sorvell): It's desireable to provide a default stylesheet | |
9024 // that's convenient for styling unresolved elements, but | |
9025 // it's cumbersome to have to include this manually in every page. | |
9026 // It would make sense to put inside some HTMLImport but | |
9027 // the HTMLImports polyfill does not allow loading of stylesheets | |
9028 // that block rendering. Therefore this injection is tolerated here. | |
9029 | |
9030 var style = document.createElement('style'); | |
9031 style.textContent = '' | |
9032 + 'body {' | |
9033 + 'transition: opacity ease-in 0.2s;' | |
9034 + ' } \n' | |
9035 + 'body[unresolved] {' | |
9036 + 'opacity: 0; display: block; overflow: hidden;' | |
9037 + ' } \n' | |
9038 ; | |
9039 var head = document.querySelector('head'); | |
9040 head.insertBefore(style, head.firstChild); | |
9041 | |
9042 })(Platform); | |
9043 | |
9044 (function(scope) { | |
9045 | |
9046 function withDependencies(task, depends) { | |
9047 depends = depends || []; | |
9048 if (!depends.map) { | |
9049 depends = [depends]; | |
9050 } | |
9051 return task.apply(this, depends.map(marshal)); | |
9052 } | |
9053 | |
9054 function module(name, dependsOrFactory, moduleFactory) { | |
9055 var module; | |
9056 switch (arguments.length) { | |
9057 case 0: | |
9058 return; | |
9059 case 1: | |
9060 module = null; | |
9061 break; | |
9062 case 2: | |
9063 module = dependsOrFactory.apply(this); | |
9064 break; | |
9065 default: | |
9066 module = withDependencies(moduleFactory, dependsOrFactory); | |
9067 break; | |
9068 } | |
9069 modules[name] = module; | |
9070 }; | |
9071 | |
9072 function marshal(name) { | |
9073 return modules[name]; | |
9074 } | |
9075 | |
9076 var modules = {}; | |
9077 | |
9078 function using(depends, task) { | |
9079 HTMLImports.whenImportsReady(function() { | |
9080 withDependencies(task, depends); | |
9081 }); | |
9082 }; | |
9083 | |
9084 // exports | |
9085 | |
9086 scope.marshal = marshal; | |
9087 scope.module = module; | |
9088 scope.using = using; | |
9089 | |
9090 })(window); | |
9091 /* | |
9092 * Copyright 2013 The Polymer Authors. All rights reserved. | |
9093 * Use of this source code is governed by a BSD-style | |
9094 * license that can be found in the LICENSE file. | |
9095 */ | |
9096 (function(scope) { | |
9097 | |
9098 var iterations = 0; | |
9099 var callbacks = []; | |
9100 var twiddle = document.createTextNode(''); | |
9101 | |
9102 function endOfMicrotask(callback) { | |
9103 twiddle.textContent = iterations++; | |
9104 callbacks.push(callback); | |
9105 } | |
9106 | |
9107 function atEndOfMicrotask() { | |
9108 while (callbacks.length) { | |
9109 callbacks.shift()(); | |
9110 } | |
9111 } | |
9112 | |
9113 new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask) | |
9114 .observe(twiddle, {characterData: true}) | |
9115 ; | |
9116 | |
9117 // exports | |
9118 | |
9119 scope.endOfMicrotask = endOfMicrotask; | |
9120 | |
9121 })(Platform); | |
9122 | |
9123 | |
9124 /* | |
9125 * Copyright 2013 The Polymer Authors. All rights reserved. | |
9126 * Use of this source code is governed by a BSD-style | |
9127 * license that can be found in the LICENSE file. | |
9128 */ | |
9129 | |
9130 (function(scope) { | |
9131 | |
9132 var urlResolver = { | |
9133 resolveDom: function(root, url) { | |
9134 url = url || root.ownerDocument.baseURI; | |
9135 this.resolveAttributes(root, url); | |
9136 this.resolveStyles(root, url); | |
9137 // handle template.content | |
9138 var templates = root.querySelectorAll('template'); | |
9139 if (templates) { | |
9140 for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i+
+) { | |
9141 if (t.content) { | |
9142 this.resolveDom(t.content, url); | |
9143 } | |
9144 } | |
9145 } | |
9146 }, | |
9147 resolveTemplate: function(template) { | |
9148 this.resolveDom(template.content, template.ownerDocument.baseURI); | |
9149 }, | |
9150 resolveStyles: function(root, url) { | |
9151 var styles = root.querySelectorAll('style'); | |
9152 if (styles) { | |
9153 for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) { | |
9154 this.resolveStyle(s, url); | |
9155 } | |
9156 } | |
9157 }, | |
9158 resolveStyle: function(style, url) { | |
9159 url = url || style.ownerDocument.baseURI; | |
9160 style.textContent = this.resolveCssText(style.textContent, url); | |
9161 }, | |
9162 resolveCssText: function(cssText, baseUrl) { | |
9163 cssText = replaceUrlsInCssText(cssText, baseUrl, CSS_URL_REGEXP); | |
9164 return replaceUrlsInCssText(cssText, baseUrl, CSS_IMPORT_REGEXP); | |
9165 }, | |
9166 resolveAttributes: function(root, url) { | |
9167 if (root.hasAttributes && root.hasAttributes()) { | |
9168 this.resolveElementAttributes(root, url); | |
9169 } | |
9170 // search for attributes that host urls | |
9171 var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR); | |
9172 if (nodes) { | |
9173 for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) { | |
9174 this.resolveElementAttributes(n, url); | |
9175 } | |
9176 } | |
9177 }, | |
9178 resolveElementAttributes: function(node, url) { | |
9179 url = url || node.ownerDocument.baseURI; | |
9180 URL_ATTRS.forEach(function(v) { | |
9181 var attr = node.attributes[v]; | |
9182 if (attr && attr.value && | |
9183 (attr.value.search(URL_TEMPLATE_SEARCH) < 0)) { | |
9184 var urlPath = resolveRelativeUrl(url, attr.value); | |
9185 attr.value = urlPath; | |
9186 } | |
9187 }); | |
9188 } | |
9189 }; | |
9190 | |
9191 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; | |
9192 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; | |
9193 var URL_ATTRS = ['href', 'src', 'action']; | |
9194 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; | |
9195 var URL_TEMPLATE_SEARCH = '{{.*}}'; | |
9196 | |
9197 function replaceUrlsInCssText(cssText, baseUrl, regexp) { | |
9198 return cssText.replace(regexp, function(m, pre, url, post) { | |
9199 var urlPath = url.replace(/["']/g, ''); | |
9200 urlPath = resolveRelativeUrl(baseUrl, urlPath); | |
9201 return pre + '\'' + urlPath + '\'' + post; | |
9202 }); | |
9203 } | |
9204 | |
9205 function resolveRelativeUrl(baseUrl, url) { | |
9206 var u = new URL(url, baseUrl); | |
9207 return makeDocumentRelPath(u.href); | |
9208 } | |
9209 | |
9210 function makeDocumentRelPath(url) { | |
9211 var root = document.baseURI; | |
9212 var u = new URL(url, root); | |
9213 if (u.host === root.host && u.port === root.port && | |
9214 u.protocol === root.protocol) { | |
9215 return makeRelPath(root.pathname, u.pathname); | |
9216 } else { | |
9217 return url; | |
9218 } | |
9219 } | |
9220 | |
9221 // make a relative path from source to target | |
9222 function makeRelPath(source, target) { | |
9223 var s = source.split('/'); | |
9224 var t = target.split('/'); | |
9225 while (s.length && s[0] === t[0]){ | |
9226 s.shift(); | |
9227 t.shift(); | |
9228 } | |
9229 for (var i = 0, l = s.length - 1; i < l; i++) { | |
9230 t.unshift('..'); | |
9231 } | |
9232 return t.join('/'); | |
9233 } | |
9234 | |
9235 // exports | |
9236 scope.urlResolver = urlResolver; | |
9237 | |
9238 })(Platform); | |
9239 | |
9240 /* | |
9241 * Copyright 2012 The Polymer Authors. All rights reserved. | |
9242 * Use of this source code is goverened by a BSD-style | |
9243 * license that can be found in the LICENSE file. | |
9244 */ | |
9245 | |
9246 (function(global) { | |
9247 | |
9248 var registrationsTable = new WeakMap(); | |
9249 | |
9250 // We use setImmediate or postMessage for our future callback. | |
9251 var setImmediate = window.msSetImmediate; | |
9252 | |
9253 // Use post message to emulate setImmediate. | |
9254 if (!setImmediate) { | |
9255 var setImmediateQueue = []; | |
9256 var sentinel = String(Math.random()); | |
9257 window.addEventListener('message', function(e) { | |
9258 if (e.data === sentinel) { | |
9259 var queue = setImmediateQueue; | |
9260 setImmediateQueue = []; | |
9261 queue.forEach(function(func) { | |
9262 func(); | |
9263 }); | |
9264 } | |
9265 }); | |
9266 setImmediate = function(func) { | |
9267 setImmediateQueue.push(func); | |
9268 window.postMessage(sentinel, '*'); | |
9269 }; | |
9270 } | |
9271 | |
9272 // This is used to ensure that we never schedule 2 callas to setImmediate | |
9273 var isScheduled = false; | |
9274 | |
9275 // Keep track of observers that needs to be notified next time. | |
9276 var scheduledObservers = []; | |
9277 | |
9278 /** | |
9279 * Schedules |dispatchCallback| to be called in the future. | |
9280 * @param {MutationObserver} observer | |
9281 */ | |
9282 function scheduleCallback(observer) { | |
9283 scheduledObservers.push(observer); | |
9284 if (!isScheduled) { | |
9285 isScheduled = true; | |
9286 setImmediate(dispatchCallbacks); | |
9287 } | |
9288 } | |
9289 | |
9290 function wrapIfNeeded(node) { | |
9291 return window.ShadowDOMPolyfill && | |
9292 window.ShadowDOMPolyfill.wrapIfNeeded(node) || | |
9293 node; | |
9294 } | |
9295 | |
9296 function dispatchCallbacks() { | |
9297 // http://dom.spec.whatwg.org/#mutation-observers | |
9298 | |
9299 isScheduled = false; // Used to allow a new setImmediate call above. | |
9300 | |
9301 var observers = scheduledObservers; | |
9302 scheduledObservers = []; | |
9303 // Sort observers based on their creation UID (incremental). | |
9304 observers.sort(function(o1, o2) { | |
9305 return o1.uid_ - o2.uid_; | |
9306 }); | |
9307 | |
9308 var anyNonEmpty = false; | |
9309 observers.forEach(function(observer) { | |
9310 | |
9311 // 2.1, 2.2 | |
9312 var queue = observer.takeRecords(); | |
9313 // 2.3. Remove all transient registered observers whose observer is mo. | |
9314 removeTransientObserversFor(observer); | |
9315 | |
9316 // 2.4 | |
9317 if (queue.length) { | |
9318 observer.callback_(queue, observer); | |
9319 anyNonEmpty = true; | |
9320 } | |
9321 }); | |
9322 | |
9323 // 3. | |
9324 if (anyNonEmpty) | |
9325 dispatchCallbacks(); | |
9326 } | |
9327 | |
9328 function removeTransientObserversFor(observer) { | |
9329 observer.nodes_.forEach(function(node) { | |
9330 var registrations = registrationsTable.get(node); | |
9331 if (!registrations) | |
9332 return; | |
9333 registrations.forEach(function(registration) { | |
9334 if (registration.observer === observer) | |
9335 registration.removeTransientObservers(); | |
9336 }); | |
9337 }); | |
9338 } | |
9339 | |
9340 /** | |
9341 * This function is used for the "For each registered observer observer (with | |
9342 * observer's options as options) in target's list of registered observers, | |
9343 * run these substeps:" and the "For each ancestor ancestor of target, and for | |
9344 * each registered observer observer (with options options) in ancestor's list | |
9345 * of registered observers, run these substeps:" part of the algorithms. The | |
9346 * |options.subtree| is checked to ensure that the callback is called | |
9347 * correctly. | |
9348 * | |
9349 * @param {Node} target | |
9350 * @param {function(MutationObserverInit):MutationRecord} callback | |
9351 */ | |
9352 function forEachAncestorAndObserverEnqueueRecord(target, callback) { | |
9353 for (var node = target; node; node = node.parentNode) { | |
9354 var registrations = registrationsTable.get(node); | |
9355 | |
9356 if (registrations) { | |
9357 for (var j = 0; j < registrations.length; j++) { | |
9358 var registration = registrations[j]; | |
9359 var options = registration.options; | |
9360 | |
9361 // Only target ignores subtree. | |
9362 if (node !== target && !options.subtree) | |
9363 continue; | |
9364 | |
9365 var record = callback(options); | |
9366 if (record) | |
9367 registration.enqueue(record); | |
9368 } | |
9369 } | |
9370 } | |
9371 } | |
9372 | |
9373 var uidCounter = 0; | |
9374 | |
9375 /** | |
9376 * The class that maps to the DOM MutationObserver interface. | |
9377 * @param {Function} callback. | |
9378 * @constructor | |
9379 */ | |
9380 function JsMutationObserver(callback) { | |
9381 this.callback_ = callback; | |
9382 this.nodes_ = []; | |
9383 this.records_ = []; | |
9384 this.uid_ = ++uidCounter; | |
9385 } | |
9386 | |
9387 JsMutationObserver.prototype = { | |
9388 observe: function(target, options) { | |
9389 target = wrapIfNeeded(target); | |
9390 | |
9391 // 1.1 | |
9392 if (!options.childList && !options.attributes && !options.characterData || | |
9393 | |
9394 // 1.2 | |
9395 options.attributeOldValue && !options.attributes || | |
9396 | |
9397 // 1.3 | |
9398 options.attributeFilter && options.attributeFilter.length && | |
9399 !options.attributes || | |
9400 | |
9401 // 1.4 | |
9402 options.characterDataOldValue && !options.characterData) { | |
9403 | |
9404 throw new SyntaxError(); | |
9405 } | |
9406 | |
9407 var registrations = registrationsTable.get(target); | |
9408 if (!registrations) | |
9409 registrationsTable.set(target, registrations = []); | |
9410 | |
9411 // 2 | |
9412 // If target's list of registered observers already includes a registered | |
9413 // observer associated with the context object, replace that registered | |
9414 // observer's options with options. | |
9415 var registration; | |
9416 for (var i = 0; i < registrations.length; i++) { | |
9417 if (registrations[i].observer === this) { | |
9418 registration = registrations[i]; | |
9419 registration.removeListeners(); | |
9420 registration.options = options; | |
9421 break; | |
9422 } | |
9423 } | |
9424 | |
9425 // 3. | |
9426 // Otherwise, add a new registered observer to target's list of registered | |
9427 // observers with the context object as the observer and options as the | |
9428 // options, and add target to context object's list of nodes on which it | |
9429 // is registered. | |
9430 if (!registration) { | |
9431 registration = new Registration(this, target, options); | |
9432 registrations.push(registration); | |
9433 this.nodes_.push(target); | |
9434 } | |
9435 | |
9436 registration.addListeners(); | |
9437 }, | |
9438 | |
9439 disconnect: function() { | |
9440 this.nodes_.forEach(function(node) { | |
9441 var registrations = registrationsTable.get(node); | |
9442 for (var i = 0; i < registrations.length; i++) { | |
9443 var registration = registrations[i]; | |
9444 if (registration.observer === this) { | |
9445 registration.removeListeners(); | |
9446 registrations.splice(i, 1); | |
9447 // Each node can only have one registered observer associated with | |
9448 // this observer. | |
9449 break; | |
9450 } | |
9451 } | |
9452 }, this); | |
9453 this.records_ = []; | |
9454 }, | |
9455 | |
9456 takeRecords: function() { | |
9457 var copyOfRecords = this.records_; | |
9458 this.records_ = []; | |
9459 return copyOfRecords; | |
9460 } | |
9461 }; | |
9462 | |
9463 /** | |
9464 * @param {string} type | |
9465 * @param {Node} target | |
9466 * @constructor | |
9467 */ | |
9468 function MutationRecord(type, target) { | |
9469 this.type = type; | |
9470 this.target = target; | |
9471 this.addedNodes = []; | |
9472 this.removedNodes = []; | |
9473 this.previousSibling = null; | |
9474 this.nextSibling = null; | |
9475 this.attributeName = null; | |
9476 this.attributeNamespace = null; | |
9477 this.oldValue = null; | |
9478 } | |
9479 | |
9480 function copyMutationRecord(original) { | |
9481 var record = new MutationRecord(original.type, original.target); | |
9482 record.addedNodes = original.addedNodes.slice(); | |
9483 record.removedNodes = original.removedNodes.slice(); | |
9484 record.previousSibling = original.previousSibling; | |
9485 record.nextSibling = original.nextSibling; | |
9486 record.attributeName = original.attributeName; | |
9487 record.attributeNamespace = original.attributeNamespace; | |
9488 record.oldValue = original.oldValue; | |
9489 return record; | |
9490 }; | |
9491 | |
9492 // We keep track of the two (possibly one) records used in a single mutation. | |
9493 var currentRecord, recordWithOldValue; | |
9494 | |
9495 /** | |
9496 * Creates a record without |oldValue| and caches it as |currentRecord| for | |
9497 * later use. | |
9498 * @param {string} oldValue | |
9499 * @return {MutationRecord} | |
9500 */ | |
9501 function getRecord(type, target) { | |
9502 return currentRecord = new MutationRecord(type, target); | |
9503 } | |
9504 | |
9505 /** | |
9506 * Gets or creates a record with |oldValue| based in the |currentRecord| | |
9507 * @param {string} oldValue | |
9508 * @return {MutationRecord} | |
9509 */ | |
9510 function getRecordWithOldValue(oldValue) { | |
9511 if (recordWithOldValue) | |
9512 return recordWithOldValue; | |
9513 recordWithOldValue = copyMutationRecord(currentRecord); | |
9514 recordWithOldValue.oldValue = oldValue; | |
9515 return recordWithOldValue; | |
9516 } | |
9517 | |
9518 function clearRecords() { | |
9519 currentRecord = recordWithOldValue = undefined; | |
9520 } | |
9521 | |
9522 /** | |
9523 * @param {MutationRecord} record | |
9524 * @return {boolean} Whether the record represents a record from the current | |
9525 * mutation event. | |
9526 */ | |
9527 function recordRepresentsCurrentMutation(record) { | |
9528 return record === recordWithOldValue || record === currentRecord; | |
9529 } | |
9530 | |
9531 /** | |
9532 * Selects which record, if any, to replace the last record in the queue. | |
9533 * This returns |null| if no record should be replaced. | |
9534 * | |
9535 * @param {MutationRecord} lastRecord | |
9536 * @param {MutationRecord} newRecord | |
9537 * @param {MutationRecord} | |
9538 */ | |
9539 function selectRecord(lastRecord, newRecord) { | |
9540 if (lastRecord === newRecord) | |
9541 return lastRecord; | |
9542 | |
9543 // Check if the the record we are adding represents the same record. If | |
9544 // so, we keep the one with the oldValue in it. | |
9545 if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) | |
9546 return recordWithOldValue; | |
9547 | |
9548 return null; | |
9549 } | |
9550 | |
9551 /** | |
9552 * Class used to represent a registered observer. | |
9553 * @param {MutationObserver} observer | |
9554 * @param {Node} target | |
9555 * @param {MutationObserverInit} options | |
9556 * @constructor | |
9557 */ | |
9558 function Registration(observer, target, options) { | |
9559 this.observer = observer; | |
9560 this.target = target; | |
9561 this.options = options; | |
9562 this.transientObservedNodes = []; | |
9563 } | |
9564 | |
9565 Registration.prototype = { | |
9566 enqueue: function(record) { | |
9567 var records = this.observer.records_; | |
9568 var length = records.length; | |
9569 | |
9570 // There are cases where we replace the last record with the new record. | |
9571 // For example if the record represents the same mutation we need to use | |
9572 // the one with the oldValue. If we get same record (this can happen as we | |
9573 // walk up the tree) we ignore the new record. | |
9574 if (records.length > 0) { | |
9575 var lastRecord = records[length - 1]; | |
9576 var recordToReplaceLast = selectRecord(lastRecord, record); | |
9577 if (recordToReplaceLast) { | |
9578 records[length - 1] = recordToReplaceLast; | |
9579 return; | |
9580 } | |
9581 } else { | |
9582 scheduleCallback(this.observer); | |
9583 } | |
9584 | |
9585 records[length] = record; | |
9586 }, | |
9587 | |
9588 addListeners: function() { | |
9589 this.addListeners_(this.target); | |
9590 }, | |
9591 | |
9592 addListeners_: function(node) { | |
9593 var options = this.options; | |
9594 if (options.attributes) | |
9595 node.addEventListener('DOMAttrModified', this, true); | |
9596 | |
9597 if (options.characterData) | |
9598 node.addEventListener('DOMCharacterDataModified', this, true); | |
9599 | |
9600 if (options.childList) | |
9601 node.addEventListener('DOMNodeInserted', this, true); | |
9602 | |
9603 if (options.childList || options.subtree) | |
9604 node.addEventListener('DOMNodeRemoved', this, true); | |
9605 }, | |
9606 | |
9607 removeListeners: function() { | |
9608 this.removeListeners_(this.target); | |
9609 }, | |
9610 | |
9611 removeListeners_: function(node) { | |
9612 var options = this.options; | |
9613 if (options.attributes) | |
9614 node.removeEventListener('DOMAttrModified', this, true); | |
9615 | |
9616 if (options.characterData) | |
9617 node.removeEventListener('DOMCharacterDataModified', this, true); | |
9618 | |
9619 if (options.childList) | |
9620 node.removeEventListener('DOMNodeInserted', this, true); | |
9621 | |
9622 if (options.childList || options.subtree) | |
9623 node.removeEventListener('DOMNodeRemoved', this, true); | |
9624 }, | |
9625 | |
9626 /** | |
9627 * Adds a transient observer on node. The transient observer gets removed | |
9628 * next time we deliver the change records. | |
9629 * @param {Node} node | |
9630 */ | |
9631 addTransientObserver: function(node) { | |
9632 // Don't add transient observers on the target itself. We already have all | |
9633 // the required listeners set up on the target. | |
9634 if (node === this.target) | |
9635 return; | |
9636 | |
9637 this.addListeners_(node); | |
9638 this.transientObservedNodes.push(node); | |
9639 var registrations = registrationsTable.get(node); | |
9640 if (!registrations) | |
9641 registrationsTable.set(node, registrations = []); | |
9642 | |
9643 // We know that registrations does not contain this because we already | |
9644 // checked if node === this.target. | |
9645 registrations.push(this); | |
9646 }, | |
9647 | |
9648 removeTransientObservers: function() { | |
9649 var transientObservedNodes = this.transientObservedNodes; | |
9650 this.transientObservedNodes = []; | |
9651 | |
9652 transientObservedNodes.forEach(function(node) { | |
9653 // Transient observers are never added to the target. | |
9654 this.removeListeners_(node); | |
9655 | |
9656 var registrations = registrationsTable.get(node); | |
9657 for (var i = 0; i < registrations.length; i++) { | |
9658 if (registrations[i] === this) { | |
9659 registrations.splice(i, 1); | |
9660 // Each node can only have one registered observer associated with | |
9661 // this observer. | |
9662 break; | |
9663 } | |
9664 } | |
9665 }, this); | |
9666 }, | |
9667 | |
9668 handleEvent: function(e) { | |
9669 // Stop propagation since we are managing the propagation manually. | |
9670 // This means that other mutation events on the page will not work | |
9671 // correctly but that is by design. | |
9672 e.stopImmediatePropagation(); | |
9673 | |
9674 switch (e.type) { | |
9675 case 'DOMAttrModified': | |
9676 // http://dom.spec.whatwg.org/#concept-mo-queue-attributes | |
9677 | |
9678 var name = e.attrName; | |
9679 var namespace = e.relatedNode.namespaceURI; | |
9680 var target = e.target; | |
9681 | |
9682 // 1. | |
9683 var record = new getRecord('attributes', target); | |
9684 record.attributeName = name; | |
9685 record.attributeNamespace = namespace; | |
9686 | |
9687 // 2. | |
9688 var oldValue = | |
9689 e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; | |
9690 | |
9691 forEachAncestorAndObserverEnqueueRecord(target, function(options) { | |
9692 // 3.1, 4.2 | |
9693 if (!options.attributes) | |
9694 return; | |
9695 | |
9696 // 3.2, 4.3 | |
9697 if (options.attributeFilter && options.attributeFilter.length && | |
9698 options.attributeFilter.indexOf(name) === -1 && | |
9699 options.attributeFilter.indexOf(namespace) === -1) { | |
9700 return; | |
9701 } | |
9702 // 3.3, 4.4 | |
9703 if (options.attributeOldValue) | |
9704 return getRecordWithOldValue(oldValue); | |
9705 | |
9706 // 3.4, 4.5 | |
9707 return record; | |
9708 }); | |
9709 | |
9710 break; | |
9711 | |
9712 case 'DOMCharacterDataModified': | |
9713 // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata | |
9714 var target = e.target; | |
9715 | |
9716 // 1. | |
9717 var record = getRecord('characterData', target); | |
9718 | |
9719 // 2. | |
9720 var oldValue = e.prevValue; | |
9721 | |
9722 | |
9723 forEachAncestorAndObserverEnqueueRecord(target, function(options) { | |
9724 // 3.1, 4.2 | |
9725 if (!options.characterData) | |
9726 return; | |
9727 | |
9728 // 3.2, 4.3 | |
9729 if (options.characterDataOldValue) | |
9730 return getRecordWithOldValue(oldValue); | |
9731 | |
9732 // 3.3, 4.4 | |
9733 return record; | |
9734 }); | |
9735 | |
9736 break; | |
9737 | |
9738 case 'DOMNodeRemoved': | |
9739 this.addTransientObserver(e.target); | |
9740 // Fall through. | |
9741 case 'DOMNodeInserted': | |
9742 // http://dom.spec.whatwg.org/#concept-mo-queue-childlist | |
9743 var target = e.relatedNode; | |
9744 var changedNode = e.target; | |
9745 var addedNodes, removedNodes; | |
9746 if (e.type === 'DOMNodeInserted') { | |
9747 addedNodes = [changedNode]; | |
9748 removedNodes = []; | |
9749 } else { | |
9750 | |
9751 addedNodes = []; | |
9752 removedNodes = [changedNode]; | |
9753 } | |
9754 var previousSibling = changedNode.previousSibling; | |
9755 var nextSibling = changedNode.nextSibling; | |
9756 | |
9757 // 1. | |
9758 var record = getRecord('childList', target); | |
9759 record.addedNodes = addedNodes; | |
9760 record.removedNodes = removedNodes; | |
9761 record.previousSibling = previousSibling; | |
9762 record.nextSibling = nextSibling; | |
9763 | |
9764 forEachAncestorAndObserverEnqueueRecord(target, function(options) { | |
9765 // 2.1, 3.2 | |
9766 if (!options.childList) | |
9767 return; | |
9768 | |
9769 // 2.2, 3.3 | |
9770 return record; | |
9771 }); | |
9772 | |
9773 } | |
9774 | |
9775 clearRecords(); | |
9776 } | |
9777 }; | |
9778 | |
9779 global.JsMutationObserver = JsMutationObserver; | |
9780 | |
9781 if (!global.MutationObserver) | |
9782 global.MutationObserver = JsMutationObserver; | |
9783 | |
9784 | |
9785 })(this); | |
9786 | |
9787 /* | |
9788 * Copyright 2013 The Polymer Authors. All rights reserved. | |
9789 * Use of this source code is governed by a BSD-style | |
9790 * license that can be found in the LICENSE file. | |
9791 */ | |
9792 window.HTMLImports = window.HTMLImports || {flags:{}}; | |
9793 /* | |
9794 * Copyright 2013 The Polymer Authors. All rights reserved. | |
9795 * Use of this source code is governed by a BSD-style | |
9796 * license that can be found in the LICENSE file. | |
9797 */ | |
9798 | |
9799 (function(scope) { | |
9800 | |
9801 // imports | |
9802 var path = scope.path; | |
9803 var xhr = scope.xhr; | |
9804 var flags = scope.flags; | |
9805 | |
9806 // TODO(sorvell): this loader supports a dynamic list of urls | |
9807 // and an oncomplete callback that is called when the loader is done. | |
9808 // The polyfill currently does *not* need this dynamism or the onComplete | |
9809 // concept. Because of this, the loader could be simplified quite a bit. | |
9810 var Loader = function(onLoad, onComplete) { | |
9811 this.cache = {}; | |
9812 this.onload = onLoad; | |
9813 this.oncomplete = onComplete; | |
9814 this.inflight = 0; | |
9815 this.pending = {}; | |
9816 }; | |
9817 | |
9818 Loader.prototype = { | |
9819 addNodes: function(nodes) { | |
9820 // number of transactions to complete | |
9821 this.inflight += nodes.length; | |
9822 // commence transactions | |
9823 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { | |
9824 this.require(n); | |
9825 } | |
9826 // anything to do? | |
9827 this.checkDone(); | |
9828 }, | |
9829 addNode: function(node) { | |
9830 // number of transactions to complete | |
9831 this.inflight++; | |
9832 // commence transactions | |
9833 this.require(node); | |
9834 // anything to do? | |
9835 this.checkDone(); | |
9836 }, | |
9837 require: function(elt) { | |
9838 var url = elt.src || elt.href; | |
9839 // ensure we have a standard url that can be used | |
9840 // reliably for deduping. | |
9841 // TODO(sjmiles): ad-hoc | |
9842 elt.__nodeUrl = url; | |
9843 // deduplication | |
9844 if (!this.dedupe(url, elt)) { | |
9845 // fetch this resource | |
9846 this.fetch(url, elt); | |
9847 } | |
9848 }, | |
9849 dedupe: function(url, elt) { | |
9850 if (this.pending[url]) { | |
9851 // add to list of nodes waiting for inUrl | |
9852 this.pending[url].push(elt); | |
9853 // don't need fetch | |
9854 return true; | |
9855 } | |
9856 var resource; | |
9857 if (this.cache[url]) { | |
9858 this.onload(url, elt, this.cache[url]); | |
9859 // finished this transaction | |
9860 this.tail(); | |
9861 // don't need fetch | |
9862 return true; | |
9863 } | |
9864 // first node waiting for inUrl | |
9865 this.pending[url] = [elt]; | |
9866 // need fetch (not a dupe) | |
9867 return false; | |
9868 }, | |
9869 fetch: function(url, elt) { | |
9870 flags.load && console.log('fetch', url, elt); | |
9871 if (url.match(/^data:/)) { | |
9872 // Handle Data URI Scheme | |
9873 var pieces = url.split(','); | |
9874 var header = pieces[0]; | |
9875 var body = pieces[1]; | |
9876 if(header.indexOf(';base64') > -1) { | |
9877 body = atob(body); | |
9878 } else { | |
9879 body = decodeURIComponent(body); | |
9880 } | |
9881 setTimeout(function() { | |
9882 this.receive(url, elt, null, body); | |
9883 }.bind(this), 0); | |
9884 } else { | |
9885 var receiveXhr = function(err, resource) { | |
9886 this.receive(url, elt, err, resource); | |
9887 }.bind(this); | |
9888 xhr.load(url, receiveXhr); | |
9889 // TODO(sorvell): blocked on) | |
9890 // https://code.google.com/p/chromium/issues/detail?id=257221 | |
9891 // xhr'ing for a document makes scripts in imports runnable; otherwise | |
9892 // they are not; however, it requires that we have doctype=html in | |
9893 // the import which is unacceptable. This is only needed on Chrome | |
9894 // to avoid the bug above. | |
9895 /* | |
9896 if (isDocumentLink(elt)) { | |
9897 xhr.loadDocument(url, receiveXhr); | |
9898 } else { | |
9899 xhr.load(url, receiveXhr); | |
9900 } | |
9901 */ | |
9902 } | |
9903 }, | |
9904 receive: function(url, elt, err, resource) { | |
9905 this.cache[url] = resource; | |
9906 var $p = this.pending[url]; | |
9907 for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) { | |
9908 //if (!err) { | |
9909 this.onload(url, p, resource); | |
9910 //} | |
9911 this.tail(); | |
9912 } | |
9913 this.pending[url] = null; | |
9914 }, | |
9915 tail: function() { | |
9916 --this.inflight; | |
9917 this.checkDone(); | |
9918 }, | |
9919 checkDone: function() { | |
9920 if (!this.inflight) { | |
9921 this.oncomplete(); | |
9922 } | |
9923 } | |
9924 }; | |
9925 | |
9926 xhr = xhr || { | |
9927 async: true, | |
9928 ok: function(request) { | |
9929 return (request.status >= 200 && request.status < 300) | |
9930 || (request.status === 304) | |
9931 || (request.status === 0); | |
9932 }, | |
9933 load: function(url, next, nextContext) { | |
9934 var request = new XMLHttpRequest(); | |
9935 if (scope.flags.debug || scope.flags.bust) { | |
9936 url += '?' + Math.random(); | |
9937 } | |
9938 request.open('GET', url, xhr.async); | |
9939 request.addEventListener('readystatechange', function(e) { | |
9940 if (request.readyState === 4) { | |
9941 next.call(nextContext, !xhr.ok(request) && request, | |
9942 request.response || request.responseText, url); | |
9943 } | |
9944 }); | |
9945 request.send(); | |
9946 return request; | |
9947 }, | |
9948 loadDocument: function(url, next, nextContext) { | |
9949 this.load(url, next, nextContext).responseType = 'document'; | |
9950 } | |
9951 }; | |
9952 | |
9953 // exports | |
9954 scope.xhr = xhr; | |
9955 scope.Loader = Loader; | |
9956 | |
9957 })(window.HTMLImports); | |
9958 | |
9959 /* | |
9960 * Copyright 2013 The Polymer Authors. All rights reserved. | |
9961 * Use of this source code is governed by a BSD-style | |
9962 * license that can be found in the LICENSE file. | |
9963 */ | |
9964 | |
9965 (function(scope) { | |
9966 | |
9967 var IMPORT_LINK_TYPE = 'import'; | |
9968 var flags = scope.flags; | |
9969 var isIe = /Trident/.test(navigator.userAgent); | |
9970 // TODO(sorvell): SD polyfill intrusion | |
9971 var mainDoc = window.ShadowDOMPolyfill ? | |
9972 window.ShadowDOMPolyfill.wrapIfNeeded(document) : document; | |
9973 | |
9974 // importParser | |
9975 // highlander object to manage parsing of imports | |
9976 // parses import related elements | |
9977 // and ensures proper parse order | |
9978 // parse order is enforced by crawling the tree and monitoring which elements | |
9979 // have been parsed; async parsing is also supported. | |
9980 | |
9981 // highlander object for parsing a document tree | |
9982 var importParser = { | |
9983 // parse selectors for main document elements | |
9984 documentSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']', | |
9985 // parse selectors for import document elements | |
9986 importsSelectors: [ | |
9987 'link[rel=' + IMPORT_LINK_TYPE + ']', | |
9988 'link[rel=stylesheet]', | |
9989 'style', | |
9990 'script:not([type])', | |
9991 'script[type="text/javascript"]' | |
9992 ].join(','), | |
9993 map: { | |
9994 link: 'parseLink', | |
9995 script: 'parseScript', | |
9996 style: 'parseStyle' | |
9997 }, | |
9998 // try to parse the next import in the tree | |
9999 parseNext: function() { | |
10000 var next = this.nextToParse(); | |
10001 if (next) { | |
10002 this.parse(next); | |
10003 } | |
10004 }, | |
10005 parse: function(elt) { | |
10006 if (this.isParsed(elt)) { | |
10007 flags.parse && console.log('[%s] is already parsed', elt.localName); | |
10008 return; | |
10009 } | |
10010 var fn = this[this.map[elt.localName]]; | |
10011 if (fn) { | |
10012 this.markParsing(elt); | |
10013 fn.call(this, elt); | |
10014 } | |
10015 }, | |
10016 // only 1 element may be parsed at a time; parsing is async so, each | |
10017 // parsing implementation must inform the system that parsing is complete | |
10018 // via markParsingComplete. | |
10019 markParsing: function(elt) { | |
10020 flags.parse && console.log('parsing', elt); | |
10021 this.parsingElement = elt; | |
10022 }, | |
10023 markParsingComplete: function(elt) { | |
10024 elt.__importParsed = true; | |
10025 if (elt.__importElement) { | |
10026 elt.__importElement.__importParsed = true; | |
10027 } | |
10028 this.parsingElement = null; | |
10029 flags.parse && console.log('completed', elt); | |
10030 this.parseNext(); | |
10031 }, | |
10032 parseImport: function(elt) { | |
10033 elt.import.__importParsed = true; | |
10034 // TODO(sorvell): consider if there's a better way to do this; | |
10035 // expose an imports parsing hook; this is needed, for example, by the | |
10036 // CustomElements polyfill. | |
10037 if (HTMLImports.__importsParsingHook) { | |
10038 HTMLImports.__importsParsingHook(elt); | |
10039 } | |
10040 // fire load event | |
10041 if (elt.__resource) { | |
10042 elt.dispatchEvent(new CustomEvent('load', {bubbles: false})); | |
10043 } else { | |
10044 elt.dispatchEvent(new CustomEvent('error', {bubbles: false})); | |
10045 } | |
10046 // TODO(sorvell): workaround for Safari addEventListener not working | |
10047 // for elements not in the main document. | |
10048 if (elt.__pending) { | |
10049 var fn; | |
10050 while (elt.__pending.length) { | |
10051 fn = elt.__pending.shift(); | |
10052 if (fn) { | |
10053 fn({target: elt}); | |
10054 } | |
10055 } | |
10056 } | |
10057 this.markParsingComplete(elt); | |
10058 }, | |
10059 parseLink: function(linkElt) { | |
10060 if (nodeIsImport(linkElt)) { | |
10061 this.parseImport(linkElt); | |
10062 } else { | |
10063 // make href absolute | |
10064 linkElt.href = linkElt.href; | |
10065 this.parseGeneric(linkElt); | |
10066 } | |
10067 }, | |
10068 parseStyle: function(elt) { | |
10069 // TODO(sorvell): style element load event can just not fire so clone styles | |
10070 var src = elt; | |
10071 elt = cloneStyle(elt); | |
10072 elt.__importElement = src; | |
10073 this.parseGeneric(elt); | |
10074 }, | |
10075 parseGeneric: function(elt) { | |
10076 this.trackElement(elt); | |
10077 document.head.appendChild(elt); | |
10078 }, | |
10079 // tracks when a loadable element has loaded | |
10080 trackElement: function(elt, callback) { | |
10081 var self = this; | |
10082 var done = function(e) { | |
10083 if (callback) { | |
10084 callback(e); | |
10085 } | |
10086 self.markParsingComplete(elt); | |
10087 }; | |
10088 elt.addEventListener('load', done); | |
10089 elt.addEventListener('error', done); | |
10090 | |
10091 // NOTE: IE does not fire "load" event for styles that have already loaded | |
10092 // This is in violation of the spec, so we try our hardest to work around it | |
10093 if (isIe && elt.localName === 'style') { | |
10094 var fakeLoad = false; | |
10095 // If there's not @import in the textContent, assume it has loaded | |
10096 if (elt.textContent.indexOf('@import') == -1) { | |
10097 fakeLoad = true; | |
10098 // if we have a sheet, we have been parsed | |
10099 } else if (elt.sheet) { | |
10100 fakeLoad = true; | |
10101 var csr = elt.sheet.cssRules; | |
10102 var len = csr ? csr.length : 0; | |
10103 // search the rules for @import's | |
10104 for (var i = 0, r; (i < len) && (r = csr[i]); i++) { | |
10105 if (r.type === CSSRule.IMPORT_RULE) { | |
10106 // if every @import has resolved, fake the load | |
10107 fakeLoad = fakeLoad && Boolean(r.styleSheet); | |
10108 } | |
10109 } | |
10110 } | |
10111 // dispatch a fake load event and continue parsing | |
10112 if (fakeLoad) { | |
10113 elt.dispatchEvent(new CustomEvent('load', {bubbles: false})); | |
10114 } | |
10115 } | |
10116 }, | |
10117 // NOTE: execute scripts by injecting them and watching for the load/error | |
10118 // event. Inline scripts are handled via dataURL's because browsers tend to | |
10119 // provide correct parsing errors in this case. If this has any compatibility | |
10120 // issues, we can switch to injecting the inline script with textContent. | |
10121 // Scripts with dataURL's do not appear to generate load events and therefore | |
10122 // we assume they execute synchronously. | |
10123 parseScript: function(scriptElt) { | |
10124 var script = document.createElement('script'); | |
10125 script.__importElement = scriptElt; | |
10126 script.src = scriptElt.src ? scriptElt.src : | |
10127 generateScriptDataUrl(scriptElt); | |
10128 scope.currentScript = scriptElt; | |
10129 this.trackElement(script, function(e) { | |
10130 script.parentNode.removeChild(script); | |
10131 scope.currentScript = null; | |
10132 }); | |
10133 document.head.appendChild(script); | |
10134 }, | |
10135 // determine the next element in the tree which should be parsed | |
10136 nextToParse: function() { | |
10137 return !this.parsingElement && this.nextToParseInDoc(mainDoc); | |
10138 }, | |
10139 nextToParseInDoc: function(doc, link) { | |
10140 var nodes = doc.querySelectorAll(this.parseSelectorsForNode(doc)); | |
10141 for (var i=0, l=nodes.length, p=0, n; (i<l) && (n=nodes[i]); i++) { | |
10142 if (!this.isParsed(n)) { | |
10143 if (this.hasResource(n)) { | |
10144 return nodeIsImport(n) ? this.nextToParseInDoc(n.import, n) : n; | |
10145 } else { | |
10146 return; | |
10147 } | |
10148 } | |
10149 } | |
10150 // all nodes have been parsed, ready to parse import, if any | |
10151 return link; | |
10152 }, | |
10153 // return the set of parse selectors relevant for this node. | |
10154 parseSelectorsForNode: function(node) { | |
10155 var doc = node.ownerDocument || node; | |
10156 return doc === mainDoc ? this.documentSelectors : this.importsSelectors; | |
10157 }, | |
10158 isParsed: function(node) { | |
10159 return node.__importParsed; | |
10160 }, | |
10161 hasResource: function(node) { | |
10162 if (nodeIsImport(node) && !node.import) { | |
10163 return false; | |
10164 } | |
10165 return true; | |
10166 } | |
10167 }; | |
10168 | |
10169 function nodeIsImport(elt) { | |
10170 return (elt.localName === 'link') && (elt.rel === IMPORT_LINK_TYPE); | |
10171 } | |
10172 | |
10173 function generateScriptDataUrl(script) { | |
10174 var scriptContent = generateScriptContent(script), b64; | |
10175 try { | |
10176 b64 = btoa(scriptContent); | |
10177 } catch(e) { | |
10178 b64 = btoa(unescape(encodeURIComponent(scriptContent))); | |
10179 console.warn('Script contained non-latin characters that were forced ' + | |
10180 'to latin. Some characters may be wrong.', script); | |
10181 } | |
10182 return 'data:text/javascript;base64,' + b64; | |
10183 } | |
10184 | |
10185 function generateScriptContent(script) { | |
10186 return script.textContent + generateSourceMapHint(script); | |
10187 } | |
10188 | |
10189 // calculate source map hint | |
10190 function generateSourceMapHint(script) { | |
10191 var moniker = script.__nodeUrl; | |
10192 if (!moniker) { | |
10193 moniker = script.ownerDocument.baseURI; | |
10194 // there could be more than one script this url | |
10195 var tag = '[' + Math.floor((Math.random()+1)*1000) + ']'; | |
10196 // TODO(sjmiles): Polymer hack, should be pluggable if we need to allow | |
10197 // this sort of thing | |
10198 var matches = script.textContent.match(/Polymer\(['"]([^'"]*)/); | |
10199 tag = matches && matches[1] || tag; | |
10200 // tag the moniker | |
10201 moniker += '/' + tag + '.js'; | |
10202 } | |
10203 return '\n//# sourceURL=' + moniker + '\n'; | |
10204 } | |
10205 | |
10206 // style/stylesheet handling | |
10207 | |
10208 // clone style with proper path resolution for main document | |
10209 // NOTE: styles are the only elements that require direct path fixup. | |
10210 function cloneStyle(style) { | |
10211 var clone = style.ownerDocument.createElement('style'); | |
10212 clone.textContent = style.textContent; | |
10213 path.resolveUrlsInStyle(clone); | |
10214 return clone; | |
10215 } | |
10216 | |
10217 // path fixup: style elements in imports must be made relative to the main | |
10218 // document. We fixup url's in url() and @import. | |
10219 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; | |
10220 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; | |
10221 | |
10222 var path = { | |
10223 resolveUrlsInStyle: function(style) { | |
10224 var doc = style.ownerDocument; | |
10225 var resolver = doc.createElement('a'); | |
10226 style.textContent = this.resolveUrlsInCssText(style.textContent, resolver); | |
10227 return style; | |
10228 }, | |
10229 resolveUrlsInCssText: function(cssText, urlObj) { | |
10230 var r = this.replaceUrls(cssText, urlObj, CSS_URL_REGEXP); | |
10231 r = this.replaceUrls(r, urlObj, CSS_IMPORT_REGEXP); | |
10232 return r; | |
10233 }, | |
10234 replaceUrls: function(text, urlObj, regexp) { | |
10235 return text.replace(regexp, function(m, pre, url, post) { | |
10236 var urlPath = url.replace(/["']/g, ''); | |
10237 urlObj.href = urlPath; | |
10238 urlPath = urlObj.href; | |
10239 return pre + '\'' + urlPath + '\'' + post; | |
10240 }); | |
10241 } | |
10242 } | |
10243 | |
10244 // exports | |
10245 scope.parser = importParser; | |
10246 scope.path = path; | |
10247 scope.isIE = isIe; | |
10248 | |
10249 })(HTMLImports); | |
10250 | |
10251 /* | |
10252 * Copyright 2013 The Polymer Authors. All rights reserved. | |
10253 * Use of this source code is governed by a BSD-style | |
10254 * license that can be found in the LICENSE file. | |
10255 */ | |
10256 | |
10257 (function(scope) { | |
10258 | |
10259 var hasNative = ('import' in document.createElement('link')); | |
10260 var useNative = hasNative; | |
10261 var flags = scope.flags; | |
10262 var IMPORT_LINK_TYPE = 'import'; | |
10263 | |
10264 // TODO(sorvell): SD polyfill intrusion | |
10265 var mainDoc = window.ShadowDOMPolyfill ? | |
10266 ShadowDOMPolyfill.wrapIfNeeded(document) : document; | |
10267 | |
10268 if (!useNative) { | |
10269 | |
10270 // imports | |
10271 var xhr = scope.xhr; | |
10272 var Loader = scope.Loader; | |
10273 var parser = scope.parser; | |
10274 | |
10275 // importer | |
10276 // highlander object to manage loading of imports | |
10277 | |
10278 // for any document, importer: | |
10279 // - loads any linked import documents (with deduping) | |
10280 | |
10281 var importer = { | |
10282 documents: {}, | |
10283 // nodes to load in the mian document | |
10284 documentPreloadSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']', | |
10285 // nodes to load in imports | |
10286 importsPreloadSelectors: [ | |
10287 'link[rel=' + IMPORT_LINK_TYPE + ']' | |
10288 ].join(','), | |
10289 loadNode: function(node) { | |
10290 importLoader.addNode(node); | |
10291 }, | |
10292 // load all loadable elements within the parent element | |
10293 loadSubtree: function(parent) { | |
10294 var nodes = this.marshalNodes(parent); | |
10295 // add these nodes to loader's queue | |
10296 importLoader.addNodes(nodes); | |
10297 }, | |
10298 marshalNodes: function(parent) { | |
10299 // all preloadable nodes in inDocument | |
10300 return parent.querySelectorAll(this.loadSelectorsForNode(parent)); | |
10301 }, | |
10302 // find the proper set of load selectors for a given node | |
10303 loadSelectorsForNode: function(node) { | |
10304 var doc = node.ownerDocument || node; | |
10305 return doc === mainDoc ? this.documentPreloadSelectors : | |
10306 this.importsPreloadSelectors; | |
10307 }, | |
10308 loaded: function(url, elt, resource) { | |
10309 flags.load && console.log('loaded', url, elt); | |
10310 // store generic resource | |
10311 // TODO(sorvell): fails for nodes inside <template>.content | |
10312 // see https://code.google.com/p/chromium/issues/detail?id=249381. | |
10313 elt.__resource = resource; | |
10314 if (isDocumentLink(elt)) { | |
10315 var doc = this.documents[url]; | |
10316 // if we've never seen a document at this url | |
10317 if (!doc) { | |
10318 // generate an HTMLDocument from data | |
10319 doc = makeDocument(resource, url); | |
10320 doc.__importLink = elt; | |
10321 // TODO(sorvell): we cannot use MO to detect parsed nodes because | |
10322 // SD polyfill does not report these as mutations. | |
10323 this.bootDocument(doc); | |
10324 // cache document | |
10325 this.documents[url] = doc; | |
10326 } | |
10327 // don't store import record until we're actually loaded | |
10328 // store document resource | |
10329 elt.import = doc; | |
10330 } | |
10331 parser.parseNext(); | |
10332 }, | |
10333 bootDocument: function(doc) { | |
10334 this.loadSubtree(doc); | |
10335 this.observe(doc); | |
10336 parser.parseNext(); | |
10337 }, | |
10338 loadedAll: function() { | |
10339 parser.parseNext(); | |
10340 } | |
10341 }; | |
10342 | |
10343 // loader singleton | |
10344 var importLoader = new Loader(importer.loaded.bind(importer), | |
10345 importer.loadedAll.bind(importer)); | |
10346 | |
10347 function isDocumentLink(elt) { | |
10348 return isLinkRel(elt, IMPORT_LINK_TYPE); | |
10349 } | |
10350 | |
10351 function isLinkRel(elt, rel) { | |
10352 return elt.localName === 'link' && elt.getAttribute('rel') === rel; | |
10353 } | |
10354 | |
10355 function isScript(elt) { | |
10356 return elt.localName === 'script'; | |
10357 } | |
10358 | |
10359 function makeDocument(resource, url) { | |
10360 // create a new HTML document | |
10361 var doc = resource; | |
10362 if (!(doc instanceof Document)) { | |
10363 doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE); | |
10364 } | |
10365 // cache the new document's source url | |
10366 doc._URL = url; | |
10367 // establish a relative path via <base> | |
10368 var base = doc.createElement('base'); | |
10369 base.setAttribute('href', url); | |
10370 // add baseURI support to browsers (IE) that lack it. | |
10371 if (!doc.baseURI) { | |
10372 doc.baseURI = url; | |
10373 } | |
10374 // ensure UTF-8 charset | |
10375 var meta = doc.createElement('meta'); | |
10376 meta.setAttribute('charset', 'utf-8'); | |
10377 | |
10378 doc.head.appendChild(meta); | |
10379 doc.head.appendChild(base); | |
10380 // install HTML last as it may trigger CustomElement upgrades | |
10381 // TODO(sjmiles): problem wrt to template boostrapping below, | |
10382 // template bootstrapping must (?) come before element upgrade | |
10383 // but we cannot bootstrap templates until they are in a document | |
10384 // which is too late | |
10385 if (!(resource instanceof Document)) { | |
10386 // install html | |
10387 doc.body.innerHTML = resource; | |
10388 } | |
10389 // TODO(sorvell): ideally this code is not aware of Template polyfill, | |
10390 // but for now the polyfill needs help to bootstrap these templates | |
10391 if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { | |
10392 HTMLTemplateElement.bootstrap(doc); | |
10393 } | |
10394 return doc; | |
10395 } | |
10396 } else { | |
10397 // do nothing if using native imports | |
10398 var importer = {}; | |
10399 } | |
10400 | |
10401 // NOTE: We cannot polyfill document.currentScript because it's not possible | |
10402 // both to override and maintain the ability to capture the native value; | |
10403 // therefore we choose to expose _currentScript both when native imports | |
10404 // and the polyfill are in use. | |
10405 var currentScriptDescriptor = { | |
10406 get: function() { | |
10407 return HTMLImports.currentScript || document.currentScript; | |
10408 }, | |
10409 configurable: true | |
10410 }; | |
10411 | |
10412 Object.defineProperty(document, '_currentScript', currentScriptDescriptor); | |
10413 Object.defineProperty(mainDoc, '_currentScript', currentScriptDescriptor); | |
10414 | |
10415 // Polyfill document.baseURI for browsers without it. | |
10416 if (!document.baseURI) { | |
10417 var baseURIDescriptor = { | |
10418 get: function() { | |
10419 return window.location.href; | |
10420 }, | |
10421 configurable: true | |
10422 }; | |
10423 | |
10424 Object.defineProperty(document, 'baseURI', baseURIDescriptor); | |
10425 Object.defineProperty(mainDoc, 'baseURI', baseURIDescriptor); | |
10426 } | |
10427 | |
10428 // call a callback when all HTMLImports in the document at call (or at least | |
10429 // document ready) time have loaded. | |
10430 // 1. ensure the document is in a ready state (has dom), then | |
10431 // 2. watch for loading of imports and call callback when done | |
10432 function whenImportsReady(callback, doc) { | |
10433 doc = doc || mainDoc; | |
10434 // if document is loading, wait and try again | |
10435 whenDocumentReady(function() { | |
10436 watchImportsLoad(callback, doc); | |
10437 }, doc); | |
10438 } | |
10439 | |
10440 // call the callback when the document is in a ready state (has dom) | |
10441 var requiredReadyState = HTMLImports.isIE ? 'complete' : 'interactive'; | |
10442 var READY_EVENT = 'readystatechange'; | |
10443 function isDocumentReady(doc) { | |
10444 return (doc.readyState === 'complete' || | |
10445 doc.readyState === requiredReadyState); | |
10446 } | |
10447 | |
10448 // call <callback> when we ensure the document is in a ready state | |
10449 function whenDocumentReady(callback, doc) { | |
10450 if (!isDocumentReady(doc)) { | |
10451 var checkReady = function() { | |
10452 if (doc.readyState === 'complete' || | |
10453 doc.readyState === requiredReadyState) { | |
10454 doc.removeEventListener(READY_EVENT, checkReady); | |
10455 whenDocumentReady(callback, doc); | |
10456 } | |
10457 } | |
10458 doc.addEventListener(READY_EVENT, checkReady); | |
10459 } else if (callback) { | |
10460 callback(); | |
10461 } | |
10462 } | |
10463 | |
10464 // call <callback> when we ensure all imports have loaded | |
10465 function watchImportsLoad(callback, doc) { | |
10466 var imports = doc.querySelectorAll('link[rel=import]'); | |
10467 var loaded = 0, l = imports.length; | |
10468 function checkDone(d) { | |
10469 if (loaded == l) { | |
10470 // go async to ensure parser isn't stuck on a script tag | |
10471 requestAnimationFrame(callback); | |
10472 } | |
10473 } | |
10474 function loadedImport(e) { | |
10475 loaded++; | |
10476 checkDone(); | |
10477 } | |
10478 if (l) { | |
10479 for (var i=0, imp; (i<l) && (imp=imports[i]); i++) { | |
10480 if (isImportLoaded(imp)) { | |
10481 loadedImport.call(imp); | |
10482 } else { | |
10483 imp.addEventListener('load', loadedImport); | |
10484 imp.addEventListener('error', loadedImport); | |
10485 } | |
10486 } | |
10487 } else { | |
10488 checkDone(); | |
10489 } | |
10490 } | |
10491 | |
10492 function isImportLoaded(link) { | |
10493 return useNative ? (link.import && (link.import.readyState !== 'loading')) : | |
10494 link.__importParsed; | |
10495 } | |
10496 | |
10497 // exports | |
10498 scope.hasNative = hasNative; | |
10499 scope.useNative = useNative; | |
10500 scope.importer = importer; | |
10501 scope.whenImportsReady = whenImportsReady; | |
10502 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
10503 scope.isImportLoaded = isImportLoaded; | |
10504 scope.importLoader = importLoader; | |
10505 | |
10506 })(window.HTMLImports); | |
10507 | |
10508 /* | |
10509 Copyright 2013 The Polymer Authors. All rights reserved. | |
10510 Use of this source code is governed by a BSD-style | |
10511 license that can be found in the LICENSE file. | |
10512 */ | |
10513 | |
10514 (function(scope){ | |
10515 | |
10516 var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; | |
10517 var importSelector = 'link[rel=' + IMPORT_LINK_TYPE + ']'; | |
10518 var importer = scope.importer; | |
10519 | |
10520 // we track mutations for addedNodes, looking for imports | |
10521 function handler(mutations) { | |
10522 for (var i=0, l=mutations.length, m; (i<l) && (m=mutations[i]); i++) { | |
10523 if (m.type === 'childList' && m.addedNodes.length) { | |
10524 addedNodes(m.addedNodes); | |
10525 } | |
10526 } | |
10527 } | |
10528 | |
10529 // find loadable elements and add them to the importer | |
10530 function addedNodes(nodes) { | |
10531 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { | |
10532 if (shouldLoadNode(n)) { | |
10533 importer.loadNode(n); | |
10534 } | |
10535 if (n.children && n.children.length) { | |
10536 addedNodes(n.children); | |
10537 } | |
10538 } | |
10539 } | |
10540 | |
10541 function shouldLoadNode(node) { | |
10542 return (node.nodeType === 1) && matches.call(node, | |
10543 importer.loadSelectorsForNode(node)); | |
10544 } | |
10545 | |
10546 // x-plat matches | |
10547 var matches = HTMLElement.prototype.matches || | |
10548 HTMLElement.prototype.matchesSelector || | |
10549 HTMLElement.prototype.webkitMatchesSelector || | |
10550 HTMLElement.prototype.mozMatchesSelector || | |
10551 HTMLElement.prototype.msMatchesSelector; | |
10552 | |
10553 var observer = new MutationObserver(handler); | |
10554 | |
10555 // observe the given root for loadable elements | |
10556 function observe(root) { | |
10557 observer.observe(root, {childList: true, subtree: true}); | |
10558 } | |
10559 | |
10560 // exports | |
10561 // TODO(sorvell): factor so can put on scope | |
10562 scope.observe = observe; | |
10563 importer.observe = observe; | |
10564 | |
10565 })(HTMLImports); | |
10566 | |
10567 /* | |
10568 * Copyright 2013 The Polymer Authors. All rights reserved. | |
10569 * Use of this source code is governed by a BSD-style | |
10570 * license that can be found in the LICENSE file. | |
10571 */ | |
10572 (function(){ | |
10573 | |
10574 // bootstrap | |
10575 | |
10576 // IE shim for CustomEvent | |
10577 if (typeof window.CustomEvent !== 'function') { | |
10578 window.CustomEvent = function(inType, dictionary) { | |
10579 var e = document.createEvent('HTMLEvents'); | |
10580 e.initEvent(inType, | |
10581 dictionary.bubbles === false ? false : true, | |
10582 dictionary.cancelable === false ? false : true, | |
10583 dictionary.detail); | |
10584 return e; | |
10585 }; | |
10586 } | |
10587 | |
10588 // TODO(sorvell): SD polyfill intrusion | |
10589 var doc = window.ShadowDOMPolyfill ? | |
10590 window.ShadowDOMPolyfill.wrapIfNeeded(document) : document; | |
10591 | |
10592 // Fire the 'HTMLImportsLoaded' event when imports in document at load time | |
10593 // have loaded. This event is required to simulate the script blocking | |
10594 // behavior of native imports. A main document script that needs to be sure | |
10595 // imports have loaded should wait for this event. | |
10596 HTMLImports.whenImportsReady(function() { | |
10597 HTMLImports.ready = true; | |
10598 HTMLImports.readyTime = new Date().getTime(); | |
10599 doc.dispatchEvent( | |
10600 new CustomEvent('HTMLImportsLoaded', {bubbles: true}) | |
10601 ); | |
10602 }); | |
10603 | |
10604 | |
10605 // no need to bootstrap the polyfill when native imports is available. | |
10606 if (!HTMLImports.useNative) { | |
10607 function bootstrap() { | |
10608 HTMLImports.importer.bootDocument(doc); | |
10609 } | |
10610 | |
10611 // TODO(sorvell): SD polyfill does *not* generate mutations for nodes added | |
10612 // by the parser. For this reason, we must wait until the dom exists to | |
10613 // bootstrap. | |
10614 if (document.readyState === 'complete' || | |
10615 (document.readyState === 'interactive' && !window.attachEvent)) { | |
10616 bootstrap(); | |
10617 } else { | |
10618 document.addEventListener('DOMContentLoaded', bootstrap); | |
10619 } | |
10620 } | |
10621 | |
10622 })(); | |
10623 | |
10624 /* | |
10625 * Copyright 2013 The Polymer Authors. All rights reserved. | |
10626 * Use of this source code is governed by a BSD-style | |
10627 * license that can be found in the LICENSE file. | |
10628 */ | |
10629 window.CustomElements = window.CustomElements || {flags:{}}; | |
10630 /* | |
10631 Copyright 2013 The Polymer Authors. All rights reserved. | |
10632 Use of this source code is governed by a BSD-style | |
10633 license that can be found in the LICENSE file. | |
10634 */ | |
10635 | |
10636 (function(scope){ | |
10637 | |
10638 var logFlags = window.logFlags || {}; | |
10639 var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none
'; | |
10640 | |
10641 // walk the subtree rooted at node, applying 'find(element, data)' function | |
10642 // to each element | |
10643 // if 'find' returns true for 'element', do not search element's subtree | |
10644 function findAll(node, find, data) { | |
10645 var e = node.firstElementChild; | |
10646 if (!e) { | |
10647 e = node.firstChild; | |
10648 while (e && e.nodeType !== Node.ELEMENT_NODE) { | |
10649 e = e.nextSibling; | |
10650 } | |
10651 } | |
10652 while (e) { | |
10653 if (find(e, data) !== true) { | |
10654 findAll(e, find, data); | |
10655 } | |
10656 e = e.nextElementSibling; | |
10657 } | |
10658 return null; | |
10659 } | |
10660 | |
10661 // walk all shadowRoots on a given node. | |
10662 function forRoots(node, cb) { | |
10663 var root = node.shadowRoot; | |
10664 while(root) { | |
10665 forSubtree(root, cb); | |
10666 root = root.olderShadowRoot; | |
10667 } | |
10668 } | |
10669 | |
10670 // walk the subtree rooted at node, including descent into shadow-roots, | |
10671 // applying 'cb' to each element | |
10672 function forSubtree(node, cb) { | |
10673 //logFlags.dom && node.childNodes && node.childNodes.length && console.group('
subTree: ', node); | |
10674 findAll(node, function(e) { | |
10675 if (cb(e)) { | |
10676 return true; | |
10677 } | |
10678 forRoots(e, cb); | |
10679 }); | |
10680 forRoots(node, cb); | |
10681 //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEn
d(); | |
10682 } | |
10683 | |
10684 // manage lifecycle on added node | |
10685 function added(node) { | |
10686 if (upgrade(node)) { | |
10687 insertedNode(node); | |
10688 return true; | |
10689 } | |
10690 inserted(node); | |
10691 } | |
10692 | |
10693 // manage lifecycle on added node's subtree only | |
10694 function addedSubtree(node) { | |
10695 forSubtree(node, function(e) { | |
10696 if (added(e)) { | |
10697 return true; | |
10698 } | |
10699 }); | |
10700 } | |
10701 | |
10702 // manage lifecycle on added node and it's subtree | |
10703 function addedNode(node) { | |
10704 return added(node) || addedSubtree(node); | |
10705 } | |
10706 | |
10707 // upgrade custom elements at node, if applicable | |
10708 function upgrade(node) { | |
10709 if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) { | |
10710 var type = node.getAttribute('is') || node.localName; | |
10711 var definition = scope.registry[type]; | |
10712 if (definition) { | |
10713 logFlags.dom && console.group('upgrade:', node.localName); | |
10714 scope.upgrade(node); | |
10715 logFlags.dom && console.groupEnd(); | |
10716 return true; | |
10717 } | |
10718 } | |
10719 } | |
10720 | |
10721 function insertedNode(node) { | |
10722 inserted(node); | |
10723 if (inDocument(node)) { | |
10724 forSubtree(node, function(e) { | |
10725 inserted(e); | |
10726 }); | |
10727 } | |
10728 } | |
10729 | |
10730 // TODO(sorvell): on platforms without MutationObserver, mutations may not be | |
10731 // reliable and therefore attached/detached are not reliable. | |
10732 // To make these callbacks less likely to fail, we defer all inserts and removes | |
10733 // to give a chance for elements to be inserted into dom. | |
10734 // This ensures attachedCallback fires for elements that are created and | |
10735 // immediately added to dom. | |
10736 var hasPolyfillMutations = (!window.MutationObserver || | |
10737 (window.MutationObserver === window.JsMutationObserver)); | |
10738 scope.hasPolyfillMutations = hasPolyfillMutations; | |
10739 | |
10740 var isPendingMutations = false; | |
10741 var pendingMutations = []; | |
10742 function deferMutation(fn) { | |
10743 pendingMutations.push(fn); | |
10744 if (!isPendingMutations) { | |
10745 isPendingMutations = true; | |
10746 var async = (window.Platform && window.Platform.endOfMicrotask) || | |
10747 setTimeout; | |
10748 async(takeMutations); | |
10749 } | |
10750 } | |
10751 | |
10752 function takeMutations() { | |
10753 isPendingMutations = false; | |
10754 var $p = pendingMutations; | |
10755 for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) { | |
10756 p(); | |
10757 } | |
10758 pendingMutations = []; | |
10759 } | |
10760 | |
10761 function inserted(element) { | |
10762 if (hasPolyfillMutations) { | |
10763 deferMutation(function() { | |
10764 _inserted(element); | |
10765 }); | |
10766 } else { | |
10767 _inserted(element); | |
10768 } | |
10769 } | |
10770 | |
10771 // TODO(sjmiles): if there are descents into trees that can never have inDocumen
t(*) true, fix this | |
10772 function _inserted(element) { | |
10773 // TODO(sjmiles): it's possible we were inserted and removed in the space | |
10774 // of one microtask, in which case we won't be 'inDocument' here | |
10775 // But there are other cases where we are testing for inserted without | |
10776 // specific knowledge of mutations, and must test 'inDocument' to determine | |
10777 // whether to call inserted | |
10778 // If we can factor these cases into separate code paths we can have | |
10779 // better diagnostics. | |
10780 // TODO(sjmiles): when logging, do work on all custom elements so we can | |
10781 // track behavior even when callbacks not defined | |
10782 //console.log('inserted: ', element.localName); | |
10783 if (element.attachedCallback || element.detachedCallback || (element.__upgrade
d__ && logFlags.dom)) { | |
10784 logFlags.dom && console.group('inserted:', element.localName); | |
10785 if (inDocument(element)) { | |
10786 element.__inserted = (element.__inserted || 0) + 1; | |
10787 // if we are in a 'removed' state, bluntly adjust to an 'inserted' state | |
10788 if (element.__inserted < 1) { | |
10789 element.__inserted = 1; | |
10790 } | |
10791 // if we are 'over inserted', squelch the callback | |
10792 if (element.__inserted > 1) { | |
10793 logFlags.dom && console.warn('inserted:', element.localName, | |
10794 'insert/remove count:', element.__inserted) | |
10795 } else if (element.attachedCallback) { | |
10796 logFlags.dom && console.log('inserted:', element.localName); | |
10797 element.attachedCallback(); | |
10798 } | |
10799 } | |
10800 logFlags.dom && console.groupEnd(); | |
10801 } | |
10802 } | |
10803 | |
10804 function removedNode(node) { | |
10805 removed(node); | |
10806 forSubtree(node, function(e) { | |
10807 removed(e); | |
10808 }); | |
10809 } | |
10810 | |
10811 function removed(element) { | |
10812 if (hasPolyfillMutations) { | |
10813 deferMutation(function() { | |
10814 _removed(element); | |
10815 }); | |
10816 } else { | |
10817 _removed(element); | |
10818 } | |
10819 } | |
10820 | |
10821 function _removed(element) { | |
10822 // TODO(sjmiles): temporary: do work on all custom elements so we can track | |
10823 // behavior even when callbacks not defined | |
10824 if (element.attachedCallback || element.detachedCallback || (element.__upgrade
d__ && logFlags.dom)) { | |
10825 logFlags.dom && console.group('removed:', element.localName); | |
10826 if (!inDocument(element)) { | |
10827 element.__inserted = (element.__inserted || 0) - 1; | |
10828 // if we are in a 'inserted' state, bluntly adjust to an 'removed' state | |
10829 if (element.__inserted > 0) { | |
10830 element.__inserted = 0; | |
10831 } | |
10832 // if we are 'over removed', squelch the callback | |
10833 if (element.__inserted < 0) { | |
10834 logFlags.dom && console.warn('removed:', element.localName, | |
10835 'insert/remove count:', element.__inserted) | |
10836 } else if (element.detachedCallback) { | |
10837 element.detachedCallback(); | |
10838 } | |
10839 } | |
10840 logFlags.dom && console.groupEnd(); | |
10841 } | |
10842 } | |
10843 | |
10844 // SD polyfill intrustion due mainly to the fact that 'document' | |
10845 // is not entirely wrapped | |
10846 function wrapIfNeeded(node) { | |
10847 return window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) | |
10848 : node; | |
10849 } | |
10850 | |
10851 function inDocument(element) { | |
10852 var p = element; | |
10853 var doc = wrapIfNeeded(document); | |
10854 while (p) { | |
10855 if (p == doc) { | |
10856 return true; | |
10857 } | |
10858 p = p.parentNode || p.host; | |
10859 } | |
10860 } | |
10861 | |
10862 function watchShadow(node) { | |
10863 if (node.shadowRoot && !node.shadowRoot.__watched) { | |
10864 logFlags.dom && console.log('watching shadow-root for: ', node.localName); | |
10865 // watch all unwatched roots... | |
10866 var root = node.shadowRoot; | |
10867 while (root) { | |
10868 watchRoot(root); | |
10869 root = root.olderShadowRoot; | |
10870 } | |
10871 } | |
10872 } | |
10873 | |
10874 function watchRoot(root) { | |
10875 if (!root.__watched) { | |
10876 observe(root); | |
10877 root.__watched = true; | |
10878 } | |
10879 } | |
10880 | |
10881 function handler(mutations) { | |
10882 // | |
10883 if (logFlags.dom) { | |
10884 var mx = mutations[0]; | |
10885 if (mx && mx.type === 'childList' && mx.addedNodes) { | |
10886 if (mx.addedNodes) { | |
10887 var d = mx.addedNodes[0]; | |
10888 while (d && d !== document && !d.host) { | |
10889 d = d.parentNode; | |
10890 } | |
10891 var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || ''; | |
10892 u = u.split('/?').shift().split('/').pop(); | |
10893 } | |
10894 } | |
10895 console.group('mutations (%d) [%s]', mutations.length, u || ''); | |
10896 } | |
10897 // | |
10898 mutations.forEach(function(mx) { | |
10899 //logFlags.dom && console.group('mutation'); | |
10900 if (mx.type === 'childList') { | |
10901 forEach(mx.addedNodes, function(n) { | |
10902 //logFlags.dom && console.log(n.localName); | |
10903 if (!n.localName) { | |
10904 return; | |
10905 } | |
10906 // nodes added may need lifecycle management | |
10907 addedNode(n); | |
10908 }); | |
10909 // removed nodes may need lifecycle management | |
10910 forEach(mx.removedNodes, function(n) { | |
10911 //logFlags.dom && console.log(n.localName); | |
10912 if (!n.localName) { | |
10913 return; | |
10914 } | |
10915 removedNode(n); | |
10916 }); | |
10917 } | |
10918 //logFlags.dom && console.groupEnd(); | |
10919 }); | |
10920 logFlags.dom && console.groupEnd(); | |
10921 }; | |
10922 | |
10923 var observer = new MutationObserver(handler); | |
10924 | |
10925 function takeRecords() { | |
10926 // TODO(sjmiles): ask Raf why we have to call handler ourselves | |
10927 handler(observer.takeRecords()); | |
10928 takeMutations(); | |
10929 } | |
10930 | |
10931 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
10932 | |
10933 function observe(inRoot) { | |
10934 observer.observe(inRoot, {childList: true, subtree: true}); | |
10935 } | |
10936 | |
10937 function observeDocument(doc) { | |
10938 observe(doc); | |
10939 } | |
10940 | |
10941 function upgradeDocument(doc) { | |
10942 logFlags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').po
p()); | |
10943 addedNode(doc); | |
10944 logFlags.dom && console.groupEnd(); | |
10945 } | |
10946 | |
10947 function upgradeDocumentTree(doc) { | |
10948 doc = wrapIfNeeded(doc); | |
10949 //console.log('upgradeDocumentTree: ', (doc.baseURI).split('/').pop()); | |
10950 // upgrade contained imported documents | |
10951 var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']'); | |
10952 for (var i=0, l=imports.length, n; (i<l) && (n=imports[i]); i++) { | |
10953 if (n.import && n.import.__parsed) { | |
10954 upgradeDocumentTree(n.import); | |
10955 } | |
10956 } | |
10957 upgradeDocument(doc); | |
10958 } | |
10959 | |
10960 // exports | |
10961 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
10962 scope.watchShadow = watchShadow; | |
10963 scope.upgradeDocumentTree = upgradeDocumentTree; | |
10964 scope.upgradeAll = addedNode; | |
10965 scope.upgradeSubtree = addedSubtree; | |
10966 scope.insertedNode = insertedNode; | |
10967 | |
10968 scope.observeDocument = observeDocument; | |
10969 scope.upgradeDocument = upgradeDocument; | |
10970 | |
10971 scope.takeRecords = takeRecords; | |
10972 | |
10973 })(window.CustomElements); | |
10974 | |
10975 /* | |
10976 * Copyright 2013 The Polymer Authors. All rights reserved. | |
10977 * Use of this source code is governed by a BSD-style | |
10978 * license that can be found in the LICENSE file. | |
10979 */ | |
10980 | |
10981 /** | |
10982 * Implements `document.register` | |
10983 * @module CustomElements | |
10984 */ | |
10985 | |
10986 /** | |
10987 * Polyfilled extensions to the `document` object. | |
10988 * @class Document | |
10989 */ | |
10990 | |
10991 (function(scope) { | |
10992 | |
10993 // imports | |
10994 | |
10995 if (!scope) { | |
10996 scope = window.CustomElements = {flags:{}}; | |
10997 } | |
10998 var flags = scope.flags; | |
10999 | |
11000 // native document.registerElement? | |
11001 | |
11002 var hasNative = Boolean(document.registerElement); | |
11003 // TODO(sorvell): See https://github.com/Polymer/polymer/issues/399 | |
11004 // we'll address this by defaulting to CE polyfill in the presence of the SD | |
11005 // polyfill. This will avoid spamming excess attached/detached callbacks. | |
11006 // If there is a compelling need to run CE native with SD polyfill, | |
11007 // we'll need to fix this issue. | |
11008 var useNative = !flags.register && hasNative && !window.ShadowDOMPolyfill; | |
11009 | |
11010 if (useNative) { | |
11011 | |
11012 // stub | |
11013 var nop = function() {}; | |
11014 | |
11015 // exports | |
11016 scope.registry = {}; | |
11017 scope.upgradeElement = nop; | |
11018 | |
11019 scope.watchShadow = nop; | |
11020 scope.upgrade = nop; | |
11021 scope.upgradeAll = nop; | |
11022 scope.upgradeSubtree = nop; | |
11023 scope.observeDocument = nop; | |
11024 scope.upgradeDocument = nop; | |
11025 scope.upgradeDocumentTree = nop; | |
11026 scope.takeRecords = nop; | |
11027 scope.reservedTagList = []; | |
11028 | |
11029 } else { | |
11030 | |
11031 /** | |
11032 * Registers a custom tag name with the document. | |
11033 * | |
11034 * When a registered element is created, a `readyCallback` method is called | |
11035 * in the scope of the element. The `readyCallback` method can be specified on | |
11036 * either `options.prototype` or `options.lifecycle` with the latter taking | |
11037 * precedence. | |
11038 * | |
11039 * @method register | |
11040 * @param {String} name The tag name to register. Must include a dash ('-'), | |
11041 * for example 'x-component'. | |
11042 * @param {Object} options | |
11043 * @param {String} [options.extends] | |
11044 * (_off spec_) Tag name of an element to extend (or blank for a new | |
11045 * element). This parameter is not part of the specification, but instead | |
11046 * is a hint for the polyfill because the extendee is difficult to infer. | |
11047 * Remember that the input prototype must chain to the extended element's | |
11048 * prototype (or HTMLElement.prototype) regardless of the value of | |
11049 * `extends`. | |
11050 * @param {Object} options.prototype The prototype to use for the new | |
11051 * element. The prototype must inherit from HTMLElement. | |
11052 * @param {Object} [options.lifecycle] | |
11053 * Callbacks that fire at important phases in the life of the custom | |
11054 * element. | |
11055 * | |
11056 * @example | |
11057 * FancyButton = document.registerElement("fancy-button", { | |
11058 * extends: 'button', | |
11059 * prototype: Object.create(HTMLButtonElement.prototype, { | |
11060 * readyCallback: { | |
11061 * value: function() { | |
11062 * console.log("a fancy-button was created", | |
11063 * } | |
11064 * } | |
11065 * }) | |
11066 * }); | |
11067 * @return {Function} Constructor for the newly registered type. | |
11068 */ | |
11069 function register(name, options) { | |
11070 //console.warn('document.registerElement("' + name + '", ', options, ')'); | |
11071 // construct a defintion out of options | |
11072 // TODO(sjmiles): probably should clone options instead of mutating it | |
11073 var definition = options || {}; | |
11074 if (!name) { | |
11075 // TODO(sjmiles): replace with more appropriate error (EricB can probably | |
11076 // offer guidance) | |
11077 throw new Error('document.registerElement: first argument `name` must not
be empty'); | |
11078 } | |
11079 if (name.indexOf('-') < 0) { | |
11080 // TODO(sjmiles): replace with more appropriate error (EricB can probably | |
11081 // offer guidance) | |
11082 throw new Error('document.registerElement: first argument (\'name\') must
contain a dash (\'-\'). Argument provided was \'' + String(name) + '\'.'); | |
11083 } | |
11084 // prevent registering reserved names | |
11085 if (isReservedTag(name)) { | |
11086 throw new Error('Failed to execute \'registerElement\' on \'Document\': Re
gistration failed for type \'' + String(name) + '\'. The type name is invalid.')
; | |
11087 } | |
11088 // elements may only be registered once | |
11089 if (getRegisteredDefinition(name)) { | |
11090 throw new Error('DuplicateDefinitionError: a type with name \'' + String(n
ame) + '\' is already registered'); | |
11091 } | |
11092 // must have a prototype, default to an extension of HTMLElement | |
11093 // TODO(sjmiles): probably should throw if no prototype, check spec | |
11094 if (!definition.prototype) { | |
11095 // TODO(sjmiles): replace with more appropriate error (EricB can probably | |
11096 // offer guidance) | |
11097 throw new Error('Options missing required prototype property'); | |
11098 } | |
11099 // record name | |
11100 definition.__name = name.toLowerCase(); | |
11101 // ensure a lifecycle object so we don't have to null test it | |
11102 definition.lifecycle = definition.lifecycle || {}; | |
11103 // build a list of ancestral custom elements (for native base detection) | |
11104 // TODO(sjmiles): we used to need to store this, but current code only | |
11105 // uses it in 'resolveTagName': it should probably be inlined | |
11106 definition.ancestry = ancestry(definition.extends); | |
11107 // extensions of native specializations of HTMLElement require localName | |
11108 // to remain native, and use secondary 'is' specifier for extension type | |
11109 resolveTagName(definition); | |
11110 // some platforms require modifications to the user-supplied prototype | |
11111 // chain | |
11112 resolvePrototypeChain(definition); | |
11113 // overrides to implement attributeChanged callback | |
11114 overrideAttributeApi(definition.prototype); | |
11115 // 7.1.5: Register the DEFINITION with DOCUMENT | |
11116 registerDefinition(definition.__name, definition); | |
11117 // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE | |
11118 // 7.1.8. Return the output of the previous step. | |
11119 definition.ctor = generateConstructor(definition); | |
11120 definition.ctor.prototype = definition.prototype; | |
11121 // force our .constructor to be our actual constructor | |
11122 definition.prototype.constructor = definition.ctor; | |
11123 // if initial parsing is complete | |
11124 if (scope.ready) { | |
11125 // upgrade any pre-existing nodes of this type | |
11126 scope.upgradeDocumentTree(document); | |
11127 } | |
11128 return definition.ctor; | |
11129 } | |
11130 | |
11131 function isReservedTag(name) { | |
11132 for (var i = 0; i < reservedTagList.length; i++) { | |
11133 if (name === reservedTagList[i]) { | |
11134 return true; | |
11135 } | |
11136 } | |
11137 } | |
11138 | |
11139 var reservedTagList = [ | |
11140 'annotation-xml', 'color-profile', 'font-face', 'font-face-src', | |
11141 'font-face-uri', 'font-face-format', 'font-face-name', 'missing-glyph' | |
11142 ]; | |
11143 | |
11144 function ancestry(extnds) { | |
11145 var extendee = getRegisteredDefinition(extnds); | |
11146 if (extendee) { | |
11147 return ancestry(extendee.extends).concat([extendee]); | |
11148 } | |
11149 return []; | |
11150 } | |
11151 | |
11152 function resolveTagName(definition) { | |
11153 // if we are explicitly extending something, that thing is our | |
11154 // baseTag, unless it represents a custom component | |
11155 var baseTag = definition.extends; | |
11156 // if our ancestry includes custom components, we only have a | |
11157 // baseTag if one of them does | |
11158 for (var i=0, a; (a=definition.ancestry[i]); i++) { | |
11159 baseTag = a.is && a.tag; | |
11160 } | |
11161 // our tag is our baseTag, if it exists, and otherwise just our name | |
11162 definition.tag = baseTag || definition.__name; | |
11163 if (baseTag) { | |
11164 // if there is a base tag, use secondary 'is' specifier | |
11165 definition.is = definition.__name; | |
11166 } | |
11167 } | |
11168 | |
11169 function resolvePrototypeChain(definition) { | |
11170 // if we don't support __proto__ we need to locate the native level | |
11171 // prototype for precise mixing in | |
11172 if (!Object.__proto__) { | |
11173 // default prototype | |
11174 var nativePrototype = HTMLElement.prototype; | |
11175 // work out prototype when using type-extension | |
11176 if (definition.is) { | |
11177 var inst = document.createElement(definition.tag); | |
11178 nativePrototype = Object.getPrototypeOf(inst); | |
11179 } | |
11180 // ensure __proto__ reference is installed at each point on the prototype | |
11181 // chain. | |
11182 // NOTE: On platforms without __proto__, a mixin strategy is used instead | |
11183 // of prototype swizzling. In this case, this generated __proto__ provides | |
11184 // limited support for prototype traversal. | |
11185 var proto = definition.prototype, ancestor; | |
11186 while (proto && (proto !== nativePrototype)) { | |
11187 var ancestor = Object.getPrototypeOf(proto); | |
11188 proto.__proto__ = ancestor; | |
11189 proto = ancestor; | |
11190 } | |
11191 } | |
11192 // cache this in case of mixin | |
11193 definition.native = nativePrototype; | |
11194 } | |
11195 | |
11196 // SECTION 4 | |
11197 | |
11198 function instantiate(definition) { | |
11199 // 4.a.1. Create a new object that implements PROTOTYPE | |
11200 // 4.a.2. Let ELEMENT by this new object | |
11201 // | |
11202 // the custom element instantiation algorithm must also ensure that the | |
11203 // output is a valid DOM element with the proper wrapper in place. | |
11204 // | |
11205 return upgrade(domCreateElement(definition.tag), definition); | |
11206 } | |
11207 | |
11208 function upgrade(element, definition) { | |
11209 // some definitions specify an 'is' attribute | |
11210 if (definition.is) { | |
11211 element.setAttribute('is', definition.is); | |
11212 } | |
11213 // remove 'unresolved' attr, which is a standin for :unresolved. | |
11214 element.removeAttribute('unresolved'); | |
11215 // make 'element' implement definition.prototype | |
11216 implement(element, definition); | |
11217 // flag as upgraded | |
11218 element.__upgraded__ = true; | |
11219 // lifecycle management | |
11220 created(element); | |
11221 // attachedCallback fires in tree order, call before recursing | |
11222 scope.insertedNode(element); | |
11223 // there should never be a shadow root on element at this point | |
11224 scope.upgradeSubtree(element); | |
11225 // OUTPUT | |
11226 return element; | |
11227 } | |
11228 | |
11229 function implement(element, definition) { | |
11230 // prototype swizzling is best | |
11231 if (Object.__proto__) { | |
11232 element.__proto__ = definition.prototype; | |
11233 } else { | |
11234 // where above we can re-acquire inPrototype via | |
11235 // getPrototypeOf(Element), we cannot do so when | |
11236 // we use mixin, so we install a magic reference | |
11237 customMixin(element, definition.prototype, definition.native); | |
11238 element.__proto__ = definition.prototype; | |
11239 } | |
11240 } | |
11241 | |
11242 function customMixin(inTarget, inSrc, inNative) { | |
11243 // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of | |
11244 // any property. This set should be precalculated. We also need to | |
11245 // consider this for supporting 'super'. | |
11246 var used = {}; | |
11247 // start with inSrc | |
11248 var p = inSrc; | |
11249 // The default is HTMLElement.prototype, so we add a test to avoid mixing in | |
11250 // native prototypes | |
11251 while (p !== inNative && p !== HTMLElement.prototype) { | |
11252 var keys = Object.getOwnPropertyNames(p); | |
11253 for (var i=0, k; k=keys[i]; i++) { | |
11254 if (!used[k]) { | |
11255 Object.defineProperty(inTarget, k, | |
11256 Object.getOwnPropertyDescriptor(p, k)); | |
11257 used[k] = 1; | |
11258 } | |
11259 } | |
11260 p = Object.getPrototypeOf(p); | |
11261 } | |
11262 } | |
11263 | |
11264 function created(element) { | |
11265 // invoke createdCallback | |
11266 if (element.createdCallback) { | |
11267 element.createdCallback(); | |
11268 } | |
11269 } | |
11270 | |
11271 // attribute watching | |
11272 | |
11273 function overrideAttributeApi(prototype) { | |
11274 // overrides to implement callbacks | |
11275 // TODO(sjmiles): should support access via .attributes NamedNodeMap | |
11276 // TODO(sjmiles): preserves user defined overrides, if any | |
11277 if (prototype.setAttribute._polyfilled) { | |
11278 return; | |
11279 } | |
11280 var setAttribute = prototype.setAttribute; | |
11281 prototype.setAttribute = function(name, value) { | |
11282 changeAttribute.call(this, name, value, setAttribute); | |
11283 } | |
11284 var removeAttribute = prototype.removeAttribute; | |
11285 prototype.removeAttribute = function(name) { | |
11286 changeAttribute.call(this, name, null, removeAttribute); | |
11287 } | |
11288 prototype.setAttribute._polyfilled = true; | |
11289 } | |
11290 | |
11291 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/ | |
11292 // index.html#dfn-attribute-changed-callback | |
11293 function changeAttribute(name, value, operation) { | |
11294 var oldValue = this.getAttribute(name); | |
11295 operation.apply(this, arguments); | |
11296 var newValue = this.getAttribute(name); | |
11297 if (this.attributeChangedCallback | |
11298 && (newValue !== oldValue)) { | |
11299 this.attributeChangedCallback(name, oldValue, newValue); | |
11300 } | |
11301 } | |
11302 | |
11303 // element registry (maps tag names to definitions) | |
11304 | |
11305 var registry = {}; | |
11306 | |
11307 function getRegisteredDefinition(name) { | |
11308 if (name) { | |
11309 return registry[name.toLowerCase()]; | |
11310 } | |
11311 } | |
11312 | |
11313 function registerDefinition(name, definition) { | |
11314 registry[name] = definition; | |
11315 } | |
11316 | |
11317 function generateConstructor(definition) { | |
11318 return function() { | |
11319 return instantiate(definition); | |
11320 }; | |
11321 } | |
11322 | |
11323 var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; | |
11324 function createElementNS(namespace, tag, typeExtension) { | |
11325 // NOTE: we do not support non-HTML elements, | |
11326 // just call createElementNS for non HTML Elements | |
11327 if (namespace === HTML_NAMESPACE) { | |
11328 return createElement(tag, typeExtension); | |
11329 } else { | |
11330 return domCreateElementNS(namespace, tag); | |
11331 } | |
11332 } | |
11333 | |
11334 function createElement(tag, typeExtension) { | |
11335 // TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could | |
11336 // error check it, or perhaps there should only ever be one argument | |
11337 var definition = getRegisteredDefinition(typeExtension || tag); | |
11338 if (definition) { | |
11339 if (tag == definition.tag && typeExtension == definition.is) { | |
11340 return new definition.ctor(); | |
11341 } | |
11342 // Handle empty string for type extension. | |
11343 if (!typeExtension && !definition.is) { | |
11344 return new definition.ctor(); | |
11345 } | |
11346 } | |
11347 | |
11348 if (typeExtension) { | |
11349 var element = createElement(tag); | |
11350 element.setAttribute('is', typeExtension); | |
11351 return element; | |
11352 } | |
11353 var element = domCreateElement(tag); | |
11354 // Custom tags should be HTMLElements even if not upgraded. | |
11355 if (tag.indexOf('-') >= 0) { | |
11356 implement(element, HTMLElement); | |
11357 } | |
11358 return element; | |
11359 } | |
11360 | |
11361 function upgradeElement(element) { | |
11362 if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) { | |
11363 var is = element.getAttribute('is'); | |
11364 var definition = getRegisteredDefinition(is || element.localName); | |
11365 if (definition) { | |
11366 if (is && definition.tag == element.localName) { | |
11367 return upgrade(element, definition); | |
11368 } else if (!is && !definition.extends) { | |
11369 return upgrade(element, definition); | |
11370 } | |
11371 } | |
11372 } | |
11373 } | |
11374 | |
11375 function cloneNode(deep) { | |
11376 // call original clone | |
11377 var n = domCloneNode.call(this, deep); | |
11378 // upgrade the element and subtree | |
11379 scope.upgradeAll(n); | |
11380 // return the clone | |
11381 return n; | |
11382 } | |
11383 // capture native createElement before we override it | |
11384 | |
11385 var domCreateElement = document.createElement.bind(document); | |
11386 var domCreateElementNS = document.createElementNS.bind(document); | |
11387 | |
11388 // capture native cloneNode before we override it | |
11389 | |
11390 var domCloneNode = Node.prototype.cloneNode; | |
11391 | |
11392 // exports | |
11393 | |
11394 document.registerElement = register; | |
11395 document.createElement = createElement; // override | |
11396 document.createElementNS = createElementNS; // override | |
11397 Node.prototype.cloneNode = cloneNode; // override | |
11398 | |
11399 scope.registry = registry; | |
11400 | |
11401 /** | |
11402 * Upgrade an element to a custom element. Upgrading an element | |
11403 * causes the custom prototype to be applied, an `is` attribute | |
11404 * to be attached (as needed), and invocation of the `readyCallback`. | |
11405 * `upgrade` does nothing if the element is already upgraded, or | |
11406 * if it matches no registered custom tag name. | |
11407 * | |
11408 * @method ugprade | |
11409 * @param {Element} element The element to upgrade. | |
11410 * @return {Element} The upgraded element. | |
11411 */ | |
11412 scope.upgrade = upgradeElement; | |
11413 } | |
11414 | |
11415 // Create a custom 'instanceof'. This is necessary when CustomElements | |
11416 // are implemented via a mixin strategy, as for example on IE10. | |
11417 var isInstance; | |
11418 if (!Object.__proto__ && !useNative) { | |
11419 isInstance = function(obj, ctor) { | |
11420 var p = obj; | |
11421 while (p) { | |
11422 // NOTE: this is not technically correct since we're not checking if | |
11423 // an object is an instance of a constructor; however, this should | |
11424 // be good enough for the mixin strategy. | |
11425 if (p === ctor.prototype) { | |
11426 return true; | |
11427 } | |
11428 p = p.__proto__; | |
11429 } | |
11430 return false; | |
11431 } | |
11432 } else { | |
11433 isInstance = function(obj, base) { | |
11434 return obj instanceof base; | |
11435 } | |
11436 } | |
11437 | |
11438 // exports | |
11439 scope.instanceof = isInstance; | |
11440 scope.reservedTagList = reservedTagList; | |
11441 | |
11442 // bc | |
11443 document.register = document.registerElement; | |
11444 | |
11445 scope.hasNative = hasNative; | |
11446 scope.useNative = useNative; | |
11447 | |
11448 })(window.CustomElements); | |
11449 | |
11450 /* | |
11451 * Copyright 2013 The Polymer Authors. All rights reserved. | |
11452 * Use of this source code is governed by a BSD-style | |
11453 * license that can be found in the LICENSE file. | |
11454 */ | |
11455 | |
11456 (function(scope) { | |
11457 | |
11458 // import | |
11459 | |
11460 var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; | |
11461 | |
11462 // highlander object for parsing a document tree | |
11463 | |
11464 var parser = { | |
11465 selectors: [ | |
11466 'link[rel=' + IMPORT_LINK_TYPE + ']' | |
11467 ], | |
11468 map: { | |
11469 link: 'parseLink' | |
11470 }, | |
11471 parse: function(inDocument) { | |
11472 if (!inDocument.__parsed) { | |
11473 // only parse once | |
11474 inDocument.__parsed = true; | |
11475 // all parsable elements in inDocument (depth-first pre-order traversal) | |
11476 var elts = inDocument.querySelectorAll(parser.selectors); | |
11477 // for each parsable node type, call the mapped parsing method | |
11478 forEach(elts, function(e) { | |
11479 parser[parser.map[e.localName]](e); | |
11480 }); | |
11481 // upgrade all upgradeable static elements, anything dynamically | |
11482 // created should be caught by observer | |
11483 CustomElements.upgradeDocument(inDocument); | |
11484 // observe document for dom changes | |
11485 CustomElements.observeDocument(inDocument); | |
11486 } | |
11487 }, | |
11488 parseLink: function(linkElt) { | |
11489 // imports | |
11490 if (isDocumentLink(linkElt)) { | |
11491 this.parseImport(linkElt); | |
11492 } | |
11493 }, | |
11494 parseImport: function(linkElt) { | |
11495 if (linkElt.import) { | |
11496 parser.parse(linkElt.import); | |
11497 } | |
11498 } | |
11499 }; | |
11500 | |
11501 function isDocumentLink(inElt) { | |
11502 return (inElt.localName === 'link' | |
11503 && inElt.getAttribute('rel') === IMPORT_LINK_TYPE); | |
11504 } | |
11505 | |
11506 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
11507 | |
11508 // exports | |
11509 | |
11510 scope.parser = parser; | |
11511 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
11512 | |
11513 })(window.CustomElements); | |
11514 /* | |
11515 * Copyright 2013 The Polymer Authors. All rights reserved. | |
11516 * Use of this source code is governed by a BSD-style | |
11517 * license that can be found in the LICENSE file. | |
11518 */ | |
11519 (function(scope){ | |
11520 | |
11521 // bootstrap parsing | |
11522 function bootstrap() { | |
11523 // parse document | |
11524 CustomElements.parser.parse(document); | |
11525 // one more pass before register is 'live' | |
11526 CustomElements.upgradeDocument(document); | |
11527 // choose async | |
11528 var async = window.Platform && Platform.endOfMicrotask ? | |
11529 Platform.endOfMicrotask : | |
11530 setTimeout; | |
11531 async(function() { | |
11532 // set internal 'ready' flag, now document.registerElement will trigger | |
11533 // synchronous upgrades | |
11534 CustomElements.ready = true; | |
11535 // capture blunt profiling data | |
11536 CustomElements.readyTime = Date.now(); | |
11537 if (window.HTMLImports) { | |
11538 CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime; | |
11539 } | |
11540 // notify the system that we are bootstrapped | |
11541 document.dispatchEvent( | |
11542 new CustomEvent('WebComponentsReady', {bubbles: true}) | |
11543 ); | |
11544 | |
11545 // install upgrade hook if HTMLImports are available | |
11546 if (window.HTMLImports) { | |
11547 HTMLImports.__importsParsingHook = function(elt) { | |
11548 CustomElements.parser.parse(elt.import); | |
11549 } | |
11550 } | |
11551 }); | |
11552 } | |
11553 | |
11554 // CustomEvent shim for IE | |
11555 if (typeof window.CustomEvent !== 'function') { | |
11556 window.CustomEvent = function(inType) { | |
11557 var e = document.createEvent('HTMLEvents'); | |
11558 e.initEvent(inType, true, true); | |
11559 return e; | |
11560 }; | |
11561 } | |
11562 | |
11563 // When loading at readyState complete time (or via flag), boot custom elements | |
11564 // immediately. | |
11565 // If relevant, HTMLImports must already be loaded. | |
11566 if (document.readyState === 'complete' || scope.flags.eager) { | |
11567 bootstrap(); | |
11568 // When loading at readyState interactive time, bootstrap only if HTMLImports | |
11569 // are not pending. Also avoid IE as the semantics of this state are unreliable. | |
11570 } else if (document.readyState === 'interactive' && !window.attachEvent && | |
11571 (!window.HTMLImports || window.HTMLImports.ready)) { | |
11572 bootstrap(); | |
11573 // When loading at other readyStates, wait for the appropriate DOM event to | |
11574 // bootstrap. | |
11575 } else { | |
11576 var loadEvent = window.HTMLImports && !HTMLImports.ready ? | |
11577 'HTMLImportsLoaded' : 'DOMContentLoaded'; | |
11578 window.addEventListener(loadEvent, bootstrap); | |
11579 } | |
11580 | |
11581 })(window.CustomElements); | |
11582 | |
11583 /* | |
11584 * Copyright 2013 The Polymer Authors. All rights reserved. | |
11585 * Use of this source code is governed by a BSD-style | |
11586 * license that can be found in the LICENSE file. | |
11587 */ | |
11588 (function() { | |
11589 | |
11590 if (window.ShadowDOMPolyfill) { | |
11591 | |
11592 // ensure wrapped inputs for these functions | |
11593 var fns = ['upgradeAll', 'upgradeSubtree', 'observeDocument', | |
11594 'upgradeDocument']; | |
11595 | |
11596 // cache originals | |
11597 var original = {}; | |
11598 fns.forEach(function(fn) { | |
11599 original[fn] = CustomElements[fn]; | |
11600 }); | |
11601 | |
11602 // override | |
11603 fns.forEach(function(fn) { | |
11604 CustomElements[fn] = function(inNode) { | |
11605 return original[fn](wrap(inNode)); | |
11606 }; | |
11607 }); | |
11608 | |
11609 } | |
11610 | |
11611 })(); | |
11612 | |
11613 /* | |
11614 * Copyright 2014 The Polymer Authors. All rights reserved. | |
11615 * Use of this source code is governed by a BSD-style | |
11616 * license that can be found in the LICENSE file. | |
11617 */ | |
11618 (function(scope) { | |
11619 var endOfMicrotask = scope.endOfMicrotask; | |
11620 | |
11621 // Generic url loader | |
11622 function Loader(regex) { | |
11623 this.regex = regex; | |
11624 } | |
11625 Loader.prototype = { | |
11626 // TODO(dfreedm): there may be a better factoring here | |
11627 // extract absolute urls from the text (full of relative urls) | |
11628 extractUrls: function(text, base) { | |
11629 var matches = []; | |
11630 var matched, u; | |
11631 while ((matched = this.regex.exec(text))) { | |
11632 u = new URL(matched[1], base); | |
11633 matches.push({matched: matched[0], url: u.href}); | |
11634 } | |
11635 return matches; | |
11636 }, | |
11637 // take a text blob, a root url, and a callback and load all the urls found
within the text | |
11638 // returns a map of absolute url to text | |
11639 process: function(text, root, callback) { | |
11640 var matches = this.extractUrls(text, root); | |
11641 this.fetch(matches, {}, callback); | |
11642 }, | |
11643 // build a mapping of url -> text from matches | |
11644 fetch: function(matches, map, callback) { | |
11645 var inflight = matches.length; | |
11646 | |
11647 // return early if there is no fetching to be done | |
11648 if (!inflight) { | |
11649 return callback(map); | |
11650 } | |
11651 | |
11652 var done = function() { | |
11653 if (--inflight === 0) { | |
11654 callback(map); | |
11655 } | |
11656 }; | |
11657 | |
11658 // map url -> responseText | |
11659 var handleXhr = function(err, request) { | |
11660 var match = request.match; | |
11661 var key = match.url; | |
11662 // handle errors with an empty string | |
11663 if (err) { | |
11664 map[key] = ''; | |
11665 return done(); | |
11666 } | |
11667 var response = request.response || request.responseText; | |
11668 map[key] = response; | |
11669 this.fetch(this.extractUrls(response, key), map, done); | |
11670 }; | |
11671 | |
11672 var m, req, url; | |
11673 for (var i = 0; i < inflight; i++) { | |
11674 m = matches[i]; | |
11675 url = m.url; | |
11676 // if this url has already been requested, skip requesting it again | |
11677 if (map[url]) { | |
11678 // Async call to done to simplify the inflight logic | |
11679 endOfMicrotask(done); | |
11680 continue; | |
11681 } | |
11682 req = this.xhr(url, handleXhr, this); | |
11683 req.match = m; | |
11684 // tag the map with an XHR request to deduplicate at the same level | |
11685 map[url] = req; | |
11686 } | |
11687 }, | |
11688 xhr: function(url, callback, scope) { | |
11689 var request = new XMLHttpRequest(); | |
11690 request.open('GET', url, true); | |
11691 request.send(); | |
11692 request.onload = function() { | |
11693 callback.call(scope, null, request); | |
11694 }; | |
11695 request.onerror = function() { | |
11696 callback.call(scope, null, request); | |
11697 }; | |
11698 return request; | |
11699 } | |
11700 }; | |
11701 | |
11702 scope.Loader = Loader; | |
11703 })(window.Platform); | |
11704 | |
11705 /* | |
11706 * Copyright 2014 The Polymer Authors. All rights reserved. | |
11707 * Use of this source code is governed by a BSD-style | |
11708 * license that can be found in the LICENSE file. | |
11709 */ | |
11710 (function(scope) { | |
11711 | |
11712 var urlResolver = scope.urlResolver; | |
11713 var Loader = scope.Loader; | |
11714 | |
11715 function StyleResolver() { | |
11716 this.loader = new Loader(this.regex); | |
11717 } | |
11718 StyleResolver.prototype = { | |
11719 regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g, | |
11720 // Recursively replace @imports with the text at that url | |
11721 resolve: function(text, url, callback) { | |
11722 var done = function(map) { | |
11723 callback(this.flatten(text, url, map)); | |
11724 }.bind(this); | |
11725 this.loader.process(text, url, done); | |
11726 }, | |
11727 // resolve the textContent of a style node | |
11728 resolveNode: function(style, callback) { | |
11729 var text = style.textContent; | |
11730 var url = style.ownerDocument.baseURI; | |
11731 var done = function(text) { | |
11732 style.textContent = text; | |
11733 callback(style); | |
11734 }; | |
11735 this.resolve(text, url, done); | |
11736 }, | |
11737 // flatten all the @imports to text | |
11738 flatten: function(text, base, map) { | |
11739 var matches = this.loader.extractUrls(text, base); | |
11740 var match, url, intermediate; | |
11741 for (var i = 0; i < matches.length; i++) { | |
11742 match = matches[i]; | |
11743 url = match.url; | |
11744 // resolve any css text to be relative to the importer | |
11745 intermediate = urlResolver.resolveCssText(map[url], url); | |
11746 // flatten intermediate @imports | |
11747 intermediate = this.flatten(intermediate, url, map); | |
11748 text = text.replace(match.matched, intermediate); | |
11749 } | |
11750 return text; | |
11751 }, | |
11752 loadStyles: function(styles, callback) { | |
11753 var loaded=0, l = styles.length; | |
11754 // called in the context of the style | |
11755 function loadedStyle(style) { | |
11756 loaded++; | |
11757 if (loaded === l && callback) { | |
11758 callback(); | |
11759 } | |
11760 } | |
11761 for (var i=0, s; (i<l) && (s=styles[i]); i++) { | |
11762 this.resolveNode(s, loadedStyle); | |
11763 } | |
11764 } | |
11765 }; | |
11766 | |
11767 var styleResolver = new StyleResolver(); | |
11768 | |
11769 // exports | |
11770 scope.styleResolver = styleResolver; | |
11771 | |
11772 })(window.Platform); | |
11773 | |
11774 /* | |
11775 * Copyright 2013 The Polymer Authors. All rights reserved. | |
11776 * Use of this source code is governed by a BSD-style | |
11777 * license that can be found in the LICENSE file. | |
11778 */ | |
11779 | |
11780 (function(scope) { | |
11781 scope = scope || {}; | |
11782 scope.external = scope.external || {}; | |
11783 var target = { | |
11784 shadow: function(inEl) { | |
11785 if (inEl) { | |
11786 return inEl.shadowRoot || inEl.webkitShadowRoot; | |
11787 } | |
11788 }, | |
11789 canTarget: function(shadow) { | |
11790 return shadow && Boolean(shadow.elementFromPoint); | |
11791 }, | |
11792 targetingShadow: function(inEl) { | |
11793 var s = this.shadow(inEl); | |
11794 if (this.canTarget(s)) { | |
11795 return s; | |
11796 } | |
11797 }, | |
11798 olderShadow: function(shadow) { | |
11799 var os = shadow.olderShadowRoot; | |
11800 if (!os) { | |
11801 var se = shadow.querySelector('shadow'); | |
11802 if (se) { | |
11803 os = se.olderShadowRoot; | |
11804 } | |
11805 } | |
11806 return os; | |
11807 }, | |
11808 allShadows: function(element) { | |
11809 var shadows = [], s = this.shadow(element); | |
11810 while(s) { | |
11811 shadows.push(s); | |
11812 s = this.olderShadow(s); | |
11813 } | |
11814 return shadows; | |
11815 }, | |
11816 searchRoot: function(inRoot, x, y) { | |
11817 if (inRoot) { | |
11818 var t = inRoot.elementFromPoint(x, y); | |
11819 var st, sr, os; | |
11820 // is element a shadow host? | |
11821 sr = this.targetingShadow(t); | |
11822 while (sr) { | |
11823 // find the the element inside the shadow root | |
11824 st = sr.elementFromPoint(x, y); | |
11825 if (!st) { | |
11826 // check for older shadows | |
11827 sr = this.olderShadow(sr); | |
11828 } else { | |
11829 // shadowed element may contain a shadow root | |
11830 var ssr = this.targetingShadow(st); | |
11831 return this.searchRoot(ssr, x, y) || st; | |
11832 } | |
11833 } | |
11834 // light dom element is the target | |
11835 return t; | |
11836 } | |
11837 }, | |
11838 owner: function(element) { | |
11839 var s = element; | |
11840 // walk up until you hit the shadow root or document | |
11841 while (s.parentNode) { | |
11842 s = s.parentNode; | |
11843 } | |
11844 // the owner element is expected to be a Document or ShadowRoot | |
11845 if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGME
NT_NODE) { | |
11846 s = document; | |
11847 } | |
11848 return s; | |
11849 }, | |
11850 findTarget: function(inEvent) { | |
11851 var x = inEvent.clientX, y = inEvent.clientY; | |
11852 // if the listener is in the shadow root, it is much faster to start there | |
11853 var s = this.owner(inEvent.target); | |
11854 // if x, y is not in this root, fall back to document search | |
11855 if (!s.elementFromPoint(x, y)) { | |
11856 s = document; | |
11857 } | |
11858 return this.searchRoot(s, x, y); | |
11859 } | |
11860 }; | |
11861 scope.targetFinding = target; | |
11862 scope.findTarget = target.findTarget.bind(target); | |
11863 | |
11864 window.PointerEventsPolyfill = scope; | |
11865 })(window.PointerEventsPolyfill); | |
11866 | |
11867 /* | |
11868 * Copyright 2013 The Polymer Authors. All rights reserved. | |
11869 * Use of this source code is governed by a BSD-style | |
11870 * license that can be found in the LICENSE file. | |
11871 */ | |
11872 (function() { | |
11873 function shadowSelector(v) { | |
11874 return 'body /shadow-deep/ ' + selector(v); | |
11875 } | |
11876 function selector(v) { | |
11877 return '[touch-action="' + v + '"]'; | |
11878 } | |
11879 function rule(v) { | |
11880 return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + '; touch-action
-delay: none; }'; | |
11881 } | |
11882 var attrib2css = [ | |
11883 'none', | |
11884 'auto', | |
11885 'pan-x', | |
11886 'pan-y', | |
11887 { | |
11888 rule: 'pan-x pan-y', | |
11889 selectors: [ | |
11890 'pan-x pan-y', | |
11891 'pan-y pan-x' | |
11892 ] | |
11893 } | |
11894 ]; | |
11895 var styles = ''; | |
11896 // only install stylesheet if the browser has touch action support | |
11897 var head = document.head; | |
11898 var hasNativePE = window.PointerEvent || window.MSPointerEvent; | |
11899 // only add shadow selectors if shadowdom is supported | |
11900 var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoo
t; | |
11901 | |
11902 if (hasNativePE) { | |
11903 attrib2css.forEach(function(r) { | |
11904 if (String(r) === r) { | |
11905 styles += selector(r) + rule(r) + '\n'; | |
11906 if (hasShadowRoot) { | |
11907 styles += shadowSelector(r) + rule(r) + '\n'; | |
11908 } | |
11909 } else { | |
11910 styles += r.selectors.map(selector) + rule(r.rule) + '\n'; | |
11911 if (hasShadowRoot) { | |
11912 styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; | |
11913 } | |
11914 } | |
11915 }); | |
11916 | |
11917 var el = document.createElement('style'); | |
11918 el.textContent = styles; | |
11919 document.head.appendChild(el); | |
11920 } | |
11921 })(); | |
11922 | |
11923 /* | |
11924 * Copyright 2013 The Polymer Authors. All rights reserved. | |
11925 * Use of this source code is governed by a BSD-style | |
11926 * license that can be found in the LICENSE file. | |
11927 */ | |
11928 | |
11929 /** | |
11930 * This is the constructor for new PointerEvents. | |
11931 * | |
11932 * New Pointer Events must be given a type, and an optional dictionary of | |
11933 * initialization properties. | |
11934 * | |
11935 * Due to certain platform requirements, events returned from the constructor | |
11936 * identify as MouseEvents. | |
11937 * | |
11938 * @constructor | |
11939 * @param {String} inType The type of the event to create. | |
11940 * @param {Object} [inDict] An optional dictionary of initial event properties. | |
11941 * @return {Event} A new PointerEvent of type `inType` and initialized with prop
erties from `inDict`. | |
11942 */ | |
11943 (function(scope) { | |
11944 | |
11945 var MOUSE_PROPS = [ | |
11946 'bubbles', | |
11947 'cancelable', | |
11948 'view', | |
11949 'detail', | |
11950 'screenX', | |
11951 'screenY', | |
11952 'clientX', | |
11953 'clientY', | |
11954 'ctrlKey', | |
11955 'altKey', | |
11956 'shiftKey', | |
11957 'metaKey', | |
11958 'button', | |
11959 'relatedTarget', | |
11960 'pageX', | |
11961 'pageY' | |
11962 ]; | |
11963 | |
11964 var MOUSE_DEFAULTS = [ | |
11965 false, | |
11966 false, | |
11967 null, | |
11968 null, | |
11969 0, | |
11970 0, | |
11971 0, | |
11972 0, | |
11973 false, | |
11974 false, | |
11975 false, | |
11976 false, | |
11977 0, | |
11978 null, | |
11979 0, | |
11980 0 | |
11981 ]; | |
11982 | |
11983 function PointerEvent(inType, inDict) { | |
11984 inDict = inDict || Object.create(null); | |
11985 | |
11986 var e = document.createEvent('Event'); | |
11987 e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); | |
11988 | |
11989 // define inherited MouseEvent properties | |
11990 for(var i = 0, p; i < MOUSE_PROPS.length; i++) { | |
11991 p = MOUSE_PROPS[i]; | |
11992 e[p] = inDict[p] || MOUSE_DEFAULTS[i]; | |
11993 } | |
11994 e.buttons = inDict.buttons || 0; | |
11995 | |
11996 // Spec requires that pointers without pressure specified use 0.5 for down | |
11997 // state and 0 for up state. | |
11998 var pressure = 0; | |
11999 if (inDict.pressure) { | |
12000 pressure = inDict.pressure; | |
12001 } else { | |
12002 pressure = e.buttons ? 0.5 : 0; | |
12003 } | |
12004 | |
12005 // add x/y properties aliased to clientX/Y | |
12006 e.x = e.clientX; | |
12007 e.y = e.clientY; | |
12008 | |
12009 // define the properties of the PointerEvent interface | |
12010 e.pointerId = inDict.pointerId || 0; | |
12011 e.width = inDict.width || 0; | |
12012 e.height = inDict.height || 0; | |
12013 e.pressure = pressure; | |
12014 e.tiltX = inDict.tiltX || 0; | |
12015 e.tiltY = inDict.tiltY || 0; | |
12016 e.pointerType = inDict.pointerType || ''; | |
12017 e.hwTimestamp = inDict.hwTimestamp || 0; | |
12018 e.isPrimary = inDict.isPrimary || false; | |
12019 return e; | |
12020 } | |
12021 | |
12022 // attach to window | |
12023 if (!scope.PointerEvent) { | |
12024 scope.PointerEvent = PointerEvent; | |
12025 } | |
12026 })(window); | |
12027 | |
12028 /* | |
12029 * Copyright 2013 The Polymer Authors. All rights reserved. | |
12030 * Use of this source code is governed by a BSD-style | |
12031 * license that can be found in the LICENSE file. | |
12032 */ | |
12033 | |
12034 /** | |
12035 * This module implements an map of pointer states | |
12036 */ | |
12037 (function(scope) { | |
12038 var USE_MAP = window.Map && window.Map.prototype.forEach; | |
12039 var POINTERS_FN = function(){ return this.size; }; | |
12040 function PointerMap() { | |
12041 if (USE_MAP) { | |
12042 var m = new Map(); | |
12043 m.pointers = POINTERS_FN; | |
12044 return m; | |
12045 } else { | |
12046 this.keys = []; | |
12047 this.values = []; | |
12048 } | |
12049 } | |
12050 | |
12051 PointerMap.prototype = { | |
12052 set: function(inId, inEvent) { | |
12053 var i = this.keys.indexOf(inId); | |
12054 if (i > -1) { | |
12055 this.values[i] = inEvent; | |
12056 } else { | |
12057 this.keys.push(inId); | |
12058 this.values.push(inEvent); | |
12059 } | |
12060 }, | |
12061 has: function(inId) { | |
12062 return this.keys.indexOf(inId) > -1; | |
12063 }, | |
12064 'delete': function(inId) { | |
12065 var i = this.keys.indexOf(inId); | |
12066 if (i > -1) { | |
12067 this.keys.splice(i, 1); | |
12068 this.values.splice(i, 1); | |
12069 } | |
12070 }, | |
12071 get: function(inId) { | |
12072 var i = this.keys.indexOf(inId); | |
12073 return this.values[i]; | |
12074 }, | |
12075 clear: function() { | |
12076 this.keys.length = 0; | |
12077 this.values.length = 0; | |
12078 }, | |
12079 // return value, key, map | |
12080 forEach: function(callback, thisArg) { | |
12081 this.values.forEach(function(v, i) { | |
12082 callback.call(thisArg, v, this.keys[i], this); | |
12083 }, this); | |
12084 }, | |
12085 pointers: function() { | |
12086 return this.keys.length; | |
12087 } | |
12088 }; | |
12089 | |
12090 scope.PointerMap = PointerMap; | |
12091 })(window.PointerEventsPolyfill); | |
12092 | |
12093 /* | |
12094 * Copyright 2013 The Polymer Authors. All rights reserved. | |
12095 * Use of this source code is governed by a BSD-style | |
12096 * license that can be found in the LICENSE file. | |
12097 */ | |
12098 | |
12099 (function(scope) { | |
12100 var CLONE_PROPS = [ | |
12101 // MouseEvent | |
12102 'bubbles', | |
12103 'cancelable', | |
12104 'view', | |
12105 'detail', | |
12106 'screenX', | |
12107 'screenY', | |
12108 'clientX', | |
12109 'clientY', | |
12110 'ctrlKey', | |
12111 'altKey', | |
12112 'shiftKey', | |
12113 'metaKey', | |
12114 'button', | |
12115 'relatedTarget', | |
12116 // DOM Level 3 | |
12117 'buttons', | |
12118 // PointerEvent | |
12119 'pointerId', | |
12120 'width', | |
12121 'height', | |
12122 'pressure', | |
12123 'tiltX', | |
12124 'tiltY', | |
12125 'pointerType', | |
12126 'hwTimestamp', | |
12127 'isPrimary', | |
12128 // event instance | |
12129 'type', | |
12130 'target', | |
12131 'currentTarget', | |
12132 'which', | |
12133 'pageX', | |
12134 'pageY' | |
12135 ]; | |
12136 | |
12137 var CLONE_DEFAULTS = [ | |
12138 // MouseEvent | |
12139 false, | |
12140 false, | |
12141 null, | |
12142 null, | |
12143 0, | |
12144 0, | |
12145 0, | |
12146 0, | |
12147 false, | |
12148 false, | |
12149 false, | |
12150 false, | |
12151 0, | |
12152 null, | |
12153 // DOM Level 3 | |
12154 0, | |
12155 // PointerEvent | |
12156 0, | |
12157 0, | |
12158 0, | |
12159 0, | |
12160 0, | |
12161 0, | |
12162 '', | |
12163 0, | |
12164 false, | |
12165 // event instance | |
12166 '', | |
12167 null, | |
12168 null, | |
12169 0, | |
12170 0, | |
12171 0 | |
12172 ]; | |
12173 | |
12174 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); | |
12175 | |
12176 /** | |
12177 * This module is for normalizing events. Mouse and Touch events will be | |
12178 * collected here, and fire PointerEvents that have the same semantics, no | |
12179 * matter the source. | |
12180 * Events fired: | |
12181 * - pointerdown: a pointing is added | |
12182 * - pointerup: a pointer is removed | |
12183 * - pointermove: a pointer is moved | |
12184 * - pointerover: a pointer crosses into an element | |
12185 * - pointerout: a pointer leaves an element | |
12186 * - pointercancel: a pointer will no longer generate events | |
12187 */ | |
12188 var dispatcher = { | |
12189 pointermap: new scope.PointerMap(), | |
12190 eventMap: Object.create(null), | |
12191 captureInfo: Object.create(null), | |
12192 // Scope objects for native events. | |
12193 // This exists for ease of testing. | |
12194 eventSources: Object.create(null), | |
12195 eventSourceList: [], | |
12196 /** | |
12197 * Add a new event source that will generate pointer events. | |
12198 * | |
12199 * `inSource` must contain an array of event names named `events`, and | |
12200 * functions with the names specified in the `events` array. | |
12201 * @param {string} name A name for the event source | |
12202 * @param {Object} source A new source of platform events. | |
12203 */ | |
12204 registerSource: function(name, source) { | |
12205 var s = source; | |
12206 var newEvents = s.events; | |
12207 if (newEvents) { | |
12208 newEvents.forEach(function(e) { | |
12209 if (s[e]) { | |
12210 this.eventMap[e] = s[e].bind(s); | |
12211 } | |
12212 }, this); | |
12213 this.eventSources[name] = s; | |
12214 this.eventSourceList.push(s); | |
12215 } | |
12216 }, | |
12217 register: function(element) { | |
12218 var l = this.eventSourceList.length; | |
12219 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | |
12220 // call eventsource register | |
12221 es.register.call(es, element); | |
12222 } | |
12223 }, | |
12224 unregister: function(element) { | |
12225 var l = this.eventSourceList.length; | |
12226 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | |
12227 // call eventsource register | |
12228 es.unregister.call(es, element); | |
12229 } | |
12230 }, | |
12231 contains: scope.external.contains || function(container, contained) { | |
12232 return container.contains(contained); | |
12233 }, | |
12234 // EVENTS | |
12235 down: function(inEvent) { | |
12236 inEvent.bubbles = true; | |
12237 this.fireEvent('pointerdown', inEvent); | |
12238 }, | |
12239 move: function(inEvent) { | |
12240 inEvent.bubbles = true; | |
12241 this.fireEvent('pointermove', inEvent); | |
12242 }, | |
12243 up: function(inEvent) { | |
12244 inEvent.bubbles = true; | |
12245 this.fireEvent('pointerup', inEvent); | |
12246 }, | |
12247 enter: function(inEvent) { | |
12248 inEvent.bubbles = false; | |
12249 this.fireEvent('pointerenter', inEvent); | |
12250 }, | |
12251 leave: function(inEvent) { | |
12252 inEvent.bubbles = false; | |
12253 this.fireEvent('pointerleave', inEvent); | |
12254 }, | |
12255 over: function(inEvent) { | |
12256 inEvent.bubbles = true; | |
12257 this.fireEvent('pointerover', inEvent); | |
12258 }, | |
12259 out: function(inEvent) { | |
12260 inEvent.bubbles = true; | |
12261 this.fireEvent('pointerout', inEvent); | |
12262 }, | |
12263 cancel: function(inEvent) { | |
12264 inEvent.bubbles = true; | |
12265 this.fireEvent('pointercancel', inEvent); | |
12266 }, | |
12267 leaveOut: function(event) { | |
12268 this.out(event); | |
12269 if (!this.contains(event.target, event.relatedTarget)) { | |
12270 this.leave(event); | |
12271 } | |
12272 }, | |
12273 enterOver: function(event) { | |
12274 this.over(event); | |
12275 if (!this.contains(event.target, event.relatedTarget)) { | |
12276 this.enter(event); | |
12277 } | |
12278 }, | |
12279 // LISTENER LOGIC | |
12280 eventHandler: function(inEvent) { | |
12281 // This is used to prevent multiple dispatch of pointerevents from | |
12282 // platform events. This can happen when two elements in different scopes | |
12283 // are set up to create pointer events, which is relevant to Shadow DOM. | |
12284 if (inEvent._handledByPE) { | |
12285 return; | |
12286 } | |
12287 var type = inEvent.type; | |
12288 var fn = this.eventMap && this.eventMap[type]; | |
12289 if (fn) { | |
12290 fn(inEvent); | |
12291 } | |
12292 inEvent._handledByPE = true; | |
12293 }, | |
12294 // set up event listeners | |
12295 listen: function(target, events) { | |
12296 events.forEach(function(e) { | |
12297 this.addEvent(target, e); | |
12298 }, this); | |
12299 }, | |
12300 // remove event listeners | |
12301 unlisten: function(target, events) { | |
12302 events.forEach(function(e) { | |
12303 this.removeEvent(target, e); | |
12304 }, this); | |
12305 }, | |
12306 addEvent: scope.external.addEvent || function(target, eventName) { | |
12307 target.addEventListener(eventName, this.boundHandler); | |
12308 }, | |
12309 removeEvent: scope.external.removeEvent || function(target, eventName) { | |
12310 target.removeEventListener(eventName, this.boundHandler); | |
12311 }, | |
12312 // EVENT CREATION AND TRACKING | |
12313 /** | |
12314 * Creates a new Event of type `inType`, based on the information in | |
12315 * `inEvent`. | |
12316 * | |
12317 * @param {string} inType A string representing the type of event to create | |
12318 * @param {Event} inEvent A platform event with a target | |
12319 * @return {Event} A PointerEvent of type `inType` | |
12320 */ | |
12321 makeEvent: function(inType, inEvent) { | |
12322 // relatedTarget must be null if pointer is captured | |
12323 if (this.captureInfo[inEvent.pointerId]) { | |
12324 inEvent.relatedTarget = null; | |
12325 } | |
12326 var e = new PointerEvent(inType, inEvent); | |
12327 if (inEvent.preventDefault) { | |
12328 e.preventDefault = inEvent.preventDefault; | |
12329 } | |
12330 e._target = e._target || inEvent.target; | |
12331 return e; | |
12332 }, | |
12333 // make and dispatch an event in one call | |
12334 fireEvent: function(inType, inEvent) { | |
12335 var e = this.makeEvent(inType, inEvent); | |
12336 return this.dispatchEvent(e); | |
12337 }, | |
12338 /** | |
12339 * Returns a snapshot of inEvent, with writable properties. | |
12340 * | |
12341 * @param {Event} inEvent An event that contains properties to copy. | |
12342 * @return {Object} An object containing shallow copies of `inEvent`'s | |
12343 * properties. | |
12344 */ | |
12345 cloneEvent: function(inEvent) { | |
12346 var eventCopy = Object.create(null), p; | |
12347 for (var i = 0; i < CLONE_PROPS.length; i++) { | |
12348 p = CLONE_PROPS[i]; | |
12349 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; | |
12350 // Work around SVGInstanceElement shadow tree | |
12351 // Return the <use> element that is represented by the instance for Safa
ri, Chrome, IE. | |
12352 // This is the behavior implemented by Firefox. | |
12353 if (HAS_SVG_INSTANCE && (p === 'target' || p === 'relatedTarget')) { | |
12354 if (eventCopy[p] instanceof SVGElementInstance) { | |
12355 eventCopy[p] = eventCopy[p].correspondingUseElement; | |
12356 } | |
12357 } | |
12358 } | |
12359 // keep the semantics of preventDefault | |
12360 if (inEvent.preventDefault) { | |
12361 eventCopy.preventDefault = function() { | |
12362 inEvent.preventDefault(); | |
12363 }; | |
12364 } | |
12365 return eventCopy; | |
12366 }, | |
12367 getTarget: function(inEvent) { | |
12368 // if pointer capture is set, route all events for the specified pointerId | |
12369 // to the capture target | |
12370 return this.captureInfo[inEvent.pointerId] || inEvent._target; | |
12371 }, | |
12372 setCapture: function(inPointerId, inTarget) { | |
12373 if (this.captureInfo[inPointerId]) { | |
12374 this.releaseCapture(inPointerId); | |
12375 } | |
12376 this.captureInfo[inPointerId] = inTarget; | |
12377 var e = document.createEvent('Event'); | |
12378 e.initEvent('gotpointercapture', true, false); | |
12379 e.pointerId = inPointerId; | |
12380 this.implicitRelease = this.releaseCapture.bind(this, inPointerId); | |
12381 document.addEventListener('pointerup', this.implicitRelease); | |
12382 document.addEventListener('pointercancel', this.implicitRelease); | |
12383 e._target = inTarget; | |
12384 this.asyncDispatchEvent(e); | |
12385 }, | |
12386 releaseCapture: function(inPointerId) { | |
12387 var t = this.captureInfo[inPointerId]; | |
12388 if (t) { | |
12389 var e = document.createEvent('Event'); | |
12390 e.initEvent('lostpointercapture', true, false); | |
12391 e.pointerId = inPointerId; | |
12392 this.captureInfo[inPointerId] = undefined; | |
12393 document.removeEventListener('pointerup', this.implicitRelease); | |
12394 document.removeEventListener('pointercancel', this.implicitRelease); | |
12395 e._target = t; | |
12396 this.asyncDispatchEvent(e); | |
12397 } | |
12398 }, | |
12399 /** | |
12400 * Dispatches the event to its target. | |
12401 * | |
12402 * @param {Event} inEvent The event to be dispatched. | |
12403 * @return {Boolean} True if an event handler returns true, false otherwise. | |
12404 */ | |
12405 dispatchEvent: scope.external.dispatchEvent || function(inEvent) { | |
12406 var t = this.getTarget(inEvent); | |
12407 if (t) { | |
12408 return t.dispatchEvent(inEvent); | |
12409 } | |
12410 }, | |
12411 asyncDispatchEvent: function(inEvent) { | |
12412 requestAnimationFrame(this.dispatchEvent.bind(this, inEvent)); | |
12413 } | |
12414 }; | |
12415 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); | |
12416 scope.dispatcher = dispatcher; | |
12417 scope.register = dispatcher.register.bind(dispatcher); | |
12418 scope.unregister = dispatcher.unregister.bind(dispatcher); | |
12419 })(window.PointerEventsPolyfill); | |
12420 | |
12421 /* | |
12422 * Copyright 2013 The Polymer Authors. All rights reserved. | |
12423 * Use of this source code is governed by a BSD-style | |
12424 * license that can be found in the LICENSE file. | |
12425 */ | |
12426 | |
12427 /** | |
12428 * This module uses Mutation Observers to dynamically adjust which nodes will | |
12429 * generate Pointer Events. | |
12430 * | |
12431 * All nodes that wish to generate Pointer Events must have the attribute | |
12432 * `touch-action` set to `none`. | |
12433 */ | |
12434 (function(scope) { | |
12435 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
12436 var map = Array.prototype.map.call.bind(Array.prototype.map); | |
12437 var toArray = Array.prototype.slice.call.bind(Array.prototype.slice); | |
12438 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); | |
12439 var MO = window.MutationObserver || window.WebKitMutationObserver; | |
12440 var SELECTOR = '[touch-action]'; | |
12441 var OBSERVER_INIT = { | |
12442 subtree: true, | |
12443 childList: true, | |
12444 attributes: true, | |
12445 attributeOldValue: true, | |
12446 attributeFilter: ['touch-action'] | |
12447 }; | |
12448 | |
12449 function Installer(add, remove, changed, binder) { | |
12450 this.addCallback = add.bind(binder); | |
12451 this.removeCallback = remove.bind(binder); | |
12452 this.changedCallback = changed.bind(binder); | |
12453 if (MO) { | |
12454 this.observer = new MO(this.mutationWatcher.bind(this)); | |
12455 } | |
12456 } | |
12457 | |
12458 Installer.prototype = { | |
12459 watchSubtree: function(target) { | |
12460 // Only watch scopes that can target find, as these are top-level. | |
12461 // Otherwise we can see duplicate additions and removals that add noise. | |
12462 // | |
12463 // TODO(dfreedman): For some instances with ShadowDOMPolyfill, we can see | |
12464 // a removal without an insertion when a node is redistributed among | |
12465 // shadows. Since it all ends up correct in the document, watching only | |
12466 // the document will yield the correct mutations to watch. | |
12467 if (scope.targetFinding.canTarget(target)) { | |
12468 this.observer.observe(target, OBSERVER_INIT); | |
12469 } | |
12470 }, | |
12471 enableOnSubtree: function(target) { | |
12472 this.watchSubtree(target); | |
12473 if (target === document && document.readyState !== 'complete') { | |
12474 this.installOnLoad(); | |
12475 } else { | |
12476 this.installNewSubtree(target); | |
12477 } | |
12478 }, | |
12479 installNewSubtree: function(target) { | |
12480 forEach(this.findElements(target), this.addElement, this); | |
12481 }, | |
12482 findElements: function(target) { | |
12483 if (target.querySelectorAll) { | |
12484 return target.querySelectorAll(SELECTOR); | |
12485 } | |
12486 return []; | |
12487 }, | |
12488 removeElement: function(el) { | |
12489 this.removeCallback(el); | |
12490 }, | |
12491 addElement: function(el) { | |
12492 this.addCallback(el); | |
12493 }, | |
12494 elementChanged: function(el, oldValue) { | |
12495 this.changedCallback(el, oldValue); | |
12496 }, | |
12497 concatLists: function(accum, list) { | |
12498 return accum.concat(toArray(list)); | |
12499 }, | |
12500 // register all touch-action = none nodes on document load | |
12501 installOnLoad: function() { | |
12502 document.addEventListener('readystatechange', function() { | |
12503 if (document.readyState === 'complete') { | |
12504 this.installNewSubtree(document); | |
12505 } | |
12506 }.bind(this)); | |
12507 }, | |
12508 isElement: function(n) { | |
12509 return n.nodeType === Node.ELEMENT_NODE; | |
12510 }, | |
12511 flattenMutationTree: function(inNodes) { | |
12512 // find children with touch-action | |
12513 var tree = map(inNodes, this.findElements, this); | |
12514 // make sure the added nodes are accounted for | |
12515 tree.push(filter(inNodes, this.isElement)); | |
12516 // flatten the list | |
12517 return tree.reduce(this.concatLists, []); | |
12518 }, | |
12519 mutationWatcher: function(mutations) { | |
12520 mutations.forEach(this.mutationHandler, this); | |
12521 }, | |
12522 mutationHandler: function(m) { | |
12523 if (m.type === 'childList') { | |
12524 var added = this.flattenMutationTree(m.addedNodes); | |
12525 added.forEach(this.addElement, this); | |
12526 var removed = this.flattenMutationTree(m.removedNodes); | |
12527 removed.forEach(this.removeElement, this); | |
12528 } else if (m.type === 'attributes') { | |
12529 this.elementChanged(m.target, m.oldValue); | |
12530 } | |
12531 } | |
12532 }; | |
12533 | |
12534 if (!MO) { | |
12535 Installer.prototype.watchSubtree = function(){ | |
12536 console.warn('PointerEventsPolyfill: MutationObservers not found, touch-ac
tion will not be dynamically detected'); | |
12537 }; | |
12538 } | |
12539 | |
12540 scope.Installer = Installer; | |
12541 })(window.PointerEventsPolyfill); | |
12542 | |
12543 /* | |
12544 * Copyright 2013 The Polymer Authors. All rights reserved. | |
12545 * Use of this source code is governed by a BSD-style | |
12546 * license that can be found in the LICENSE file. | |
12547 */ | |
12548 | |
12549 (function (scope) { | |
12550 var dispatcher = scope.dispatcher; | |
12551 var pointermap = dispatcher.pointermap; | |
12552 // radius around touchend that swallows mouse events | |
12553 var DEDUP_DIST = 25; | |
12554 | |
12555 var WHICH_TO_BUTTONS = [0, 1, 4, 2]; | |
12556 | |
12557 var HAS_BUTTONS = false; | |
12558 try { | |
12559 HAS_BUTTONS = new MouseEvent('test', {buttons: 1}).buttons === 1; | |
12560 } catch (e) {} | |
12561 | |
12562 // handler block for native mouse events | |
12563 var mouseEvents = { | |
12564 POINTER_ID: 1, | |
12565 POINTER_TYPE: 'mouse', | |
12566 events: [ | |
12567 'mousedown', | |
12568 'mousemove', | |
12569 'mouseup', | |
12570 'mouseover', | |
12571 'mouseout' | |
12572 ], | |
12573 register: function(target) { | |
12574 dispatcher.listen(target, this.events); | |
12575 }, | |
12576 unregister: function(target) { | |
12577 dispatcher.unlisten(target, this.events); | |
12578 }, | |
12579 lastTouches: [], | |
12580 // collide with the global mouse listener | |
12581 isEventSimulatedFromTouch: function(inEvent) { | |
12582 var lts = this.lastTouches; | |
12583 var x = inEvent.clientX, y = inEvent.clientY; | |
12584 for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { | |
12585 // simulated mouse events will be swallowed near a primary touchend | |
12586 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); | |
12587 if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { | |
12588 return true; | |
12589 } | |
12590 } | |
12591 }, | |
12592 prepareEvent: function(inEvent) { | |
12593 var e = dispatcher.cloneEvent(inEvent); | |
12594 // forward mouse preventDefault | |
12595 var pd = e.preventDefault; | |
12596 e.preventDefault = function() { | |
12597 inEvent.preventDefault(); | |
12598 pd(); | |
12599 }; | |
12600 e.pointerId = this.POINTER_ID; | |
12601 e.isPrimary = true; | |
12602 e.pointerType = this.POINTER_TYPE; | |
12603 if (!HAS_BUTTONS) { | |
12604 e.buttons = WHICH_TO_BUTTONS[e.which] || 0; | |
12605 } | |
12606 return e; | |
12607 }, | |
12608 mousedown: function(inEvent) { | |
12609 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
12610 var p = pointermap.has(this.POINTER_ID); | |
12611 // TODO(dfreedman) workaround for some elements not sending mouseup | |
12612 // http://crbug/149091 | |
12613 if (p) { | |
12614 this.cancel(inEvent); | |
12615 } | |
12616 var e = this.prepareEvent(inEvent); | |
12617 pointermap.set(this.POINTER_ID, inEvent); | |
12618 dispatcher.down(e); | |
12619 } | |
12620 }, | |
12621 mousemove: function(inEvent) { | |
12622 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
12623 var e = this.prepareEvent(inEvent); | |
12624 dispatcher.move(e); | |
12625 } | |
12626 }, | |
12627 mouseup: function(inEvent) { | |
12628 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
12629 var p = pointermap.get(this.POINTER_ID); | |
12630 if (p && p.button === inEvent.button) { | |
12631 var e = this.prepareEvent(inEvent); | |
12632 dispatcher.up(e); | |
12633 this.cleanupMouse(); | |
12634 } | |
12635 } | |
12636 }, | |
12637 mouseover: function(inEvent) { | |
12638 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
12639 var e = this.prepareEvent(inEvent); | |
12640 dispatcher.enterOver(e); | |
12641 } | |
12642 }, | |
12643 mouseout: function(inEvent) { | |
12644 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
12645 var e = this.prepareEvent(inEvent); | |
12646 dispatcher.leaveOut(e); | |
12647 } | |
12648 }, | |
12649 cancel: function(inEvent) { | |
12650 var e = this.prepareEvent(inEvent); | |
12651 dispatcher.cancel(e); | |
12652 this.cleanupMouse(); | |
12653 }, | |
12654 cleanupMouse: function() { | |
12655 pointermap['delete'](this.POINTER_ID); | |
12656 } | |
12657 }; | |
12658 | |
12659 scope.mouseEvents = mouseEvents; | |
12660 })(window.PointerEventsPolyfill); | |
12661 | |
12662 /* | |
12663 * Copyright 2013 The Polymer Authors. All rights reserved. | |
12664 * Use of this source code is governed by a BSD-style | |
12665 * license that can be found in the LICENSE file. | |
12666 */ | |
12667 | |
12668 (function(scope) { | |
12669 var dispatcher = scope.dispatcher; | |
12670 var captureInfo = dispatcher.captureInfo; | |
12671 var findTarget = scope.findTarget; | |
12672 var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); | |
12673 var pointermap = dispatcher.pointermap; | |
12674 var touchMap = Array.prototype.map.call.bind(Array.prototype.map); | |
12675 // This should be long enough to ignore compat mouse events made by touch | |
12676 var DEDUP_TIMEOUT = 2500; | |
12677 var CLICK_COUNT_TIMEOUT = 200; | |
12678 var ATTRIB = 'touch-action'; | |
12679 var INSTALLER; | |
12680 // The presence of touch event handlers blocks scrolling, and so we must be ca
reful to | |
12681 // avoid adding handlers unnecessarily. Chrome plans to add a touch-action-de
lay property | |
12682 // (crbug.com/329559) to address this, and once we have that we can opt-in to
a simpler | |
12683 // handler registration mechanism. Rather than try to predict how exactly to
opt-in to | |
12684 // that we'll just leave this disabled until there is a build of Chrome to tes
t. | |
12685 var HAS_TOUCH_ACTION_DELAY = false; | |
12686 | |
12687 // handler block for native touch events | |
12688 var touchEvents = { | |
12689 events: [ | |
12690 'touchstart', | |
12691 'touchmove', | |
12692 'touchend', | |
12693 'touchcancel' | |
12694 ], | |
12695 register: function(target) { | |
12696 if (HAS_TOUCH_ACTION_DELAY) { | |
12697 dispatcher.listen(target, this.events); | |
12698 } else { | |
12699 INSTALLER.enableOnSubtree(target); | |
12700 } | |
12701 }, | |
12702 unregister: function(target) { | |
12703 if (HAS_TOUCH_ACTION_DELAY) { | |
12704 dispatcher.unlisten(target, this.events); | |
12705 } else { | |
12706 // TODO(dfreedman): is it worth it to disconnect the MO? | |
12707 } | |
12708 }, | |
12709 elementAdded: function(el) { | |
12710 var a = el.getAttribute(ATTRIB); | |
12711 var st = this.touchActionToScrollType(a); | |
12712 if (st) { | |
12713 el._scrollType = st; | |
12714 dispatcher.listen(el, this.events); | |
12715 // set touch-action on shadows as well | |
12716 allShadows(el).forEach(function(s) { | |
12717 s._scrollType = st; | |
12718 dispatcher.listen(s, this.events); | |
12719 }, this); | |
12720 } | |
12721 }, | |
12722 elementRemoved: function(el) { | |
12723 el._scrollType = undefined; | |
12724 dispatcher.unlisten(el, this.events); | |
12725 // remove touch-action from shadow | |
12726 allShadows(el).forEach(function(s) { | |
12727 s._scrollType = undefined; | |
12728 dispatcher.unlisten(s, this.events); | |
12729 }, this); | |
12730 }, | |
12731 elementChanged: function(el, oldValue) { | |
12732 var a = el.getAttribute(ATTRIB); | |
12733 var st = this.touchActionToScrollType(a); | |
12734 var oldSt = this.touchActionToScrollType(oldValue); | |
12735 // simply update scrollType if listeners are already established | |
12736 if (st && oldSt) { | |
12737 el._scrollType = st; | |
12738 allShadows(el).forEach(function(s) { | |
12739 s._scrollType = st; | |
12740 }, this); | |
12741 } else if (oldSt) { | |
12742 this.elementRemoved(el); | |
12743 } else if (st) { | |
12744 this.elementAdded(el); | |
12745 } | |
12746 }, | |
12747 scrollTypes: { | |
12748 EMITTER: 'none', | |
12749 XSCROLLER: 'pan-x', | |
12750 YSCROLLER: 'pan-y', | |
12751 SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/ | |
12752 }, | |
12753 touchActionToScrollType: function(touchAction) { | |
12754 var t = touchAction; | |
12755 var st = this.scrollTypes; | |
12756 if (t === 'none') { | |
12757 return 'none'; | |
12758 } else if (t === st.XSCROLLER) { | |
12759 return 'X'; | |
12760 } else if (t === st.YSCROLLER) { | |
12761 return 'Y'; | |
12762 } else if (st.SCROLLER.exec(t)) { | |
12763 return 'XY'; | |
12764 } | |
12765 }, | |
12766 POINTER_TYPE: 'touch', | |
12767 firstTouch: null, | |
12768 isPrimaryTouch: function(inTouch) { | |
12769 return this.firstTouch === inTouch.identifier; | |
12770 }, | |
12771 setPrimaryTouch: function(inTouch) { | |
12772 // set primary touch if there no pointers, or the only pointer is the mous
e | |
12773 if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointer
map.has(1))) { | |
12774 this.firstTouch = inTouch.identifier; | |
12775 this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; | |
12776 this.scrolling = false; | |
12777 this.cancelResetClickCount(); | |
12778 } | |
12779 }, | |
12780 removePrimaryPointer: function(inPointer) { | |
12781 if (inPointer.isPrimary) { | |
12782 this.firstTouch = null; | |
12783 this.firstXY = null; | |
12784 this.resetClickCount(); | |
12785 } | |
12786 }, | |
12787 clickCount: 0, | |
12788 resetId: null, | |
12789 resetClickCount: function() { | |
12790 var fn = function() { | |
12791 this.clickCount = 0; | |
12792 this.resetId = null; | |
12793 }.bind(this); | |
12794 this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); | |
12795 }, | |
12796 cancelResetClickCount: function() { | |
12797 if (this.resetId) { | |
12798 clearTimeout(this.resetId); | |
12799 } | |
12800 }, | |
12801 typeToButtons: function(type) { | |
12802 var ret = 0; | |
12803 if (type === 'touchstart' || type === 'touchmove') { | |
12804 ret = 1; | |
12805 } | |
12806 return ret; | |
12807 }, | |
12808 touchToPointer: function(inTouch) { | |
12809 var cte = this.currentTouchEvent; | |
12810 var e = dispatcher.cloneEvent(inTouch); | |
12811 // Spec specifies that pointerId 1 is reserved for Mouse. | |
12812 // Touch identifiers can start at 0. | |
12813 // Add 2 to the touch identifier for compatibility. | |
12814 var id = e.pointerId = inTouch.identifier + 2; | |
12815 e.target = captureInfo[id] || findTarget(e); | |
12816 e.bubbles = true; | |
12817 e.cancelable = true; | |
12818 e.detail = this.clickCount; | |
12819 e.button = 0; | |
12820 e.buttons = this.typeToButtons(cte.type); | |
12821 e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; | |
12822 e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; | |
12823 e.pressure = inTouch.webkitForce || inTouch.force || 0.5; | |
12824 e.isPrimary = this.isPrimaryTouch(inTouch); | |
12825 e.pointerType = this.POINTER_TYPE; | |
12826 // forward touch preventDefaults | |
12827 var self = this; | |
12828 e.preventDefault = function() { | |
12829 self.scrolling = false; | |
12830 self.firstXY = null; | |
12831 cte.preventDefault(); | |
12832 }; | |
12833 return e; | |
12834 }, | |
12835 processTouches: function(inEvent, inFunction) { | |
12836 var tl = inEvent.changedTouches; | |
12837 this.currentTouchEvent = inEvent; | |
12838 for (var i = 0, t; i < tl.length; i++) { | |
12839 t = tl[i]; | |
12840 inFunction.call(this, this.touchToPointer(t)); | |
12841 } | |
12842 }, | |
12843 // For single axis scrollers, determines whether the element should emit | |
12844 // pointer events or behave as a scroller | |
12845 shouldScroll: function(inEvent) { | |
12846 if (this.firstXY) { | |
12847 var ret; | |
12848 var scrollAxis = inEvent.currentTarget._scrollType; | |
12849 if (scrollAxis === 'none') { | |
12850 // this element is a touch-action: none, should never scroll | |
12851 ret = false; | |
12852 } else if (scrollAxis === 'XY') { | |
12853 // this element should always scroll | |
12854 ret = true; | |
12855 } else { | |
12856 var t = inEvent.changedTouches[0]; | |
12857 // check the intended scroll axis, and other axis | |
12858 var a = scrollAxis; | |
12859 var oa = scrollAxis === 'Y' ? 'X' : 'Y'; | |
12860 var da = Math.abs(t['client' + a] - this.firstXY[a]); | |
12861 var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); | |
12862 // if delta in the scroll axis > delta other axis, scroll instead of | |
12863 // making events | |
12864 ret = da >= doa; | |
12865 } | |
12866 this.firstXY = null; | |
12867 return ret; | |
12868 } | |
12869 }, | |
12870 findTouch: function(inTL, inId) { | |
12871 for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { | |
12872 if (t.identifier === inId) { | |
12873 return true; | |
12874 } | |
12875 } | |
12876 }, | |
12877 // In some instances, a touchstart can happen without a touchend. This | |
12878 // leaves the pointermap in a broken state. | |
12879 // Therefore, on every touchstart, we remove the touches that did not fire a | |
12880 // touchend event. | |
12881 // To keep state globally consistent, we fire a | |
12882 // pointercancel for this "abandoned" touch | |
12883 vacuumTouches: function(inEvent) { | |
12884 var tl = inEvent.touches; | |
12885 // pointermap.pointers() should be < tl.length here, as the touchstart has
not | |
12886 // been processed yet. | |
12887 if (pointermap.pointers() >= tl.length) { | |
12888 var d = []; | |
12889 pointermap.forEach(function(value, key) { | |
12890 // Never remove pointerId == 1, which is mouse. | |
12891 // Touch identifiers are 2 smaller than their pointerId, which is the | |
12892 // index in pointermap. | |
12893 if (key !== 1 && !this.findTouch(tl, key - 2)) { | |
12894 var p = value.out; | |
12895 d.push(p); | |
12896 } | |
12897 }, this); | |
12898 d.forEach(this.cancelOut, this); | |
12899 } | |
12900 }, | |
12901 touchstart: function(inEvent) { | |
12902 this.vacuumTouches(inEvent); | |
12903 this.setPrimaryTouch(inEvent.changedTouches[0]); | |
12904 this.dedupSynthMouse(inEvent); | |
12905 if (!this.scrolling) { | |
12906 this.clickCount++; | |
12907 this.processTouches(inEvent, this.overDown); | |
12908 } | |
12909 }, | |
12910 overDown: function(inPointer) { | |
12911 var p = pointermap.set(inPointer.pointerId, { | |
12912 target: inPointer.target, | |
12913 out: inPointer, | |
12914 outTarget: inPointer.target | |
12915 }); | |
12916 dispatcher.over(inPointer); | |
12917 dispatcher.enter(inPointer); | |
12918 dispatcher.down(inPointer); | |
12919 }, | |
12920 touchmove: function(inEvent) { | |
12921 if (!this.scrolling) { | |
12922 if (this.shouldScroll(inEvent)) { | |
12923 this.scrolling = true; | |
12924 this.touchcancel(inEvent); | |
12925 } else { | |
12926 inEvent.preventDefault(); | |
12927 this.processTouches(inEvent, this.moveOverOut); | |
12928 } | |
12929 } | |
12930 }, | |
12931 moveOverOut: function(inPointer) { | |
12932 var event = inPointer; | |
12933 var pointer = pointermap.get(event.pointerId); | |
12934 // a finger drifted off the screen, ignore it | |
12935 if (!pointer) { | |
12936 return; | |
12937 } | |
12938 var outEvent = pointer.out; | |
12939 var outTarget = pointer.outTarget; | |
12940 dispatcher.move(event); | |
12941 if (outEvent && outTarget !== event.target) { | |
12942 outEvent.relatedTarget = event.target; | |
12943 event.relatedTarget = outTarget; | |
12944 // recover from retargeting by shadow | |
12945 outEvent.target = outTarget; | |
12946 if (event.target) { | |
12947 dispatcher.leaveOut(outEvent); | |
12948 dispatcher.enterOver(event); | |
12949 } else { | |
12950 // clean up case when finger leaves the screen | |
12951 event.target = outTarget; | |
12952 event.relatedTarget = null; | |
12953 this.cancelOut(event); | |
12954 } | |
12955 } | |
12956 pointer.out = event; | |
12957 pointer.outTarget = event.target; | |
12958 }, | |
12959 touchend: function(inEvent) { | |
12960 this.dedupSynthMouse(inEvent); | |
12961 this.processTouches(inEvent, this.upOut); | |
12962 }, | |
12963 upOut: function(inPointer) { | |
12964 if (!this.scrolling) { | |
12965 dispatcher.up(inPointer); | |
12966 dispatcher.out(inPointer); | |
12967 dispatcher.leave(inPointer); | |
12968 } | |
12969 this.cleanUpPointer(inPointer); | |
12970 }, | |
12971 touchcancel: function(inEvent) { | |
12972 this.processTouches(inEvent, this.cancelOut); | |
12973 }, | |
12974 cancelOut: function(inPointer) { | |
12975 dispatcher.cancel(inPointer); | |
12976 dispatcher.out(inPointer); | |
12977 dispatcher.leave(inPointer); | |
12978 this.cleanUpPointer(inPointer); | |
12979 }, | |
12980 cleanUpPointer: function(inPointer) { | |
12981 pointermap['delete'](inPointer.pointerId); | |
12982 this.removePrimaryPointer(inPointer); | |
12983 }, | |
12984 // prevent synth mouse events from creating pointer events | |
12985 dedupSynthMouse: function(inEvent) { | |
12986 var lts = scope.mouseEvents.lastTouches; | |
12987 var t = inEvent.changedTouches[0]; | |
12988 // only the primary finger will synth mouse events | |
12989 if (this.isPrimaryTouch(t)) { | |
12990 // remember x/y of last touch | |
12991 var lt = {x: t.clientX, y: t.clientY}; | |
12992 lts.push(lt); | |
12993 var fn = (function(lts, lt){ | |
12994 var i = lts.indexOf(lt); | |
12995 if (i > -1) { | |
12996 lts.splice(i, 1); | |
12997 } | |
12998 }).bind(null, lts, lt); | |
12999 setTimeout(fn, DEDUP_TIMEOUT); | |
13000 } | |
13001 } | |
13002 }; | |
13003 | |
13004 if (!HAS_TOUCH_ACTION_DELAY) { | |
13005 INSTALLER = new scope.Installer(touchEvents.elementAdded, touchEvents.elemen
tRemoved, touchEvents.elementChanged, touchEvents); | |
13006 } | |
13007 | |
13008 scope.touchEvents = touchEvents; | |
13009 })(window.PointerEventsPolyfill); | |
13010 | |
13011 /* | |
13012 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13013 * Use of this source code is governed by a BSD-style | |
13014 * license that can be found in the LICENSE file. | |
13015 */ | |
13016 | |
13017 (function(scope) { | |
13018 var dispatcher = scope.dispatcher; | |
13019 var pointermap = dispatcher.pointermap; | |
13020 var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MS
POINTER_TYPE_MOUSE === 'number'; | |
13021 var msEvents = { | |
13022 events: [ | |
13023 'MSPointerDown', | |
13024 'MSPointerMove', | |
13025 'MSPointerUp', | |
13026 'MSPointerOut', | |
13027 'MSPointerOver', | |
13028 'MSPointerCancel', | |
13029 'MSGotPointerCapture', | |
13030 'MSLostPointerCapture' | |
13031 ], | |
13032 register: function(target) { | |
13033 dispatcher.listen(target, this.events); | |
13034 }, | |
13035 unregister: function(target) { | |
13036 dispatcher.unlisten(target, this.events); | |
13037 }, | |
13038 POINTER_TYPES: [ | |
13039 '', | |
13040 'unavailable', | |
13041 'touch', | |
13042 'pen', | |
13043 'mouse' | |
13044 ], | |
13045 prepareEvent: function(inEvent) { | |
13046 var e = inEvent; | |
13047 if (HAS_BITMAP_TYPE) { | |
13048 e = dispatcher.cloneEvent(inEvent); | |
13049 e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; | |
13050 } | |
13051 return e; | |
13052 }, | |
13053 cleanup: function(id) { | |
13054 pointermap['delete'](id); | |
13055 }, | |
13056 MSPointerDown: function(inEvent) { | |
13057 pointermap.set(inEvent.pointerId, inEvent); | |
13058 var e = this.prepareEvent(inEvent); | |
13059 dispatcher.down(e); | |
13060 }, | |
13061 MSPointerMove: function(inEvent) { | |
13062 var e = this.prepareEvent(inEvent); | |
13063 dispatcher.move(e); | |
13064 }, | |
13065 MSPointerUp: function(inEvent) { | |
13066 var e = this.prepareEvent(inEvent); | |
13067 dispatcher.up(e); | |
13068 this.cleanup(inEvent.pointerId); | |
13069 }, | |
13070 MSPointerOut: function(inEvent) { | |
13071 var e = this.prepareEvent(inEvent); | |
13072 dispatcher.leaveOut(e); | |
13073 }, | |
13074 MSPointerOver: function(inEvent) { | |
13075 var e = this.prepareEvent(inEvent); | |
13076 dispatcher.enterOver(e); | |
13077 }, | |
13078 MSPointerCancel: function(inEvent) { | |
13079 var e = this.prepareEvent(inEvent); | |
13080 dispatcher.cancel(e); | |
13081 this.cleanup(inEvent.pointerId); | |
13082 }, | |
13083 MSLostPointerCapture: function(inEvent) { | |
13084 var e = dispatcher.makeEvent('lostpointercapture', inEvent); | |
13085 dispatcher.dispatchEvent(e); | |
13086 }, | |
13087 MSGotPointerCapture: function(inEvent) { | |
13088 var e = dispatcher.makeEvent('gotpointercapture', inEvent); | |
13089 dispatcher.dispatchEvent(e); | |
13090 } | |
13091 }; | |
13092 | |
13093 scope.msEvents = msEvents; | |
13094 })(window.PointerEventsPolyfill); | |
13095 | |
13096 /* | |
13097 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13098 * Use of this source code is governed by a BSD-style | |
13099 * license that can be found in the LICENSE file. | |
13100 */ | |
13101 | |
13102 /** | |
13103 * This module contains the handlers for native platform events. | |
13104 * From here, the dispatcher is called to create unified pointer events. | |
13105 * Included are touch events (v1), mouse events, and MSPointerEvents. | |
13106 */ | |
13107 (function(scope) { | |
13108 var dispatcher = scope.dispatcher; | |
13109 | |
13110 // only activate if this platform does not have pointer events | |
13111 if (window.PointerEvent !== scope.PointerEvent) { | |
13112 | |
13113 if (window.navigator.msPointerEnabled) { | |
13114 var tp = window.navigator.msMaxTouchPoints; | |
13115 Object.defineProperty(window.navigator, 'maxTouchPoints', { | |
13116 value: tp, | |
13117 enumerable: true | |
13118 }); | |
13119 dispatcher.registerSource('ms', scope.msEvents); | |
13120 } else { | |
13121 dispatcher.registerSource('mouse', scope.mouseEvents); | |
13122 if (window.ontouchstart !== undefined) { | |
13123 dispatcher.registerSource('touch', scope.touchEvents); | |
13124 } | |
13125 } | |
13126 | |
13127 dispatcher.register(document); | |
13128 } | |
13129 })(window.PointerEventsPolyfill); | |
13130 | |
13131 /* | |
13132 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13133 * Use of this source code is governed by a BSD-style | |
13134 * license that can be found in the LICENSE file. | |
13135 */ | |
13136 | |
13137 (function(scope) { | |
13138 var dispatcher = scope.dispatcher; | |
13139 var n = window.navigator; | |
13140 var s, r; | |
13141 function assertDown(id) { | |
13142 if (!dispatcher.pointermap.has(id)) { | |
13143 throw new Error('InvalidPointerId'); | |
13144 } | |
13145 } | |
13146 if (n.msPointerEnabled) { | |
13147 s = function(pointerId) { | |
13148 assertDown(pointerId); | |
13149 this.msSetPointerCapture(pointerId); | |
13150 }; | |
13151 r = function(pointerId) { | |
13152 assertDown(pointerId); | |
13153 this.msReleasePointerCapture(pointerId); | |
13154 }; | |
13155 } else { | |
13156 s = function setPointerCapture(pointerId) { | |
13157 assertDown(pointerId); | |
13158 dispatcher.setCapture(pointerId, this); | |
13159 }; | |
13160 r = function releasePointerCapture(pointerId) { | |
13161 assertDown(pointerId); | |
13162 dispatcher.releaseCapture(pointerId, this); | |
13163 }; | |
13164 } | |
13165 if (window.Element && !Element.prototype.setPointerCapture) { | |
13166 Object.defineProperties(Element.prototype, { | |
13167 'setPointerCapture': { | |
13168 value: s | |
13169 }, | |
13170 'releasePointerCapture': { | |
13171 value: r | |
13172 } | |
13173 }); | |
13174 } | |
13175 })(window.PointerEventsPolyfill); | |
13176 | |
13177 /* | |
13178 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13179 * Use of this source code is governed by a BSD-style | |
13180 * license that can be found in the LICENSE file. | |
13181 */ | |
13182 | |
13183 /** | |
13184 * PointerGestureEvent is the constructor for all PointerGesture events. | |
13185 * | |
13186 * @module PointerGestures | |
13187 * @class PointerGestureEvent | |
13188 * @extends UIEvent | |
13189 * @constructor | |
13190 * @param {String} inType Event type | |
13191 * @param {Object} [inDict] Dictionary of properties to initialize on the event | |
13192 */ | |
13193 | |
13194 function PointerGestureEvent(inType, inDict) { | |
13195 var dict = inDict || {}; | |
13196 var e = document.createEvent('Event'); | |
13197 var props = { | |
13198 bubbles: Boolean(dict.bubbles) === dict.bubbles || true, | |
13199 cancelable: Boolean(dict.cancelable) === dict.cancelable || true | |
13200 }; | |
13201 | |
13202 e.initEvent(inType, props.bubbles, props.cancelable); | |
13203 | |
13204 var keys = Object.keys(dict), k; | |
13205 for (var i = 0; i < keys.length; i++) { | |
13206 k = keys[i]; | |
13207 e[k] = dict[k]; | |
13208 } | |
13209 | |
13210 e.preventTap = this.preventTap; | |
13211 | |
13212 return e; | |
13213 } | |
13214 | |
13215 /** | |
13216 * Allows for any gesture to prevent the tap gesture. | |
13217 * | |
13218 * @method preventTap | |
13219 */ | |
13220 PointerGestureEvent.prototype.preventTap = function() { | |
13221 this.tapPrevented = true; | |
13222 }; | |
13223 | |
13224 | |
13225 /* | |
13226 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13227 * Use of this source code is governed by a BSD-style | |
13228 * license that can be found in the LICENSE file. | |
13229 */ | |
13230 | |
13231 (function(scope) { | |
13232 /** | |
13233 * This class contains the gesture recognizers that create the PointerGesture | |
13234 * events. | |
13235 * | |
13236 * @class PointerGestures | |
13237 * @static | |
13238 */ | |
13239 scope = scope || {}; | |
13240 scope.utils = { | |
13241 LCA: { | |
13242 // Determines the lowest node in the ancestor chain of a and b | |
13243 find: function(a, b) { | |
13244 if (a === b) { | |
13245 return a; | |
13246 } | |
13247 // fast case, a is a direct descendant of b or vice versa | |
13248 if (a.contains) { | |
13249 if (a.contains(b)) { | |
13250 return a; | |
13251 } | |
13252 if (b.contains(a)) { | |
13253 return b; | |
13254 } | |
13255 } | |
13256 var adepth = this.depth(a); | |
13257 var bdepth = this.depth(b); | |
13258 var d = adepth - bdepth; | |
13259 if (d > 0) { | |
13260 a = this.walk(a, d); | |
13261 } else { | |
13262 b = this.walk(b, -d); | |
13263 } | |
13264 while(a && b && a !== b) { | |
13265 a = this.walk(a, 1); | |
13266 b = this.walk(b, 1); | |
13267 } | |
13268 return a; | |
13269 }, | |
13270 walk: function(n, u) { | |
13271 for (var i = 0; i < u; i++) { | |
13272 n = n.parentNode; | |
13273 } | |
13274 return n; | |
13275 }, | |
13276 depth: function(n) { | |
13277 var d = 0; | |
13278 while(n) { | |
13279 d++; | |
13280 n = n.parentNode; | |
13281 } | |
13282 return d; | |
13283 } | |
13284 } | |
13285 }; | |
13286 scope.findLCA = function(a, b) { | |
13287 return scope.utils.LCA.find(a, b); | |
13288 } | |
13289 window.PointerGestures = scope; | |
13290 })(window.PointerGestures); | |
13291 | |
13292 /* | |
13293 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13294 * Use of this source code is governed by a BSD-style | |
13295 * license that can be found in the LICENSE file. | |
13296 */ | |
13297 | |
13298 /** | |
13299 * This module implements an map of pointer states | |
13300 */ | |
13301 (function(scope) { | |
13302 var USE_MAP = window.Map && window.Map.prototype.forEach; | |
13303 var POINTERS_FN = function(){ return this.size; }; | |
13304 function PointerMap() { | |
13305 if (USE_MAP) { | |
13306 var m = new Map(); | |
13307 m.pointers = POINTERS_FN; | |
13308 return m; | |
13309 } else { | |
13310 this.keys = []; | |
13311 this.values = []; | |
13312 } | |
13313 } | |
13314 | |
13315 PointerMap.prototype = { | |
13316 set: function(inId, inEvent) { | |
13317 var i = this.keys.indexOf(inId); | |
13318 if (i > -1) { | |
13319 this.values[i] = inEvent; | |
13320 } else { | |
13321 this.keys.push(inId); | |
13322 this.values.push(inEvent); | |
13323 } | |
13324 }, | |
13325 has: function(inId) { | |
13326 return this.keys.indexOf(inId) > -1; | |
13327 }, | |
13328 'delete': function(inId) { | |
13329 var i = this.keys.indexOf(inId); | |
13330 if (i > -1) { | |
13331 this.keys.splice(i, 1); | |
13332 this.values.splice(i, 1); | |
13333 } | |
13334 }, | |
13335 get: function(inId) { | |
13336 var i = this.keys.indexOf(inId); | |
13337 return this.values[i]; | |
13338 }, | |
13339 clear: function() { | |
13340 this.keys.length = 0; | |
13341 this.values.length = 0; | |
13342 }, | |
13343 // return value, key, map | |
13344 forEach: function(callback, thisArg) { | |
13345 this.values.forEach(function(v, i) { | |
13346 callback.call(thisArg, v, this.keys[i], this); | |
13347 }, this); | |
13348 }, | |
13349 pointers: function() { | |
13350 return this.keys.length; | |
13351 } | |
13352 }; | |
13353 | |
13354 scope.PointerMap = PointerMap; | |
13355 })(window.PointerGestures); | |
13356 | |
13357 /* | |
13358 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13359 * Use of this source code is governed by a BSD-style | |
13360 * license that can be found in the LICENSE file. | |
13361 */ | |
13362 | |
13363 (function(scope) { | |
13364 var CLONE_PROPS = [ | |
13365 // MouseEvent | |
13366 'bubbles', | |
13367 'cancelable', | |
13368 'view', | |
13369 'detail', | |
13370 'screenX', | |
13371 'screenY', | |
13372 'clientX', | |
13373 'clientY', | |
13374 'ctrlKey', | |
13375 'altKey', | |
13376 'shiftKey', | |
13377 'metaKey', | |
13378 'button', | |
13379 'relatedTarget', | |
13380 // DOM Level 3 | |
13381 'buttons', | |
13382 // PointerEvent | |
13383 'pointerId', | |
13384 'width', | |
13385 'height', | |
13386 'pressure', | |
13387 'tiltX', | |
13388 'tiltY', | |
13389 'pointerType', | |
13390 'hwTimestamp', | |
13391 'isPrimary', | |
13392 // event instance | |
13393 'type', | |
13394 'target', | |
13395 'currentTarget', | |
13396 'screenX', | |
13397 'screenY', | |
13398 'pageX', | |
13399 'pageY', | |
13400 'tapPrevented' | |
13401 ]; | |
13402 | |
13403 var CLONE_DEFAULTS = [ | |
13404 // MouseEvent | |
13405 false, | |
13406 false, | |
13407 null, | |
13408 null, | |
13409 0, | |
13410 0, | |
13411 0, | |
13412 0, | |
13413 false, | |
13414 false, | |
13415 false, | |
13416 false, | |
13417 0, | |
13418 null, | |
13419 // DOM Level 3 | |
13420 0, | |
13421 // PointerEvent | |
13422 0, | |
13423 0, | |
13424 0, | |
13425 0, | |
13426 0, | |
13427 0, | |
13428 '', | |
13429 0, | |
13430 false, | |
13431 // event instance | |
13432 '', | |
13433 null, | |
13434 null, | |
13435 0, | |
13436 0, | |
13437 0, | |
13438 0 | |
13439 ]; | |
13440 | |
13441 var dispatcher = { | |
13442 handledEvents: new WeakMap(), | |
13443 targets: new WeakMap(), | |
13444 handlers: {}, | |
13445 recognizers: {}, | |
13446 events: {}, | |
13447 // Add a new gesture recognizer to the event listeners. | |
13448 // Recognizer needs an `events` property. | |
13449 registerRecognizer: function(inName, inRecognizer) { | |
13450 var r = inRecognizer; | |
13451 this.recognizers[inName] = r; | |
13452 r.events.forEach(function(e) { | |
13453 if (r[e]) { | |
13454 this.events[e] = true; | |
13455 var f = r[e].bind(r); | |
13456 this.addHandler(e, f); | |
13457 } | |
13458 }, this); | |
13459 }, | |
13460 addHandler: function(inEvent, inFn) { | |
13461 var e = inEvent; | |
13462 if (!this.handlers[e]) { | |
13463 this.handlers[e] = []; | |
13464 } | |
13465 this.handlers[e].push(inFn); | |
13466 }, | |
13467 // add event listeners for inTarget | |
13468 registerTarget: function(inTarget) { | |
13469 this.listen(Object.keys(this.events), inTarget); | |
13470 }, | |
13471 // remove event listeners for inTarget | |
13472 unregisterTarget: function(inTarget) { | |
13473 this.unlisten(Object.keys(this.events), inTarget); | |
13474 }, | |
13475 // LISTENER LOGIC | |
13476 eventHandler: function(inEvent) { | |
13477 if (this.handledEvents.get(inEvent)) { | |
13478 return; | |
13479 } | |
13480 var type = inEvent.type, fns = this.handlers[type]; | |
13481 if (fns) { | |
13482 this.makeQueue(fns, inEvent); | |
13483 } | |
13484 this.handledEvents.set(inEvent, true); | |
13485 }, | |
13486 // queue event for async dispatch | |
13487 makeQueue: function(inHandlerFns, inEvent) { | |
13488 // must clone events to keep the (possibly shadowed) target correct for | |
13489 // async dispatching | |
13490 var e = this.cloneEvent(inEvent); | |
13491 requestAnimationFrame(this.runQueue.bind(this, inHandlerFns, e)); | |
13492 }, | |
13493 // Dispatch the queued events | |
13494 runQueue: function(inHandlers, inEvent) { | |
13495 this.currentPointerId = inEvent.pointerId; | |
13496 for (var i = 0, f, l = inHandlers.length; (i < l) && (f = inHandlers[i]);
i++) { | |
13497 f(inEvent); | |
13498 } | |
13499 this.currentPointerId = 0; | |
13500 }, | |
13501 // set up event listeners | |
13502 listen: function(inEvents, inTarget) { | |
13503 inEvents.forEach(function(e) { | |
13504 this.addEvent(e, this.boundHandler, false, inTarget); | |
13505 }, this); | |
13506 }, | |
13507 // remove event listeners | |
13508 unlisten: function(inEvents) { | |
13509 inEvents.forEach(function(e) { | |
13510 this.removeEvent(e, this.boundHandler, false, inTarget); | |
13511 }, this); | |
13512 }, | |
13513 addEvent: function(inEventName, inEventHandler, inCapture, inTarget) { | |
13514 inTarget.addEventListener(inEventName, inEventHandler, inCapture); | |
13515 }, | |
13516 removeEvent: function(inEventName, inEventHandler, inCapture, inTarget) { | |
13517 inTarget.removeEventListener(inEventName, inEventHandler, inCapture); | |
13518 }, | |
13519 // EVENT CREATION AND TRACKING | |
13520 // Creates a new Event of type `inType`, based on the information in | |
13521 // `inEvent`. | |
13522 makeEvent: function(inType, inDict) { | |
13523 return new PointerGestureEvent(inType, inDict); | |
13524 }, | |
13525 /* | |
13526 * Returns a snapshot of inEvent, with writable properties. | |
13527 * | |
13528 * @method cloneEvent | |
13529 * @param {Event} inEvent An event that contains properties to copy. | |
13530 * @return {Object} An object containing shallow copies of `inEvent`'s | |
13531 * properties. | |
13532 */ | |
13533 cloneEvent: function(inEvent) { | |
13534 var eventCopy = {}, p; | |
13535 for (var i = 0; i < CLONE_PROPS.length; i++) { | |
13536 p = CLONE_PROPS[i]; | |
13537 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; | |
13538 } | |
13539 return eventCopy; | |
13540 }, | |
13541 // Dispatches the event to its target. | |
13542 dispatchEvent: function(inEvent, inTarget) { | |
13543 var t = inTarget || this.targets.get(inEvent); | |
13544 if (t) { | |
13545 t.dispatchEvent(inEvent); | |
13546 if (inEvent.tapPrevented) { | |
13547 this.preventTap(this.currentPointerId); | |
13548 } | |
13549 } | |
13550 }, | |
13551 asyncDispatchEvent: function(inEvent, inTarget) { | |
13552 requestAnimationFrame(this.dispatchEvent.bind(this, inEvent, inTarget)); | |
13553 }, | |
13554 preventTap: function(inPointerId) { | |
13555 var t = this.recognizers.tap; | |
13556 if (t){ | |
13557 t.preventTap(inPointerId); | |
13558 } | |
13559 } | |
13560 }; | |
13561 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); | |
13562 // recognizers call into the dispatcher and load later | |
13563 // solve the chicken and egg problem by having registerScopes module run last | |
13564 dispatcher.registerQueue = []; | |
13565 dispatcher.immediateRegister = false; | |
13566 scope.dispatcher = dispatcher; | |
13567 /** | |
13568 * Enable gesture events for a given scope, typically | |
13569 * [ShadowRoots](https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow
/index.html#shadow-root-object). | |
13570 * | |
13571 * @for PointerGestures | |
13572 * @method register | |
13573 * @param {ShadowRoot} scope A top level scope to enable gesture | |
13574 * support on. | |
13575 */ | |
13576 scope.register = function(inScope) { | |
13577 if (dispatcher.immediateRegister) { | |
13578 var pe = window.PointerEventsPolyfill; | |
13579 if (pe) { | |
13580 pe.register(inScope); | |
13581 } | |
13582 scope.dispatcher.registerTarget(inScope); | |
13583 } else { | |
13584 dispatcher.registerQueue.push(inScope); | |
13585 } | |
13586 }; | |
13587 scope.register(document); | |
13588 })(window.PointerGestures); | |
13589 | |
13590 /* | |
13591 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13592 * Use of this source code is governed by a BSD-style | |
13593 * license that can be found in the LICENSE file. | |
13594 */ | |
13595 | |
13596 /** | |
13597 * This event is fired when a pointer is held down for 200ms. | |
13598 * | |
13599 * @module PointerGestures | |
13600 * @submodule Events | |
13601 * @class hold | |
13602 */ | |
13603 /** | |
13604 * Type of pointer that made the holding event. | |
13605 * @type String | |
13606 * @property pointerType | |
13607 */ | |
13608 /** | |
13609 * Screen X axis position of the held pointer | |
13610 * @type Number | |
13611 * @property clientX | |
13612 */ | |
13613 /** | |
13614 * Screen Y axis position of the held pointer | |
13615 * @type Number | |
13616 * @property clientY | |
13617 */ | |
13618 /** | |
13619 * Type of pointer that made the holding event. | |
13620 * @type String | |
13621 * @property pointerType | |
13622 */ | |
13623 /** | |
13624 * This event is fired every 200ms while a pointer is held down. | |
13625 * | |
13626 * @class holdpulse | |
13627 * @extends hold | |
13628 */ | |
13629 /** | |
13630 * Milliseconds pointer has been held down. | |
13631 * @type Number | |
13632 * @property holdTime | |
13633 */ | |
13634 /** | |
13635 * This event is fired when a held pointer is released or moved. | |
13636 * | |
13637 * @class released | |
13638 */ | |
13639 | |
13640 (function(scope) { | |
13641 var dispatcher = scope.dispatcher; | |
13642 var hold = { | |
13643 // wait at least HOLD_DELAY ms between hold and pulse events | |
13644 HOLD_DELAY: 200, | |
13645 // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold | |
13646 WIGGLE_THRESHOLD: 16, | |
13647 events: [ | |
13648 'pointerdown', | |
13649 'pointermove', | |
13650 'pointerup', | |
13651 'pointercancel' | |
13652 ], | |
13653 heldPointer: null, | |
13654 holdJob: null, | |
13655 pulse: function() { | |
13656 var hold = Date.now() - this.heldPointer.timeStamp; | |
13657 var type = this.held ? 'holdpulse' : 'hold'; | |
13658 this.fireHold(type, hold); | |
13659 this.held = true; | |
13660 }, | |
13661 cancel: function() { | |
13662 clearInterval(this.holdJob); | |
13663 if (this.held) { | |
13664 this.fireHold('release'); | |
13665 } | |
13666 this.held = false; | |
13667 this.heldPointer = null; | |
13668 this.target = null; | |
13669 this.holdJob = null; | |
13670 }, | |
13671 pointerdown: function(inEvent) { | |
13672 if (inEvent.isPrimary && !this.heldPointer) { | |
13673 this.heldPointer = inEvent; | |
13674 this.target = inEvent.target; | |
13675 this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); | |
13676 } | |
13677 }, | |
13678 pointerup: function(inEvent) { | |
13679 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ | |
13680 this.cancel(); | |
13681 } | |
13682 }, | |
13683 pointercancel: function(inEvent) { | |
13684 this.cancel(); | |
13685 }, | |
13686 pointermove: function(inEvent) { | |
13687 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ | |
13688 var x = inEvent.clientX - this.heldPointer.clientX; | |
13689 var y = inEvent.clientY - this.heldPointer.clientY; | |
13690 if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { | |
13691 this.cancel(); | |
13692 } | |
13693 } | |
13694 }, | |
13695 fireHold: function(inType, inHoldTime) { | |
13696 var p = { | |
13697 pointerType: this.heldPointer.pointerType, | |
13698 clientX: this.heldPointer.clientX, | |
13699 clientY: this.heldPointer.clientY | |
13700 }; | |
13701 if (inHoldTime) { | |
13702 p.holdTime = inHoldTime; | |
13703 } | |
13704 var e = dispatcher.makeEvent(inType, p); | |
13705 dispatcher.dispatchEvent(e, this.target); | |
13706 if (e.tapPrevented) { | |
13707 dispatcher.preventTap(this.heldPointer.pointerId); | |
13708 } | |
13709 } | |
13710 }; | |
13711 dispatcher.registerRecognizer('hold', hold); | |
13712 })(window.PointerGestures); | |
13713 | |
13714 /* | |
13715 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13716 * Use of this source code is governed by a BSD-style | |
13717 * license that can be found in the LICENSE file. | |
13718 */ | |
13719 | |
13720 /** | |
13721 * This event denotes the beginning of a series of tracking events. | |
13722 * | |
13723 * @module PointerGestures | |
13724 * @submodule Events | |
13725 * @class trackstart | |
13726 */ | |
13727 /** | |
13728 * Pixels moved in the x direction since trackstart. | |
13729 * @type Number | |
13730 * @property dx | |
13731 */ | |
13732 /** | |
13733 * Pixes moved in the y direction since trackstart. | |
13734 * @type Number | |
13735 * @property dy | |
13736 */ | |
13737 /** | |
13738 * Pixels moved in the x direction since the last track. | |
13739 * @type Number | |
13740 * @property ddx | |
13741 */ | |
13742 /** | |
13743 * Pixles moved in the y direction since the last track. | |
13744 * @type Number | |
13745 * @property ddy | |
13746 */ | |
13747 /** | |
13748 * The clientX position of the track gesture. | |
13749 * @type Number | |
13750 * @property clientX | |
13751 */ | |
13752 /** | |
13753 * The clientY position of the track gesture. | |
13754 * @type Number | |
13755 * @property clientY | |
13756 */ | |
13757 /** | |
13758 * The pageX position of the track gesture. | |
13759 * @type Number | |
13760 * @property pageX | |
13761 */ | |
13762 /** | |
13763 * The pageY position of the track gesture. | |
13764 * @type Number | |
13765 * @property pageY | |
13766 */ | |
13767 /** | |
13768 * The screenX position of the track gesture. | |
13769 * @type Number | |
13770 * @property screenX | |
13771 */ | |
13772 /** | |
13773 * The screenY position of the track gesture. | |
13774 * @type Number | |
13775 * @property screenY | |
13776 */ | |
13777 /** | |
13778 * The last x axis direction of the pointer. | |
13779 * @type Number | |
13780 * @property xDirection | |
13781 */ | |
13782 /** | |
13783 * The last y axis direction of the pointer. | |
13784 * @type Number | |
13785 * @property yDirection | |
13786 */ | |
13787 /** | |
13788 * A shared object between all tracking events. | |
13789 * @type Object | |
13790 * @property trackInfo | |
13791 */ | |
13792 /** | |
13793 * The element currently under the pointer. | |
13794 * @type Element | |
13795 * @property relatedTarget | |
13796 */ | |
13797 /** | |
13798 * The type of pointer that make the track gesture. | |
13799 * @type String | |
13800 * @property pointerType | |
13801 */ | |
13802 /** | |
13803 * | |
13804 * This event fires for all pointer movement being tracked. | |
13805 * | |
13806 * @class track | |
13807 * @extends trackstart | |
13808 */ | |
13809 /** | |
13810 * This event fires when the pointer is no longer being tracked. | |
13811 * | |
13812 * @class trackend | |
13813 * @extends trackstart | |
13814 */ | |
13815 | |
13816 (function(scope) { | |
13817 var dispatcher = scope.dispatcher; | |
13818 var pointermap = new scope.PointerMap(); | |
13819 var track = { | |
13820 events: [ | |
13821 'pointerdown', | |
13822 'pointermove', | |
13823 'pointerup', | |
13824 'pointercancel' | |
13825 ], | |
13826 WIGGLE_THRESHOLD: 4, | |
13827 clampDir: function(inDelta) { | |
13828 return inDelta > 0 ? 1 : -1; | |
13829 }, | |
13830 calcPositionDelta: function(inA, inB) { | |
13831 var x = 0, y = 0; | |
13832 if (inA && inB) { | |
13833 x = inB.pageX - inA.pageX; | |
13834 y = inB.pageY - inA.pageY; | |
13835 } | |
13836 return {x: x, y: y}; | |
13837 }, | |
13838 fireTrack: function(inType, inEvent, inTrackingData) { | |
13839 var t = inTrackingData; | |
13840 var d = this.calcPositionDelta(t.downEvent, inEvent); | |
13841 var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); | |
13842 if (dd.x) { | |
13843 t.xDirection = this.clampDir(dd.x); | |
13844 } | |
13845 if (dd.y) { | |
13846 t.yDirection = this.clampDir(dd.y); | |
13847 } | |
13848 var trackData = { | |
13849 dx: d.x, | |
13850 dy: d.y, | |
13851 ddx: dd.x, | |
13852 ddy: dd.y, | |
13853 clientX: inEvent.clientX, | |
13854 clientY: inEvent.clientY, | |
13855 pageX: inEvent.pageX, | |
13856 pageY: inEvent.pageY, | |
13857 screenX: inEvent.screenX, | |
13858 screenY: inEvent.screenY, | |
13859 xDirection: t.xDirection, | |
13860 yDirection: t.yDirection, | |
13861 trackInfo: t.trackInfo, | |
13862 relatedTarget: inEvent.target, | |
13863 pointerType: inEvent.pointerType | |
13864 }; | |
13865 var e = dispatcher.makeEvent(inType, trackData); | |
13866 t.lastMoveEvent = inEvent; | |
13867 dispatcher.dispatchEvent(e, t.downTarget); | |
13868 }, | |
13869 pointerdown: function(inEvent) { | |
13870 if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.butto
ns === 1 : true)) { | |
13871 var p = { | |
13872 downEvent: inEvent, | |
13873 downTarget: inEvent.target, | |
13874 trackInfo: {}, | |
13875 lastMoveEvent: null, | |
13876 xDirection: 0, | |
13877 yDirection: 0, | |
13878 tracking: false | |
13879 }; | |
13880 pointermap.set(inEvent.pointerId, p); | |
13881 } | |
13882 }, | |
13883 pointermove: function(inEvent) { | |
13884 var p = pointermap.get(inEvent.pointerId); | |
13885 if (p) { | |
13886 if (!p.tracking) { | |
13887 var d = this.calcPositionDelta(p.downEvent, inEvent); | |
13888 var move = d.x * d.x + d.y * d.y; | |
13889 // start tracking only if finger moves more than WIGGLE_THRESHOLD | |
13890 if (move > this.WIGGLE_THRESHOLD) { | |
13891 p.tracking = true; | |
13892 this.fireTrack('trackstart', p.downEvent, p); | |
13893 this.fireTrack('track', inEvent, p); | |
13894 } | |
13895 } else { | |
13896 this.fireTrack('track', inEvent, p); | |
13897 } | |
13898 } | |
13899 }, | |
13900 pointerup: function(inEvent) { | |
13901 var p = pointermap.get(inEvent.pointerId); | |
13902 if (p) { | |
13903 if (p.tracking) { | |
13904 this.fireTrack('trackend', inEvent, p); | |
13905 } | |
13906 pointermap.delete(inEvent.pointerId); | |
13907 } | |
13908 }, | |
13909 pointercancel: function(inEvent) { | |
13910 this.pointerup(inEvent); | |
13911 } | |
13912 }; | |
13913 dispatcher.registerRecognizer('track', track); | |
13914 })(window.PointerGestures); | |
13915 | |
13916 /* | |
13917 * Copyright 2013 The Polymer Authors. All rights reserved. | |
13918 * Use of this source code is governed by a BSD-style | |
13919 * license that can be found in the LICENSE file. | |
13920 */ | |
13921 | |
13922 /** | |
13923 * This event denotes a rapid down/move/up sequence from a pointer. | |
13924 * | |
13925 * The event is sent to the first element the pointer went down on. | |
13926 * | |
13927 * @module PointerGestures | |
13928 * @submodule Events | |
13929 * @class flick | |
13930 */ | |
13931 /** | |
13932 * Signed velocity of the flick in the x direction. | |
13933 * @property xVelocity | |
13934 * @type Number | |
13935 */ | |
13936 /** | |
13937 * Signed velocity of the flick in the y direction. | |
13938 * @type Number | |
13939 * @property yVelocity | |
13940 */ | |
13941 /** | |
13942 * Unsigned total velocity of the flick. | |
13943 * @type Number | |
13944 * @property velocity | |
13945 */ | |
13946 /** | |
13947 * Angle of the flick in degrees, with 0 along the | |
13948 * positive x axis. | |
13949 * @type Number | |
13950 * @property angle | |
13951 */ | |
13952 /** | |
13953 * Axis with the greatest absolute velocity. Denoted | |
13954 * with 'x' or 'y'. | |
13955 * @type String | |
13956 * @property majorAxis | |
13957 */ | |
13958 /** | |
13959 * Type of the pointer that made the flick. | |
13960 * @type String | |
13961 * @property pointerType | |
13962 */ | |
13963 | |
13964 (function(scope) { | |
13965 var dispatcher = scope.dispatcher; | |
13966 var flick = { | |
13967 // TODO(dfreedman): value should be low enough for low speed flicks, but | |
13968 // high enough to remove accidental flicks | |
13969 MIN_VELOCITY: 0.5 /* px/ms */, | |
13970 MAX_QUEUE: 4, | |
13971 moveQueue: [], | |
13972 target: null, | |
13973 pointerId: null, | |
13974 events: [ | |
13975 'pointerdown', | |
13976 'pointermove', | |
13977 'pointerup', | |
13978 'pointercancel' | |
13979 ], | |
13980 pointerdown: function(inEvent) { | |
13981 if (inEvent.isPrimary && !this.pointerId) { | |
13982 this.pointerId = inEvent.pointerId; | |
13983 this.target = inEvent.target; | |
13984 this.addMove(inEvent); | |
13985 } | |
13986 }, | |
13987 pointermove: function(inEvent) { | |
13988 if (inEvent.pointerId === this.pointerId) { | |
13989 this.addMove(inEvent); | |
13990 } | |
13991 }, | |
13992 pointerup: function(inEvent) { | |
13993 if (inEvent.pointerId === this.pointerId) { | |
13994 this.fireFlick(inEvent); | |
13995 } | |
13996 this.cleanup(); | |
13997 }, | |
13998 pointercancel: function(inEvent) { | |
13999 this.cleanup(); | |
14000 }, | |
14001 cleanup: function() { | |
14002 this.moveQueue = []; | |
14003 this.target = null; | |
14004 this.pointerId = null; | |
14005 }, | |
14006 addMove: function(inEvent) { | |
14007 if (this.moveQueue.length >= this.MAX_QUEUE) { | |
14008 this.moveQueue.shift(); | |
14009 } | |
14010 this.moveQueue.push(inEvent); | |
14011 }, | |
14012 fireFlick: function(inEvent) { | |
14013 var e = inEvent; | |
14014 var l = this.moveQueue.length; | |
14015 var dt, dx, dy, tx, ty, tv, x = 0, y = 0, v = 0; | |
14016 // flick based off the fastest segment of movement | |
14017 for (var i = 0, m; i < l && (m = this.moveQueue[i]); i++) { | |
14018 dt = e.timeStamp - m.timeStamp; | |
14019 dx = e.clientX - m.clientX, dy = e.clientY - m.clientY; | |
14020 tx = dx / dt, ty = dy / dt, tv = Math.sqrt(tx * tx + ty * ty); | |
14021 if (tv > v) { | |
14022 x = tx, y = ty, v = tv; | |
14023 } | |
14024 } | |
14025 var ma = Math.abs(x) > Math.abs(y) ? 'x' : 'y'; | |
14026 var a = this.calcAngle(x, y); | |
14027 if (Math.abs(v) >= this.MIN_VELOCITY) { | |
14028 var ev = dispatcher.makeEvent('flick', { | |
14029 xVelocity: x, | |
14030 yVelocity: y, | |
14031 velocity: v, | |
14032 angle: a, | |
14033 majorAxis: ma, | |
14034 pointerType: inEvent.pointerType | |
14035 }); | |
14036 dispatcher.dispatchEvent(ev, this.target); | |
14037 } | |
14038 }, | |
14039 calcAngle: function(inX, inY) { | |
14040 return (Math.atan2(inY, inX) * 180 / Math.PI); | |
14041 } | |
14042 }; | |
14043 dispatcher.registerRecognizer('flick', flick); | |
14044 })(window.PointerGestures); | |
14045 | |
14046 /* | |
14047 * Copyright 2013 The Polymer Authors. All rights reserved. | |
14048 * Use of this source code is governed by a BSD-style | |
14049 * license that can be found in the LICENSE file. | |
14050 */ | |
14051 | |
14052 /* | |
14053 * Basic strategy: find the farthest apart points, use as diameter of circle | |
14054 * react to size change and rotation of the chord | |
14055 */ | |
14056 | |
14057 /** | |
14058 * @module PointerGestures | |
14059 * @submodule Events | |
14060 * @class pinch | |
14061 */ | |
14062 /** | |
14063 * Scale of the pinch zoom gesture | |
14064 * @property scale | |
14065 * @type Number | |
14066 */ | |
14067 /** | |
14068 * Center X position of pointers causing pinch | |
14069 * @property centerX | |
14070 * @type Number | |
14071 */ | |
14072 /** | |
14073 * Center Y position of pointers causing pinch | |
14074 * @property centerY | |
14075 * @type Number | |
14076 */ | |
14077 | |
14078 /** | |
14079 * @module PointerGestures | |
14080 * @submodule Events | |
14081 * @class rotate | |
14082 */ | |
14083 /** | |
14084 * Angle (in degrees) of rotation. Measured from starting positions of pointers. | |
14085 * @property angle | |
14086 * @type Number | |
14087 */ | |
14088 /** | |
14089 * Center X position of pointers causing rotation | |
14090 * @property centerX | |
14091 * @type Number | |
14092 */ | |
14093 /** | |
14094 * Center Y position of pointers causing rotation | |
14095 * @property centerY | |
14096 * @type Number | |
14097 */ | |
14098 (function(scope) { | |
14099 var dispatcher = scope.dispatcher; | |
14100 var pointermap = new scope.PointerMap(); | |
14101 var RAD_TO_DEG = 180 / Math.PI; | |
14102 var pinch = { | |
14103 events: [ | |
14104 'pointerdown', | |
14105 'pointermove', | |
14106 'pointerup', | |
14107 'pointercancel' | |
14108 ], | |
14109 reference: {}, | |
14110 pointerdown: function(ev) { | |
14111 pointermap.set(ev.pointerId, ev); | |
14112 if (pointermap.pointers() == 2) { | |
14113 var points = this.calcChord(); | |
14114 var angle = this.calcAngle(points); | |
14115 this.reference = { | |
14116 angle: angle, | |
14117 diameter: points.diameter, | |
14118 target: scope.findLCA(points.a.target, points.b.target) | |
14119 }; | |
14120 } | |
14121 }, | |
14122 pointerup: function(ev) { | |
14123 pointermap.delete(ev.pointerId); | |
14124 }, | |
14125 pointermove: function(ev) { | |
14126 if (pointermap.has(ev.pointerId)) { | |
14127 pointermap.set(ev.pointerId, ev); | |
14128 if (pointermap.pointers() > 1) { | |
14129 this.calcPinchRotate(); | |
14130 } | |
14131 } | |
14132 }, | |
14133 pointercancel: function(ev) { | |
14134 this.pointerup(ev); | |
14135 }, | |
14136 dispatchPinch: function(diameter, points) { | |
14137 var zoom = diameter / this.reference.diameter; | |
14138 var ev = dispatcher.makeEvent('pinch', { | |
14139 scale: zoom, | |
14140 centerX: points.center.x, | |
14141 centerY: points.center.y | |
14142 }); | |
14143 dispatcher.dispatchEvent(ev, this.reference.target); | |
14144 }, | |
14145 dispatchRotate: function(angle, points) { | |
14146 var diff = Math.round((angle - this.reference.angle) % 360); | |
14147 var ev = dispatcher.makeEvent('rotate', { | |
14148 angle: diff, | |
14149 centerX: points.center.x, | |
14150 centerY: points.center.y | |
14151 }); | |
14152 dispatcher.dispatchEvent(ev, this.reference.target); | |
14153 }, | |
14154 calcPinchRotate: function() { | |
14155 var points = this.calcChord(); | |
14156 var diameter = points.diameter; | |
14157 var angle = this.calcAngle(points); | |
14158 if (diameter != this.reference.diameter) { | |
14159 this.dispatchPinch(diameter, points); | |
14160 } | |
14161 if (angle != this.reference.angle) { | |
14162 this.dispatchRotate(angle, points); | |
14163 } | |
14164 }, | |
14165 calcChord: function() { | |
14166 var pointers = []; | |
14167 pointermap.forEach(function(p) { | |
14168 pointers.push(p); | |
14169 }); | |
14170 var dist = 0; | |
14171 // start with at least two pointers | |
14172 var points = {a: pointers[0], b: pointers[1]}; | |
14173 var x, y, d; | |
14174 for (var i = 0; i < pointers.length; i++) { | |
14175 var a = pointers[i]; | |
14176 for (var j = i + 1; j < pointers.length; j++) { | |
14177 var b = pointers[j]; | |
14178 x = Math.abs(a.clientX - b.clientX); | |
14179 y = Math.abs(a.clientY - b.clientY); | |
14180 d = x + y; | |
14181 if (d > dist) { | |
14182 dist = d; | |
14183 points = {a: a, b: b}; | |
14184 } | |
14185 } | |
14186 } | |
14187 x = Math.abs(points.a.clientX + points.b.clientX) / 2; | |
14188 y = Math.abs(points.a.clientY + points.b.clientY) / 2; | |
14189 points.center = { x: x, y: y }; | |
14190 points.diameter = dist; | |
14191 return points; | |
14192 }, | |
14193 calcAngle: function(points) { | |
14194 var x = points.a.clientX - points.b.clientX; | |
14195 var y = points.a.clientY - points.b.clientY; | |
14196 return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; | |
14197 }, | |
14198 }; | |
14199 dispatcher.registerRecognizer('pinch', pinch); | |
14200 })(window.PointerGestures); | |
14201 | |
14202 /* | |
14203 * Copyright 2013 The Polymer Authors. All rights reserved. | |
14204 * Use of this source code is governed by a BSD-style | |
14205 * license that can be found in the LICENSE file. | |
14206 */ | |
14207 | |
14208 /** | |
14209 * This event is fired when a pointer quickly goes down and up, and is used to | |
14210 * denote activation. | |
14211 * | |
14212 * Any gesture event can prevent the tap event from being created by calling | |
14213 * `event.preventTap`. | |
14214 * | |
14215 * Any pointer event can prevent the tap by setting the `tapPrevented` property | |
14216 * on itself. | |
14217 * | |
14218 * @module PointerGestures | |
14219 * @submodule Events | |
14220 * @class tap | |
14221 */ | |
14222 /** | |
14223 * X axis position of the tap. | |
14224 * @property x | |
14225 * @type Number | |
14226 */ | |
14227 /** | |
14228 * Y axis position of the tap. | |
14229 * @property y | |
14230 * @type Number | |
14231 */ | |
14232 /** | |
14233 * Type of the pointer that made the tap. | |
14234 * @property pointerType | |
14235 * @type String | |
14236 */ | |
14237 (function(scope) { | |
14238 var dispatcher = scope.dispatcher; | |
14239 var pointermap = new scope.PointerMap(); | |
14240 var tap = { | |
14241 events: [ | |
14242 'pointerdown', | |
14243 'pointermove', | |
14244 'pointerup', | |
14245 'pointercancel', | |
14246 'keyup' | |
14247 ], | |
14248 pointerdown: function(inEvent) { | |
14249 if (inEvent.isPrimary && !inEvent.tapPrevented) { | |
14250 pointermap.set(inEvent.pointerId, { | |
14251 target: inEvent.target, | |
14252 buttons: inEvent.buttons, | |
14253 x: inEvent.clientX, | |
14254 y: inEvent.clientY | |
14255 }); | |
14256 } | |
14257 }, | |
14258 pointermove: function(inEvent) { | |
14259 if (inEvent.isPrimary) { | |
14260 var start = pointermap.get(inEvent.pointerId); | |
14261 if (start) { | |
14262 if (inEvent.tapPrevented) { | |
14263 pointermap.delete(inEvent.pointerId); | |
14264 } | |
14265 } | |
14266 } | |
14267 }, | |
14268 shouldTap: function(e, downState) { | |
14269 if (!e.tapPrevented) { | |
14270 if (e.pointerType === 'mouse') { | |
14271 // only allow left click to tap for mouse | |
14272 return downState.buttons === 1; | |
14273 } else { | |
14274 return true; | |
14275 } | |
14276 } | |
14277 }, | |
14278 pointerup: function(inEvent) { | |
14279 var start = pointermap.get(inEvent.pointerId); | |
14280 if (start && this.shouldTap(inEvent, start)) { | |
14281 var t = scope.findLCA(start.target, inEvent.target); | |
14282 if (t) { | |
14283 var e = dispatcher.makeEvent('tap', { | |
14284 x: inEvent.clientX, | |
14285 y: inEvent.clientY, | |
14286 detail: inEvent.detail, | |
14287 pointerType: inEvent.pointerType | |
14288 }); | |
14289 dispatcher.dispatchEvent(e, t); | |
14290 } | |
14291 } | |
14292 pointermap.delete(inEvent.pointerId); | |
14293 }, | |
14294 pointercancel: function(inEvent) { | |
14295 pointermap.delete(inEvent.pointerId); | |
14296 }, | |
14297 keyup: function(inEvent) { | |
14298 var code = inEvent.keyCode; | |
14299 // 32 == spacebar | |
14300 if (code === 32) { | |
14301 var t = inEvent.target; | |
14302 if (!(t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement)
) { | |
14303 dispatcher.dispatchEvent(dispatcher.makeEvent('tap', { | |
14304 x: 0, | |
14305 y: 0, | |
14306 detail: 0, | |
14307 pointerType: 'unavailable' | |
14308 }), t); | |
14309 } | |
14310 } | |
14311 }, | |
14312 preventTap: function(inPointerId) { | |
14313 pointermap.delete(inPointerId); | |
14314 } | |
14315 }; | |
14316 dispatcher.registerRecognizer('tap', tap); | |
14317 })(window.PointerGestures); | |
14318 | |
14319 /* | |
14320 * Copyright 2014 The Polymer Authors. All rights reserved. | |
14321 * Use of this source code is governed by a BSD-style | |
14322 * license that can be found in the LICENSE file. | |
14323 */ | |
14324 | |
14325 /** | |
14326 * Because recognizers are loaded after dispatcher, we have to wait to register | |
14327 * scopes until after all the recognizers. | |
14328 */ | |
14329 (function(scope) { | |
14330 var dispatcher = scope.dispatcher; | |
14331 function registerScopes() { | |
14332 dispatcher.immediateRegister = true; | |
14333 var rq = dispatcher.registerQueue; | |
14334 rq.forEach(scope.register); | |
14335 rq.length = 0; | |
14336 } | |
14337 if (document.readyState === 'complete') { | |
14338 registerScopes(); | |
14339 } else { | |
14340 // register scopes after a steadystate is reached | |
14341 // less MutationObserver churn | |
14342 document.addEventListener('readystatechange', function() { | |
14343 if (document.readyState === 'complete') { | |
14344 registerScopes(); | |
14345 } | |
14346 }); | |
14347 } | |
14348 })(window.PointerGestures); | |
14349 | |
14350 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
14351 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
14352 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
14353 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
14354 // Code distributed by Google as part of the polymer project is also | |
14355 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
14356 | |
14357 (function(global) { | |
14358 'use strict'; | |
14359 | |
14360 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); | |
14361 | |
14362 function getTreeScope(node) { | |
14363 while (node.parentNode) { | |
14364 node = node.parentNode; | |
14365 } | |
14366 | |
14367 return typeof node.getElementById === 'function' ? node : null; | |
14368 } | |
14369 | |
14370 Node.prototype.bind = function(name, observable) { | |
14371 console.error('Unhandled binding to Node: ', this, name, observable); | |
14372 }; | |
14373 | |
14374 function updateBindings(node, name, binding) { | |
14375 var bindings = node.bindings_; | |
14376 if (!bindings) | |
14377 bindings = node.bindings_ = {}; | |
14378 | |
14379 if (bindings[name]) | |
14380 binding[name].close(); | |
14381 | |
14382 return bindings[name] = binding; | |
14383 } | |
14384 | |
14385 function returnBinding(node, name, binding) { | |
14386 return binding; | |
14387 } | |
14388 | |
14389 function sanitizeValue(value) { | |
14390 return value == null ? '' : value; | |
14391 } | |
14392 | |
14393 function updateText(node, value) { | |
14394 node.data = sanitizeValue(value); | |
14395 } | |
14396 | |
14397 function textBinding(node) { | |
14398 return function(value) { | |
14399 return updateText(node, value); | |
14400 }; | |
14401 } | |
14402 | |
14403 var maybeUpdateBindings = returnBinding; | |
14404 | |
14405 Object.defineProperty(Platform, 'enableBindingsReflection', { | |
14406 get: function() { | |
14407 return maybeUpdateBindings === updateBindings; | |
14408 }, | |
14409 set: function(enable) { | |
14410 maybeUpdateBindings = enable ? updateBindings : returnBinding; | |
14411 return enable; | |
14412 }, | |
14413 configurable: true | |
14414 }); | |
14415 | |
14416 Text.prototype.bind = function(name, value, oneTime) { | |
14417 if (name !== 'textContent') | |
14418 return Node.prototype.bind.call(this, name, value, oneTime); | |
14419 | |
14420 if (oneTime) | |
14421 return updateText(this, value); | |
14422 | |
14423 var observable = value; | |
14424 updateText(this, observable.open(textBinding(this))); | |
14425 return maybeUpdateBindings(this, name, observable); | |
14426 } | |
14427 | |
14428 function updateAttribute(el, name, conditional, value) { | |
14429 if (conditional) { | |
14430 if (value) | |
14431 el.setAttribute(name, ''); | |
14432 else | |
14433 el.removeAttribute(name); | |
14434 return; | |
14435 } | |
14436 | |
14437 el.setAttribute(name, sanitizeValue(value)); | |
14438 } | |
14439 | |
14440 function attributeBinding(el, name, conditional) { | |
14441 return function(value) { | |
14442 updateAttribute(el, name, conditional, value); | |
14443 }; | |
14444 } | |
14445 | |
14446 Element.prototype.bind = function(name, value, oneTime) { | |
14447 var conditional = name[name.length - 1] == '?'; | |
14448 if (conditional) { | |
14449 this.removeAttribute(name); | |
14450 name = name.slice(0, -1); | |
14451 } | |
14452 | |
14453 if (oneTime) | |
14454 return updateAttribute(this, name, conditional, value); | |
14455 | |
14456 | |
14457 var observable = value; | |
14458 updateAttribute(this, name, conditional, | |
14459 observable.open(attributeBinding(this, name, conditional))); | |
14460 | |
14461 return maybeUpdateBindings(this, name, observable); | |
14462 }; | |
14463 | |
14464 var checkboxEventType; | |
14465 (function() { | |
14466 // Attempt to feature-detect which event (change or click) is fired first | |
14467 // for checkboxes. | |
14468 var div = document.createElement('div'); | |
14469 var checkbox = div.appendChild(document.createElement('input')); | |
14470 checkbox.setAttribute('type', 'checkbox'); | |
14471 var first; | |
14472 var count = 0; | |
14473 checkbox.addEventListener('click', function(e) { | |
14474 count++; | |
14475 first = first || 'click'; | |
14476 }); | |
14477 checkbox.addEventListener('change', function() { | |
14478 count++; | |
14479 first = first || 'change'; | |
14480 }); | |
14481 | |
14482 var event = document.createEvent('MouseEvent'); | |
14483 event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, | |
14484 false, false, false, 0, null); | |
14485 checkbox.dispatchEvent(event); | |
14486 // WebKit/Blink don't fire the change event if the element is outside the | |
14487 // document, so assume 'change' for that case. | |
14488 checkboxEventType = count == 1 ? 'change' : first; | |
14489 })(); | |
14490 | |
14491 function getEventForInputType(element) { | |
14492 switch (element.type) { | |
14493 case 'checkbox': | |
14494 return checkboxEventType; | |
14495 case 'radio': | |
14496 case 'select-multiple': | |
14497 case 'select-one': | |
14498 return 'change'; | |
14499 case 'range': | |
14500 if (/Trident|MSIE/.test(navigator.userAgent)) | |
14501 return 'change'; | |
14502 default: | |
14503 return 'input'; | |
14504 } | |
14505 } | |
14506 | |
14507 function updateInput(input, property, value, santizeFn) { | |
14508 input[property] = (santizeFn || sanitizeValue)(value); | |
14509 } | |
14510 | |
14511 function inputBinding(input, property, santizeFn) { | |
14512 return function(value) { | |
14513 return updateInput(input, property, value, santizeFn); | |
14514 } | |
14515 } | |
14516 | |
14517 function noop() {} | |
14518 | |
14519 function bindInputEvent(input, property, observable, postEventFn) { | |
14520 var eventType = getEventForInputType(input); | |
14521 | |
14522 function eventHandler() { | |
14523 observable.setValue(input[property]); | |
14524 observable.discardChanges(); | |
14525 (postEventFn || noop)(input); | |
14526 Platform.performMicrotaskCheckpoint(); | |
14527 } | |
14528 input.addEventListener(eventType, eventHandler); | |
14529 | |
14530 return { | |
14531 close: function() { | |
14532 input.removeEventListener(eventType, eventHandler); | |
14533 observable.close(); | |
14534 }, | |
14535 | |
14536 observable_: observable | |
14537 } | |
14538 } | |
14539 | |
14540 function booleanSanitize(value) { | |
14541 return Boolean(value); | |
14542 } | |
14543 | |
14544 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. | |
14545 // Returns an array containing all radio buttons other than |element| that | |
14546 // have the same |name|, either in the form that |element| belongs to or, | |
14547 // if no form, in the document tree to which |element| belongs. | |
14548 // | |
14549 // This implementation is based upon the HTML spec definition of a | |
14550 // "radio button group": | |
14551 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group | |
14552 // | |
14553 function getAssociatedRadioButtons(element) { | |
14554 if (element.form) { | |
14555 return filter(element.form.elements, function(el) { | |
14556 return el != element && | |
14557 el.tagName == 'INPUT' && | |
14558 el.type == 'radio' && | |
14559 el.name == element.name; | |
14560 }); | |
14561 } else { | |
14562 var treeScope = getTreeScope(element); | |
14563 if (!treeScope) | |
14564 return []; | |
14565 var radios = treeScope.querySelectorAll( | |
14566 'input[type="radio"][name="' + element.name + '"]'); | |
14567 return filter(radios, function(el) { | |
14568 return el != element && !el.form; | |
14569 }); | |
14570 } | |
14571 } | |
14572 | |
14573 function checkedPostEvent(input) { | |
14574 // Only the radio button that is getting checked gets an event. We | |
14575 // therefore find all the associated radio buttons and update their | |
14576 // check binding manually. | |
14577 if (input.tagName === 'INPUT' && | |
14578 input.type === 'radio') { | |
14579 getAssociatedRadioButtons(input).forEach(function(radio) { | |
14580 var checkedBinding = radio.bindings_.checked; | |
14581 if (checkedBinding) { | |
14582 // Set the value directly to avoid an infinite call stack. | |
14583 checkedBinding.observable_.setValue(false); | |
14584 } | |
14585 }); | |
14586 } | |
14587 } | |
14588 | |
14589 HTMLInputElement.prototype.bind = function(name, value, oneTime) { | |
14590 if (name !== 'value' && name !== 'checked') | |
14591 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
14592 | |
14593 this.removeAttribute(name); | |
14594 var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; | |
14595 var postEventFn = name == 'checked' ? checkedPostEvent : noop; | |
14596 | |
14597 if (oneTime) | |
14598 return updateInput(this, name, value, sanitizeFn); | |
14599 | |
14600 | |
14601 var observable = value; | |
14602 var binding = bindInputEvent(this, name, observable, postEventFn); | |
14603 updateInput(this, name, | |
14604 observable.open(inputBinding(this, name, sanitizeFn)), | |
14605 sanitizeFn); | |
14606 | |
14607 // Checkboxes may need to update bindings of other checkboxes. | |
14608 return updateBindings(this, name, binding); | |
14609 } | |
14610 | |
14611 HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { | |
14612 if (name !== 'value') | |
14613 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
14614 | |
14615 this.removeAttribute('value'); | |
14616 | |
14617 if (oneTime) | |
14618 return updateInput(this, 'value', value); | |
14619 | |
14620 var observable = value; | |
14621 var binding = bindInputEvent(this, 'value', observable); | |
14622 updateInput(this, 'value', | |
14623 observable.open(inputBinding(this, 'value', sanitizeValue))); | |
14624 return maybeUpdateBindings(this, name, binding); | |
14625 } | |
14626 | |
14627 function updateOption(option, value) { | |
14628 var parentNode = option.parentNode;; | |
14629 var select; | |
14630 var selectBinding; | |
14631 var oldValue; | |
14632 if (parentNode instanceof HTMLSelectElement && | |
14633 parentNode.bindings_ && | |
14634 parentNode.bindings_.value) { | |
14635 select = parentNode; | |
14636 selectBinding = select.bindings_.value; | |
14637 oldValue = select.value; | |
14638 } | |
14639 | |
14640 option.value = sanitizeValue(value); | |
14641 | |
14642 if (select && select.value != oldValue) { | |
14643 selectBinding.observable_.setValue(select.value); | |
14644 selectBinding.observable_.discardChanges(); | |
14645 Platform.performMicrotaskCheckpoint(); | |
14646 } | |
14647 } | |
14648 | |
14649 function optionBinding(option) { | |
14650 return function(value) { | |
14651 updateOption(option, value); | |
14652 } | |
14653 } | |
14654 | |
14655 HTMLOptionElement.prototype.bind = function(name, value, oneTime) { | |
14656 if (name !== 'value') | |
14657 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
14658 | |
14659 this.removeAttribute('value'); | |
14660 | |
14661 if (oneTime) | |
14662 return updateOption(this, value); | |
14663 | |
14664 var observable = value; | |
14665 var binding = bindInputEvent(this, 'value', observable); | |
14666 updateOption(this, observable.open(optionBinding(this))); | |
14667 return maybeUpdateBindings(this, name, binding); | |
14668 } | |
14669 | |
14670 HTMLSelectElement.prototype.bind = function(name, value, oneTime) { | |
14671 if (name === 'selectedindex') | |
14672 name = 'selectedIndex'; | |
14673 | |
14674 if (name !== 'selectedIndex' && name !== 'value') | |
14675 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
14676 | |
14677 this.removeAttribute(name); | |
14678 | |
14679 if (oneTime) | |
14680 return updateInput(this, name, value); | |
14681 | |
14682 var observable = value; | |
14683 var binding = bindInputEvent(this, name, observable); | |
14684 updateInput(this, name, | |
14685 observable.open(inputBinding(this, name))); | |
14686 | |
14687 // Option update events may need to access select bindings. | |
14688 return updateBindings(this, name, binding); | |
14689 } | |
14690 })(this); | |
14691 | |
14692 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
14693 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
14694 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
14695 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
14696 // Code distributed by Google as part of the polymer project is also | |
14697 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
14698 | |
14699 (function(global) { | |
14700 'use strict'; | |
14701 | |
14702 function assert(v) { | |
14703 if (!v) | |
14704 throw new Error('Assertion failed'); | |
14705 } | |
14706 | |
14707 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
14708 | |
14709 function getFragmentRoot(node) { | |
14710 var p; | |
14711 while (p = node.parentNode) { | |
14712 node = p; | |
14713 } | |
14714 | |
14715 return node; | |
14716 } | |
14717 | |
14718 function searchRefId(node, id) { | |
14719 if (!id) | |
14720 return; | |
14721 | |
14722 var ref; | |
14723 var selector = '#' + id; | |
14724 while (!ref) { | |
14725 node = getFragmentRoot(node); | |
14726 | |
14727 if (node.protoContent_) | |
14728 ref = node.protoContent_.querySelector(selector); | |
14729 else if (node.getElementById) | |
14730 ref = node.getElementById(id); | |
14731 | |
14732 if (ref || !node.templateCreator_) | |
14733 break | |
14734 | |
14735 node = node.templateCreator_; | |
14736 } | |
14737 | |
14738 return ref; | |
14739 } | |
14740 | |
14741 function getInstanceRoot(node) { | |
14742 while (node.parentNode) { | |
14743 node = node.parentNode; | |
14744 } | |
14745 return node.templateCreator_ ? node : null; | |
14746 } | |
14747 | |
14748 var Map; | |
14749 if (global.Map && typeof global.Map.prototype.forEach === 'function') { | |
14750 Map = global.Map; | |
14751 } else { | |
14752 Map = function() { | |
14753 this.keys = []; | |
14754 this.values = []; | |
14755 }; | |
14756 | |
14757 Map.prototype = { | |
14758 set: function(key, value) { | |
14759 var index = this.keys.indexOf(key); | |
14760 if (index < 0) { | |
14761 this.keys.push(key); | |
14762 this.values.push(value); | |
14763 } else { | |
14764 this.values[index] = value; | |
14765 } | |
14766 }, | |
14767 | |
14768 get: function(key) { | |
14769 var index = this.keys.indexOf(key); | |
14770 if (index < 0) | |
14771 return; | |
14772 | |
14773 return this.values[index]; | |
14774 }, | |
14775 | |
14776 delete: function(key, value) { | |
14777 var index = this.keys.indexOf(key); | |
14778 if (index < 0) | |
14779 return false; | |
14780 | |
14781 this.keys.splice(index, 1); | |
14782 this.values.splice(index, 1); | |
14783 return true; | |
14784 }, | |
14785 | |
14786 forEach: function(f, opt_this) { | |
14787 for (var i = 0; i < this.keys.length; i++) | |
14788 f.call(opt_this || this, this.values[i], this.keys[i], this); | |
14789 } | |
14790 }; | |
14791 } | |
14792 | |
14793 // JScript does not have __proto__. We wrap all object literals with | |
14794 // createObject which uses Object.create, Object.defineProperty and | |
14795 // Object.getOwnPropertyDescriptor to create a new object that does the exact | |
14796 // same thing. The main downside to this solution is that we have to extract | |
14797 // all those property descriptors for IE. | |
14798 var createObject = ('__proto__' in {}) ? | |
14799 function(obj) { return obj; } : | |
14800 function(obj) { | |
14801 var proto = obj.__proto__; | |
14802 if (!proto) | |
14803 return obj; | |
14804 var newObject = Object.create(proto); | |
14805 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
14806 Object.defineProperty(newObject, name, | |
14807 Object.getOwnPropertyDescriptor(obj, name)); | |
14808 }); | |
14809 return newObject; | |
14810 }; | |
14811 | |
14812 // IE does not support have Document.prototype.contains. | |
14813 if (typeof document.contains != 'function') { | |
14814 Document.prototype.contains = function(node) { | |
14815 if (node === this || node.parentNode === this) | |
14816 return true; | |
14817 return this.documentElement.contains(node); | |
14818 } | |
14819 } | |
14820 | |
14821 var BIND = 'bind'; | |
14822 var REPEAT = 'repeat'; | |
14823 var IF = 'if'; | |
14824 | |
14825 var templateAttributeDirectives = { | |
14826 'template': true, | |
14827 'repeat': true, | |
14828 'bind': true, | |
14829 'ref': true | |
14830 }; | |
14831 | |
14832 var semanticTemplateElements = { | |
14833 'THEAD': true, | |
14834 'TBODY': true, | |
14835 'TFOOT': true, | |
14836 'TH': true, | |
14837 'TR': true, | |
14838 'TD': true, | |
14839 'COLGROUP': true, | |
14840 'COL': true, | |
14841 'CAPTION': true, | |
14842 'OPTION': true, | |
14843 'OPTGROUP': true | |
14844 }; | |
14845 | |
14846 var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined'; | |
14847 if (hasTemplateElement) { | |
14848 // TODO(rafaelw): Remove when fix for | |
14849 // https://codereview.chromium.org/164803002/ | |
14850 // makes it to Chrome release. | |
14851 (function() { | |
14852 var t = document.createElement('template'); | |
14853 var d = t.content.ownerDocument; | |
14854 var html = d.appendChild(d.createElement('html')); | |
14855 var head = html.appendChild(d.createElement('head')); | |
14856 var base = d.createElement('base'); | |
14857 base.href = document.baseURI; | |
14858 head.appendChild(base); | |
14859 })(); | |
14860 } | |
14861 | |
14862 var allTemplatesSelectors = 'template, ' + | |
14863 Object.keys(semanticTemplateElements).map(function(tagName) { | |
14864 return tagName.toLowerCase() + '[template]'; | |
14865 }).join(', '); | |
14866 | |
14867 function isSVGTemplate(el) { | |
14868 return el.tagName == 'template' && | |
14869 el.namespaceURI == 'http://www.w3.org/2000/svg'; | |
14870 } | |
14871 | |
14872 function isHTMLTemplate(el) { | |
14873 return el.tagName == 'TEMPLATE' && | |
14874 el.namespaceURI == 'http://www.w3.org/1999/xhtml'; | |
14875 } | |
14876 | |
14877 function isAttributeTemplate(el) { | |
14878 return Boolean(semanticTemplateElements[el.tagName] && | |
14879 el.hasAttribute('template')); | |
14880 } | |
14881 | |
14882 function isTemplate(el) { | |
14883 if (el.isTemplate_ === undefined) | |
14884 el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el); | |
14885 | |
14886 return el.isTemplate_; | |
14887 } | |
14888 | |
14889 // FIXME: Observe templates being added/removed from documents | |
14890 // FIXME: Expose imperative API to decorate and observe templates in | |
14891 // "disconnected tress" (e.g. ShadowRoot) | |
14892 document.addEventListener('DOMContentLoaded', function(e) { | |
14893 bootstrapTemplatesRecursivelyFrom(document); | |
14894 // FIXME: Is this needed? Seems like it shouldn't be. | |
14895 Platform.performMicrotaskCheckpoint(); | |
14896 }, false); | |
14897 | |
14898 function forAllTemplatesFrom(node, fn) { | |
14899 var subTemplates = node.querySelectorAll(allTemplatesSelectors); | |
14900 | |
14901 if (isTemplate(node)) | |
14902 fn(node) | |
14903 forEach(subTemplates, fn); | |
14904 } | |
14905 | |
14906 function bootstrapTemplatesRecursivelyFrom(node) { | |
14907 function bootstrap(template) { | |
14908 if (!HTMLTemplateElement.decorate(template)) | |
14909 bootstrapTemplatesRecursivelyFrom(template.content); | |
14910 } | |
14911 | |
14912 forAllTemplatesFrom(node, bootstrap); | |
14913 } | |
14914 | |
14915 if (!hasTemplateElement) { | |
14916 /** | |
14917 * This represents a <template> element. | |
14918 * @constructor | |
14919 * @extends {HTMLElement} | |
14920 */ | |
14921 global.HTMLTemplateElement = function() { | |
14922 throw TypeError('Illegal constructor'); | |
14923 }; | |
14924 } | |
14925 | |
14926 var hasProto = '__proto__' in {}; | |
14927 | |
14928 function mixin(to, from) { | |
14929 Object.getOwnPropertyNames(from).forEach(function(name) { | |
14930 Object.defineProperty(to, name, | |
14931 Object.getOwnPropertyDescriptor(from, name)); | |
14932 }); | |
14933 } | |
14934 | |
14935 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner | |
14936 function getOrCreateTemplateContentsOwner(template) { | |
14937 var doc = template.ownerDocument | |
14938 if (!doc.defaultView) | |
14939 return doc; | |
14940 var d = doc.templateContentsOwner_; | |
14941 if (!d) { | |
14942 // TODO(arv): This should either be a Document or HTMLDocument depending | |
14943 // on doc. | |
14944 d = doc.implementation.createHTMLDocument(''); | |
14945 while (d.lastChild) { | |
14946 d.removeChild(d.lastChild); | |
14947 } | |
14948 doc.templateContentsOwner_ = d; | |
14949 } | |
14950 return d; | |
14951 } | |
14952 | |
14953 function getTemplateStagingDocument(template) { | |
14954 if (!template.stagingDocument_) { | |
14955 var owner = template.ownerDocument; | |
14956 if (!owner.stagingDocument_) { | |
14957 owner.stagingDocument_ = owner.implementation.createHTMLDocument(''); | |
14958 | |
14959 // TODO(rafaelw): Remove when fix for | |
14960 // https://codereview.chromium.org/164803002/ | |
14961 // makes it to Chrome release. | |
14962 var base = owner.stagingDocument_.createElement('base'); | |
14963 base.href = document.baseURI; | |
14964 owner.stagingDocument_.head.appendChild(base); | |
14965 | |
14966 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_; | |
14967 } | |
14968 | |
14969 template.stagingDocument_ = owner.stagingDocument_; | |
14970 } | |
14971 | |
14972 return template.stagingDocument_; | |
14973 } | |
14974 | |
14975 // For non-template browsers, the parser will disallow <template> in certain | |
14976 // locations, so we allow "attribute templates" which combine the template | |
14977 // element with the top-level container node of the content, e.g. | |
14978 // | |
14979 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr> | |
14980 // | |
14981 // becomes | |
14982 // | |
14983 // <template repeat="{{ foo }}"> | |
14984 // + #document-fragment | |
14985 // + <tr class="bar"> | |
14986 // + <td>Bar</td> | |
14987 // | |
14988 function extractTemplateFromAttributeTemplate(el) { | |
14989 var template = el.ownerDocument.createElement('template'); | |
14990 el.parentNode.insertBefore(template, el); | |
14991 | |
14992 var attribs = el.attributes; | |
14993 var count = attribs.length; | |
14994 while (count-- > 0) { | |
14995 var attrib = attribs[count]; | |
14996 if (templateAttributeDirectives[attrib.name]) { | |
14997 if (attrib.name !== 'template') | |
14998 template.setAttribute(attrib.name, attrib.value); | |
14999 el.removeAttribute(attrib.name); | |
15000 } | |
15001 } | |
15002 | |
15003 return template; | |
15004 } | |
15005 | |
15006 function extractTemplateFromSVGTemplate(el) { | |
15007 var template = el.ownerDocument.createElement('template'); | |
15008 el.parentNode.insertBefore(template, el); | |
15009 | |
15010 var attribs = el.attributes; | |
15011 var count = attribs.length; | |
15012 while (count-- > 0) { | |
15013 var attrib = attribs[count]; | |
15014 template.setAttribute(attrib.name, attrib.value); | |
15015 el.removeAttribute(attrib.name); | |
15016 } | |
15017 | |
15018 el.parentNode.removeChild(el); | |
15019 return template; | |
15020 } | |
15021 | |
15022 function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) { | |
15023 var content = template.content; | |
15024 if (useRoot) { | |
15025 content.appendChild(el); | |
15026 return; | |
15027 } | |
15028 | |
15029 var child; | |
15030 while (child = el.firstChild) { | |
15031 content.appendChild(child); | |
15032 } | |
15033 } | |
15034 | |
15035 var templateObserver; | |
15036 if (typeof MutationObserver == 'function') { | |
15037 templateObserver = new MutationObserver(function(records) { | |
15038 for (var i = 0; i < records.length; i++) { | |
15039 records[i].target.refChanged_(); | |
15040 } | |
15041 }); | |
15042 } | |
15043 | |
15044 /** | |
15045 * Ensures proper API and content model for template elements. | |
15046 * @param {HTMLTemplateElement} opt_instanceRef The template element which | |
15047 * |el| template element will return as the value of its ref(), and whose | |
15048 * content will be used as source when createInstance() is invoked. | |
15049 */ | |
15050 HTMLTemplateElement.decorate = function(el, opt_instanceRef) { | |
15051 if (el.templateIsDecorated_) | |
15052 return false; | |
15053 | |
15054 var templateElement = el; | |
15055 templateElement.templateIsDecorated_ = true; | |
15056 | |
15057 var isNativeHTMLTemplate = isHTMLTemplate(templateElement) && | |
15058 hasTemplateElement; | |
15059 var bootstrapContents = isNativeHTMLTemplate; | |
15060 var liftContents = !isNativeHTMLTemplate; | |
15061 var liftRoot = false; | |
15062 | |
15063 if (!isNativeHTMLTemplate) { | |
15064 if (isAttributeTemplate(templateElement)) { | |
15065 assert(!opt_instanceRef); | |
15066 templateElement = extractTemplateFromAttributeTemplate(el); | |
15067 templateElement.templateIsDecorated_ = true; | |
15068 isNativeHTMLTemplate = hasTemplateElement; | |
15069 liftRoot = true; | |
15070 } else if (isSVGTemplate(templateElement)) { | |
15071 templateElement = extractTemplateFromSVGTemplate(el); | |
15072 templateElement.templateIsDecorated_ = true; | |
15073 isNativeHTMLTemplate = hasTemplateElement; | |
15074 } | |
15075 } | |
15076 | |
15077 if (!isNativeHTMLTemplate) { | |
15078 fixTemplateElementPrototype(templateElement); | |
15079 var doc = getOrCreateTemplateContentsOwner(templateElement); | |
15080 templateElement.content_ = doc.createDocumentFragment(); | |
15081 } | |
15082 | |
15083 if (opt_instanceRef) { | |
15084 // template is contained within an instance, its direct content must be | |
15085 // empty | |
15086 templateElement.instanceRef_ = opt_instanceRef; | |
15087 } else if (liftContents) { | |
15088 liftNonNativeTemplateChildrenIntoContent(templateElement, | |
15089 el, | |
15090 liftRoot); | |
15091 } else if (bootstrapContents) { | |
15092 bootstrapTemplatesRecursivelyFrom(templateElement.content); | |
15093 } | |
15094 | |
15095 return true; | |
15096 }; | |
15097 | |
15098 // TODO(rafaelw): This used to decorate recursively all templates from a given | |
15099 // node. This happens by default on 'DOMContentLoaded', but may be needed | |
15100 // in subtrees not descendent from document (e.g. ShadowRoot). | |
15101 // Review whether this is the right public API. | |
15102 HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom; | |
15103 | |
15104 var htmlElement = global.HTMLUnknownElement || HTMLElement; | |
15105 | |
15106 var contentDescriptor = { | |
15107 get: function() { | |
15108 return this.content_; | |
15109 }, | |
15110 enumerable: true, | |
15111 configurable: true | |
15112 }; | |
15113 | |
15114 if (!hasTemplateElement) { | |
15115 // Gecko is more picky with the prototype than WebKit. Make sure to use the | |
15116 // same prototype as created in the constructor. | |
15117 HTMLTemplateElement.prototype = Object.create(htmlElement.prototype); | |
15118 | |
15119 Object.defineProperty(HTMLTemplateElement.prototype, 'content', | |
15120 contentDescriptor); | |
15121 } | |
15122 | |
15123 function fixTemplateElementPrototype(el) { | |
15124 if (hasProto) | |
15125 el.__proto__ = HTMLTemplateElement.prototype; | |
15126 else | |
15127 mixin(el, HTMLTemplateElement.prototype); | |
15128 } | |
15129 | |
15130 function ensureSetModelScheduled(template) { | |
15131 if (!template.setModelFn_) { | |
15132 template.setModelFn_ = function() { | |
15133 template.setModelFnScheduled_ = false; | |
15134 var map = getBindings(template, | |
15135 template.delegate_ && template.delegate_.prepareBinding); | |
15136 processBindings(template, map, template.model_); | |
15137 }; | |
15138 } | |
15139 | |
15140 if (!template.setModelFnScheduled_) { | |
15141 template.setModelFnScheduled_ = true; | |
15142 Observer.runEOM_(template.setModelFn_); | |
15143 } | |
15144 } | |
15145 | |
15146 mixin(HTMLTemplateElement.prototype, { | |
15147 bind: function(name, value, oneTime) { | |
15148 if (name != 'ref') | |
15149 return Element.prototype.bind.call(this, name, value, oneTime); | |
15150 | |
15151 var self = this; | |
15152 var ref = oneTime ? value : value.open(function(ref) { | |
15153 self.setAttribute('ref', ref); | |
15154 self.refChanged_(); | |
15155 }); | |
15156 | |
15157 this.setAttribute('ref', ref); | |
15158 this.refChanged_(); | |
15159 if (oneTime) | |
15160 return; | |
15161 | |
15162 if (!this.bindings_) { | |
15163 this.bindings_ = { ref: value }; | |
15164 } else { | |
15165 this.bindings_.ref = value; | |
15166 } | |
15167 | |
15168 return value; | |
15169 }, | |
15170 | |
15171 processBindingDirectives_: function(directives) { | |
15172 if (this.iterator_) | |
15173 this.iterator_.closeDeps(); | |
15174 | |
15175 if (!directives.if && !directives.bind && !directives.repeat) { | |
15176 if (this.iterator_) { | |
15177 this.iterator_.close(); | |
15178 this.iterator_ = undefined; | |
15179 } | |
15180 | |
15181 return; | |
15182 } | |
15183 | |
15184 if (!this.iterator_) { | |
15185 this.iterator_ = new TemplateIterator(this); | |
15186 } | |
15187 | |
15188 this.iterator_.updateDependencies(directives, this.model_); | |
15189 | |
15190 if (templateObserver) { | |
15191 templateObserver.observe(this, { attributes: true, | |
15192 attributeFilter: ['ref'] }); | |
15193 } | |
15194 | |
15195 return this.iterator_; | |
15196 }, | |
15197 | |
15198 createInstance: function(model, bindingDelegate, delegate_) { | |
15199 if (bindingDelegate) | |
15200 delegate_ = this.newDelegate_(bindingDelegate); | |
15201 | |
15202 if (!this.refContent_) | |
15203 this.refContent_ = this.ref_.content; | |
15204 var content = this.refContent_; | |
15205 if (content.firstChild === null) | |
15206 return emptyInstance; | |
15207 | |
15208 var map = this.bindingMap_; | |
15209 if (!map || map.content !== content) { | |
15210 // TODO(rafaelw): Setup a MutationObserver on content to detect | |
15211 // when the instanceMap is invalid. | |
15212 map = createInstanceBindingMap(content, | |
15213 delegate_ && delegate_.prepareBinding) || []; | |
15214 map.content = content; | |
15215 this.bindingMap_ = map; | |
15216 } | |
15217 | |
15218 var stagingDocument = getTemplateStagingDocument(this); | |
15219 var instance = stagingDocument.createDocumentFragment(); | |
15220 instance.templateCreator_ = this; | |
15221 instance.protoContent_ = content; | |
15222 instance.bindings_ = []; | |
15223 instance.terminator_ = null; | |
15224 var instanceRecord = instance.templateInstance_ = { | |
15225 firstNode: null, | |
15226 lastNode: null, | |
15227 model: model | |
15228 }; | |
15229 | |
15230 var i = 0; | |
15231 var collectTerminator = false; | |
15232 for (var child = content.firstChild; child; child = child.nextSibling) { | |
15233 // The terminator of the instance is the clone of the last child of the | |
15234 // content. If the last child is an active template, it may produce | |
15235 // instances as a result of production, so simply collecting the last | |
15236 // child of the instance after it has finished producing may be wrong. | |
15237 if (child.nextSibling === null) | |
15238 collectTerminator = true; | |
15239 | |
15240 var clone = cloneAndBindInstance(child, instance, stagingDocument, | |
15241 map.children[i++], | |
15242 model, | |
15243 delegate_, | |
15244 instance.bindings_); | |
15245 clone.templateInstance_ = instanceRecord; | |
15246 if (collectTerminator) | |
15247 instance.terminator_ = clone; | |
15248 } | |
15249 | |
15250 instanceRecord.firstNode = instance.firstChild; | |
15251 instanceRecord.lastNode = instance.lastChild; | |
15252 instance.templateCreator_ = undefined; | |
15253 instance.protoContent_ = undefined; | |
15254 return instance; | |
15255 }, | |
15256 | |
15257 get model() { | |
15258 return this.model_; | |
15259 }, | |
15260 | |
15261 set model(model) { | |
15262 this.model_ = model; | |
15263 ensureSetModelScheduled(this); | |
15264 }, | |
15265 | |
15266 get bindingDelegate() { | |
15267 return this.delegate_ && this.delegate_.raw; | |
15268 }, | |
15269 | |
15270 refChanged_: function() { | |
15271 if (!this.iterator_ || this.refContent_ === this.ref_.content) | |
15272 return; | |
15273 | |
15274 this.refContent_ = undefined; | |
15275 this.iterator_.valueChanged(); | |
15276 this.iterator_.updateIteratedValue(); | |
15277 }, | |
15278 | |
15279 clear: function() { | |
15280 this.model_ = undefined; | |
15281 this.delegate_ = undefined; | |
15282 if (this.bindings_ && this.bindings_.ref) | |
15283 this.bindings_.ref.close() | |
15284 this.refContent_ = undefined; | |
15285 if (!this.iterator_) | |
15286 return; | |
15287 this.iterator_.valueChanged(); | |
15288 this.iterator_.close() | |
15289 this.iterator_ = undefined; | |
15290 }, | |
15291 | |
15292 setDelegate_: function(delegate) { | |
15293 this.delegate_ = delegate; | |
15294 this.bindingMap_ = undefined; | |
15295 if (this.iterator_) { | |
15296 this.iterator_.instancePositionChangedFn_ = undefined; | |
15297 this.iterator_.instanceModelFn_ = undefined; | |
15298 } | |
15299 }, | |
15300 | |
15301 newDelegate_: function(bindingDelegate) { | |
15302 if (!bindingDelegate) | |
15303 return {}; | |
15304 | |
15305 function delegateFn(name) { | |
15306 var fn = bindingDelegate && bindingDelegate[name]; | |
15307 if (typeof fn != 'function') | |
15308 return; | |
15309 | |
15310 return function() { | |
15311 return fn.apply(bindingDelegate, arguments); | |
15312 }; | |
15313 } | |
15314 | |
15315 return { | |
15316 raw: bindingDelegate, | |
15317 prepareBinding: delegateFn('prepareBinding'), | |
15318 prepareInstanceModel: delegateFn('prepareInstanceModel'), | |
15319 prepareInstancePositionChanged: | |
15320 delegateFn('prepareInstancePositionChanged') | |
15321 }; | |
15322 }, | |
15323 | |
15324 // TODO(rafaelw): Assigning .bindingDelegate always succeeds. It may | |
15325 // make sense to issue a warning or even throw if the template is already | |
15326 // "activated", since this would be a strange thing to do. | |
15327 set bindingDelegate(bindingDelegate) { | |
15328 if (this.delegate_) { | |
15329 throw Error('Template must be cleared before a new bindingDelegate ' + | |
15330 'can be assigned'); | |
15331 } | |
15332 | |
15333 this.setDelegate_(this.newDelegate_(bindingDelegate)); | |
15334 }, | |
15335 | |
15336 get ref_() { | |
15337 var ref = searchRefId(this, this.getAttribute('ref')); | |
15338 if (!ref) | |
15339 ref = this.instanceRef_; | |
15340 | |
15341 if (!ref) | |
15342 return this; | |
15343 | |
15344 var nextRef = ref.ref_; | |
15345 return nextRef ? nextRef : ref; | |
15346 } | |
15347 }); | |
15348 | |
15349 // Returns | |
15350 // a) undefined if there are no mustaches. | |
15351 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one
mustache. | |
15352 function parseMustaches(s, name, node, prepareBindingFn) { | |
15353 if (!s || !s.length) | |
15354 return; | |
15355 | |
15356 var tokens; | |
15357 var length = s.length; | |
15358 var startIndex = 0, lastIndex = 0, endIndex = 0; | |
15359 var onlyOneTime = true; | |
15360 while (lastIndex < length) { | |
15361 var startIndex = s.indexOf('{{', lastIndex); | |
15362 var oneTimeStart = s.indexOf('[[', lastIndex); | |
15363 var oneTime = false; | |
15364 var terminator = '}}'; | |
15365 | |
15366 if (oneTimeStart >= 0 && | |
15367 (startIndex < 0 || oneTimeStart < startIndex)) { | |
15368 startIndex = oneTimeStart; | |
15369 oneTime = true; | |
15370 terminator = ']]'; | |
15371 } | |
15372 | |
15373 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2); | |
15374 | |
15375 if (endIndex < 0) { | |
15376 if (!tokens) | |
15377 return; | |
15378 | |
15379 tokens.push(s.slice(lastIndex)); // TEXT | |
15380 break; | |
15381 } | |
15382 | |
15383 tokens = tokens || []; | |
15384 tokens.push(s.slice(lastIndex, startIndex)); // TEXT | |
15385 var pathString = s.slice(startIndex + 2, endIndex).trim(); | |
15386 tokens.push(oneTime); // ONE_TIME? | |
15387 onlyOneTime = onlyOneTime && oneTime; | |
15388 var delegateFn = prepareBindingFn && | |
15389 prepareBindingFn(pathString, name, node); | |
15390 // Don't try to parse the expression if there's a prepareBinding function | |
15391 if (delegateFn == null) { | |
15392 tokens.push(Path.get(pathString)); // PATH | |
15393 } else { | |
15394 tokens.push(null); | |
15395 } | |
15396 tokens.push(delegateFn); // DELEGATE_FN | |
15397 lastIndex = endIndex + 2; | |
15398 } | |
15399 | |
15400 if (lastIndex === length) | |
15401 tokens.push(''); // TEXT | |
15402 | |
15403 tokens.hasOnePath = tokens.length === 5; | |
15404 tokens.isSimplePath = tokens.hasOnePath && | |
15405 tokens[0] == '' && | |
15406 tokens[4] == ''; | |
15407 tokens.onlyOneTime = onlyOneTime; | |
15408 | |
15409 tokens.combinator = function(values) { | |
15410 var newValue = tokens[0]; | |
15411 | |
15412 for (var i = 1; i < tokens.length; i += 4) { | |
15413 var value = tokens.hasOnePath ? values : values[(i - 1) / 4]; | |
15414 if (value !== undefined) | |
15415 newValue += value; | |
15416 newValue += tokens[i + 3]; | |
15417 } | |
15418 | |
15419 return newValue; | |
15420 } | |
15421 | |
15422 return tokens; | |
15423 }; | |
15424 | |
15425 function processOneTimeBinding(name, tokens, node, model) { | |
15426 if (tokens.hasOnePath) { | |
15427 var delegateFn = tokens[3]; | |
15428 var value = delegateFn ? delegateFn(model, node, true) : | |
15429 tokens[2].getValueFrom(model); | |
15430 return tokens.isSimplePath ? value : tokens.combinator(value); | |
15431 } | |
15432 | |
15433 var values = []; | |
15434 for (var i = 1; i < tokens.length; i += 4) { | |
15435 var delegateFn = tokens[i + 2]; | |
15436 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) : | |
15437 tokens[i + 1].getValueFrom(model); | |
15438 } | |
15439 | |
15440 return tokens.combinator(values); | |
15441 } | |
15442 | |
15443 function processSinglePathBinding(name, tokens, node, model) { | |
15444 var delegateFn = tokens[3]; | |
15445 var observer = delegateFn ? delegateFn(model, node, false) : | |
15446 new PathObserver(model, tokens[2]); | |
15447 | |
15448 return tokens.isSimplePath ? observer : | |
15449 new ObserverTransform(observer, tokens.combinator); | |
15450 } | |
15451 | |
15452 function processBinding(name, tokens, node, model) { | |
15453 if (tokens.onlyOneTime) | |
15454 return processOneTimeBinding(name, tokens, node, model); | |
15455 | |
15456 if (tokens.hasOnePath) | |
15457 return processSinglePathBinding(name, tokens, node, model); | |
15458 | |
15459 var observer = new CompoundObserver(); | |
15460 | |
15461 for (var i = 1; i < tokens.length; i += 4) { | |
15462 var oneTime = tokens[i]; | |
15463 var delegateFn = tokens[i + 2]; | |
15464 | |
15465 if (delegateFn) { | |
15466 var value = delegateFn(model, node, oneTime); | |
15467 if (oneTime) | |
15468 observer.addPath(value) | |
15469 else | |
15470 observer.addObserver(value); | |
15471 continue; | |
15472 } | |
15473 | |
15474 var path = tokens[i + 1]; | |
15475 if (oneTime) | |
15476 observer.addPath(path.getValueFrom(model)) | |
15477 else | |
15478 observer.addPath(model, path); | |
15479 } | |
15480 | |
15481 return new ObserverTransform(observer, tokens.combinator); | |
15482 } | |
15483 | |
15484 function processBindings(node, bindings, model, instanceBindings) { | |
15485 for (var i = 0; i < bindings.length; i += 2) { | |
15486 var name = bindings[i] | |
15487 var tokens = bindings[i + 1]; | |
15488 var value = processBinding(name, tokens, node, model); | |
15489 var binding = node.bind(name, value, tokens.onlyOneTime); | |
15490 if (binding && instanceBindings) | |
15491 instanceBindings.push(binding); | |
15492 } | |
15493 | |
15494 if (!bindings.isTemplate) | |
15495 return; | |
15496 | |
15497 node.model_ = model; | |
15498 var iter = node.processBindingDirectives_(bindings); | |
15499 if (instanceBindings && iter) | |
15500 instanceBindings.push(iter); | |
15501 } | |
15502 | |
15503 function parseWithDefault(el, name, prepareBindingFn) { | |
15504 var v = el.getAttribute(name); | |
15505 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn); | |
15506 } | |
15507 | |
15508 function parseAttributeBindings(element, prepareBindingFn) { | |
15509 assert(element); | |
15510 | |
15511 var bindings = []; | |
15512 var ifFound = false; | |
15513 var bindFound = false; | |
15514 | |
15515 for (var i = 0; i < element.attributes.length; i++) { | |
15516 var attr = element.attributes[i]; | |
15517 var name = attr.name; | |
15518 var value = attr.value; | |
15519 | |
15520 // Allow bindings expressed in attributes to be prefixed with underbars. | |
15521 // We do this to allow correct semantics for browsers that don't implement | |
15522 // <template> where certain attributes might trigger side-effects -- and | |
15523 // for IE which sanitizes certain attributes, disallowing mustache | |
15524 // replacements in their text. | |
15525 while (name[0] === '_') { | |
15526 name = name.substring(1); | |
15527 } | |
15528 | |
15529 if (isTemplate(element) && | |
15530 (name === IF || name === BIND || name === REPEAT)) { | |
15531 continue; | |
15532 } | |
15533 | |
15534 var tokens = parseMustaches(value, name, element, | |
15535 prepareBindingFn); | |
15536 if (!tokens) | |
15537 continue; | |
15538 | |
15539 bindings.push(name, tokens); | |
15540 } | |
15541 | |
15542 if (isTemplate(element)) { | |
15543 bindings.isTemplate = true; | |
15544 bindings.if = parseWithDefault(element, IF, prepareBindingFn); | |
15545 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn); | |
15546 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn); | |
15547 | |
15548 if (bindings.if && !bindings.bind && !bindings.repeat) | |
15549 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn); | |
15550 } | |
15551 | |
15552 return bindings; | |
15553 } | |
15554 | |
15555 function getBindings(node, prepareBindingFn) { | |
15556 if (node.nodeType === Node.ELEMENT_NODE) | |
15557 return parseAttributeBindings(node, prepareBindingFn); | |
15558 | |
15559 if (node.nodeType === Node.TEXT_NODE) { | |
15560 var tokens = parseMustaches(node.data, 'textContent', node, | |
15561 prepareBindingFn); | |
15562 if (tokens) | |
15563 return ['textContent', tokens]; | |
15564 } | |
15565 | |
15566 return []; | |
15567 } | |
15568 | |
15569 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model, | |
15570 delegate, | |
15571 instanceBindings, | |
15572 instanceRecord) { | |
15573 var clone = parent.appendChild(stagingDocument.importNode(node, false)); | |
15574 | |
15575 var i = 0; | |
15576 for (var child = node.firstChild; child; child = child.nextSibling) { | |
15577 cloneAndBindInstance(child, clone, stagingDocument, | |
15578 bindings.children[i++], | |
15579 model, | |
15580 delegate, | |
15581 instanceBindings); | |
15582 } | |
15583 | |
15584 if (bindings.isTemplate) { | |
15585 HTMLTemplateElement.decorate(clone, node); | |
15586 if (delegate) | |
15587 clone.setDelegate_(delegate); | |
15588 } | |
15589 | |
15590 processBindings(clone, bindings, model, instanceBindings); | |
15591 return clone; | |
15592 } | |
15593 | |
15594 function createInstanceBindingMap(node, prepareBindingFn) { | |
15595 var map = getBindings(node, prepareBindingFn); | |
15596 map.children = {}; | |
15597 var index = 0; | |
15598 for (var child = node.firstChild; child; child = child.nextSibling) { | |
15599 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn); | |
15600 } | |
15601 | |
15602 return map; | |
15603 } | |
15604 | |
15605 Object.defineProperty(Node.prototype, 'templateInstance', { | |
15606 get: function() { | |
15607 var instance = this.templateInstance_; | |
15608 return instance ? instance : | |
15609 (this.parentNode ? this.parentNode.templateInstance : undefined); | |
15610 } | |
15611 }); | |
15612 | |
15613 var emptyInstance = document.createDocumentFragment(); | |
15614 emptyInstance.bindings_ = []; | |
15615 emptyInstance.terminator_ = null; | |
15616 | |
15617 function TemplateIterator(templateElement) { | |
15618 this.closed = false; | |
15619 this.templateElement_ = templateElement; | |
15620 this.instances = []; | |
15621 this.deps = undefined; | |
15622 this.iteratedValue = []; | |
15623 this.presentValue = undefined; | |
15624 this.arrayObserver = undefined; | |
15625 } | |
15626 | |
15627 TemplateIterator.prototype = { | |
15628 closeDeps: function() { | |
15629 var deps = this.deps; | |
15630 if (deps) { | |
15631 if (deps.ifOneTime === false) | |
15632 deps.ifValue.close(); | |
15633 if (deps.oneTime === false) | |
15634 deps.value.close(); | |
15635 } | |
15636 }, | |
15637 | |
15638 updateDependencies: function(directives, model) { | |
15639 this.closeDeps(); | |
15640 | |
15641 var deps = this.deps = {}; | |
15642 var template = this.templateElement_; | |
15643 | |
15644 if (directives.if) { | |
15645 deps.hasIf = true; | |
15646 deps.ifOneTime = directives.if.onlyOneTime; | |
15647 deps.ifValue = processBinding(IF, directives.if, template, model); | |
15648 | |
15649 // oneTime if & predicate is false. nothing else to do. | |
15650 if (deps.ifOneTime && !deps.ifValue) { | |
15651 this.updateIteratedValue(); | |
15652 return; | |
15653 } | |
15654 | |
15655 if (!deps.ifOneTime) | |
15656 deps.ifValue.open(this.updateIteratedValue, this); | |
15657 } | |
15658 | |
15659 if (directives.repeat) { | |
15660 deps.repeat = true; | |
15661 deps.oneTime = directives.repeat.onlyOneTime; | |
15662 deps.value = processBinding(REPEAT, directives.repeat, template, model); | |
15663 } else { | |
15664 deps.repeat = false; | |
15665 deps.oneTime = directives.bind.onlyOneTime; | |
15666 deps.value = processBinding(BIND, directives.bind, template, model); | |
15667 } | |
15668 | |
15669 if (!deps.oneTime) | |
15670 deps.value.open(this.updateIteratedValue, this); | |
15671 | |
15672 this.updateIteratedValue(); | |
15673 }, | |
15674 | |
15675 updateIteratedValue: function() { | |
15676 if (this.deps.hasIf) { | |
15677 var ifValue = this.deps.ifValue; | |
15678 if (!this.deps.ifOneTime) | |
15679 ifValue = ifValue.discardChanges(); | |
15680 if (!ifValue) { | |
15681 this.valueChanged(); | |
15682 return; | |
15683 } | |
15684 } | |
15685 | |
15686 var value = this.deps.value; | |
15687 if (!this.deps.oneTime) | |
15688 value = value.discardChanges(); | |
15689 if (!this.deps.repeat) | |
15690 value = [value]; | |
15691 var observe = this.deps.repeat && | |
15692 !this.deps.oneTime && | |
15693 Array.isArray(value); | |
15694 this.valueChanged(value, observe); | |
15695 }, | |
15696 | |
15697 valueChanged: function(value, observeValue) { | |
15698 if (!Array.isArray(value)) | |
15699 value = []; | |
15700 | |
15701 if (value === this.iteratedValue) | |
15702 return; | |
15703 | |
15704 this.unobserve(); | |
15705 this.presentValue = value; | |
15706 if (observeValue) { | |
15707 this.arrayObserver = new ArrayObserver(this.presentValue); | |
15708 this.arrayObserver.open(this.handleSplices, this); | |
15709 } | |
15710 | |
15711 this.handleSplices(ArrayObserver.calculateSplices(this.presentValue, | |
15712 this.iteratedValue)); | |
15713 }, | |
15714 | |
15715 getLastInstanceNode: function(index) { | |
15716 if (index == -1) | |
15717 return this.templateElement_; | |
15718 var instance = this.instances[index]; | |
15719 var terminator = instance.terminator_; | |
15720 if (!terminator) | |
15721 return this.getLastInstanceNode(index - 1); | |
15722 | |
15723 if (terminator.nodeType !== Node.ELEMENT_NODE || | |
15724 this.templateElement_ === terminator) { | |
15725 return terminator; | |
15726 } | |
15727 | |
15728 var subtemplateIterator = terminator.iterator_; | |
15729 if (!subtemplateIterator) | |
15730 return terminator; | |
15731 | |
15732 return subtemplateIterator.getLastTemplateNode(); | |
15733 }, | |
15734 | |
15735 getLastTemplateNode: function() { | |
15736 return this.getLastInstanceNode(this.instances.length - 1); | |
15737 }, | |
15738 | |
15739 insertInstanceAt: function(index, fragment) { | |
15740 var previousInstanceLast = this.getLastInstanceNode(index - 1); | |
15741 var parent = this.templateElement_.parentNode; | |
15742 this.instances.splice(index, 0, fragment); | |
15743 | |
15744 parent.insertBefore(fragment, previousInstanceLast.nextSibling); | |
15745 }, | |
15746 | |
15747 extractInstanceAt: function(index) { | |
15748 var previousInstanceLast = this.getLastInstanceNode(index - 1); | |
15749 var lastNode = this.getLastInstanceNode(index); | |
15750 var parent = this.templateElement_.parentNode; | |
15751 var instance = this.instances.splice(index, 1)[0]; | |
15752 | |
15753 while (lastNode !== previousInstanceLast) { | |
15754 var node = previousInstanceLast.nextSibling; | |
15755 if (node == lastNode) | |
15756 lastNode = previousInstanceLast; | |
15757 | |
15758 instance.appendChild(parent.removeChild(node)); | |
15759 } | |
15760 | |
15761 return instance; | |
15762 }, | |
15763 | |
15764 getDelegateFn: function(fn) { | |
15765 fn = fn && fn(this.templateElement_); | |
15766 return typeof fn === 'function' ? fn : null; | |
15767 }, | |
15768 | |
15769 handleSplices: function(splices) { | |
15770 if (this.closed || !splices.length) | |
15771 return; | |
15772 | |
15773 var template = this.templateElement_; | |
15774 | |
15775 if (!template.parentNode) { | |
15776 this.close(); | |
15777 return; | |
15778 } | |
15779 | |
15780 ArrayObserver.applySplices(this.iteratedValue, this.presentValue, | |
15781 splices); | |
15782 | |
15783 var delegate = template.delegate_; | |
15784 if (this.instanceModelFn_ === undefined) { | |
15785 this.instanceModelFn_ = | |
15786 this.getDelegateFn(delegate && delegate.prepareInstanceModel); | |
15787 } | |
15788 | |
15789 if (this.instancePositionChangedFn_ === undefined) { | |
15790 this.instancePositionChangedFn_ = | |
15791 this.getDelegateFn(delegate && | |
15792 delegate.prepareInstancePositionChanged); | |
15793 } | |
15794 | |
15795 // Instance Removals | |
15796 var instanceCache = new Map; | |
15797 var removeDelta = 0; | |
15798 for (var i = 0; i < splices.length; i++) { | |
15799 var splice = splices[i]; | |
15800 var removed = splice.removed; | |
15801 for (var j = 0; j < removed.length; j++) { | |
15802 var model = removed[j]; | |
15803 var instance = this.extractInstanceAt(splice.index + removeDelta); | |
15804 if (instance !== emptyInstance) { | |
15805 instanceCache.set(model, instance); | |
15806 } | |
15807 } | |
15808 | |
15809 removeDelta -= splice.addedCount; | |
15810 } | |
15811 | |
15812 // Instance Insertions | |
15813 for (var i = 0; i < splices.length; i++) { | |
15814 var splice = splices[i]; | |
15815 var addIndex = splice.index; | |
15816 for (; addIndex < splice.index + splice.addedCount; addIndex++) { | |
15817 var model = this.iteratedValue[addIndex]; | |
15818 var instance = instanceCache.get(model); | |
15819 if (instance) { | |
15820 instanceCache.delete(model); | |
15821 } else { | |
15822 if (this.instanceModelFn_) { | |
15823 model = this.instanceModelFn_(model); | |
15824 } | |
15825 | |
15826 if (model === undefined) { | |
15827 instance = emptyInstance; | |
15828 } else { | |
15829 instance = template.createInstance(model, undefined, delegate); | |
15830 } | |
15831 } | |
15832 | |
15833 this.insertInstanceAt(addIndex, instance); | |
15834 } | |
15835 } | |
15836 | |
15837 instanceCache.forEach(function(instance) { | |
15838 this.closeInstanceBindings(instance); | |
15839 }, this); | |
15840 | |
15841 if (this.instancePositionChangedFn_) | |
15842 this.reportInstancesMoved(splices); | |
15843 }, | |
15844 | |
15845 reportInstanceMoved: function(index) { | |
15846 var instance = this.instances[index]; | |
15847 if (instance === emptyInstance) | |
15848 return; | |
15849 | |
15850 this.instancePositionChangedFn_(instance.templateInstance_, index); | |
15851 }, | |
15852 | |
15853 reportInstancesMoved: function(splices) { | |
15854 var index = 0; | |
15855 var offset = 0; | |
15856 for (var i = 0; i < splices.length; i++) { | |
15857 var splice = splices[i]; | |
15858 if (offset != 0) { | |
15859 while (index < splice.index) { | |
15860 this.reportInstanceMoved(index); | |
15861 index++; | |
15862 } | |
15863 } else { | |
15864 index = splice.index; | |
15865 } | |
15866 | |
15867 while (index < splice.index + splice.addedCount) { | |
15868 this.reportInstanceMoved(index); | |
15869 index++; | |
15870 } | |
15871 | |
15872 offset += splice.addedCount - splice.removed.length; | |
15873 } | |
15874 | |
15875 if (offset == 0) | |
15876 return; | |
15877 | |
15878 var length = this.instances.length; | |
15879 while (index < length) { | |
15880 this.reportInstanceMoved(index); | |
15881 index++; | |
15882 } | |
15883 }, | |
15884 | |
15885 closeInstanceBindings: function(instance) { | |
15886 var bindings = instance.bindings_; | |
15887 for (var i = 0; i < bindings.length; i++) { | |
15888 bindings[i].close(); | |
15889 } | |
15890 }, | |
15891 | |
15892 unobserve: function() { | |
15893 if (!this.arrayObserver) | |
15894 return; | |
15895 | |
15896 this.arrayObserver.close(); | |
15897 this.arrayObserver = undefined; | |
15898 }, | |
15899 | |
15900 close: function() { | |
15901 if (this.closed) | |
15902 return; | |
15903 this.unobserve(); | |
15904 for (var i = 0; i < this.instances.length; i++) { | |
15905 this.closeInstanceBindings(this.instances[i]); | |
15906 } | |
15907 | |
15908 this.instances.length = 0; | |
15909 this.closeDeps(); | |
15910 this.templateElement_.iterator_ = undefined; | |
15911 this.closed = true; | |
15912 } | |
15913 }; | |
15914 | |
15915 // Polyfill-specific API. | |
15916 HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom; | |
15917 })(this); | |
15918 | |
15919 /* | |
15920 Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com> | |
15921 Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com> | |
15922 Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> | |
15923 Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be> | |
15924 Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl> | |
15925 Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com> | |
15926 Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com> | |
15927 Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com> | |
15928 Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com> | |
15929 | |
15930 Redistribution and use in source and binary forms, with or without | |
15931 modification, are permitted provided that the following conditions are met: | |
15932 | |
15933 * Redistributions of source code must retain the above copyright | |
15934 notice, this list of conditions and the following disclaimer. | |
15935 * Redistributions in binary form must reproduce the above copyright | |
15936 notice, this list of conditions and the following disclaimer in the | |
15937 documentation and/or other materials provided with the distribution. | |
15938 | |
15939 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
15940 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
15941 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
15942 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | |
15943 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
15944 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
15945 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
15946 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
15947 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
15948 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
15949 */ | |
15950 | |
15951 (function (global) { | |
15952 'use strict'; | |
15953 | |
15954 var Token, | |
15955 TokenName, | |
15956 Syntax, | |
15957 Messages, | |
15958 source, | |
15959 index, | |
15960 length, | |
15961 delegate, | |
15962 lookahead, | |
15963 state; | |
15964 | |
15965 Token = { | |
15966 BooleanLiteral: 1, | |
15967 EOF: 2, | |
15968 Identifier: 3, | |
15969 Keyword: 4, | |
15970 NullLiteral: 5, | |
15971 NumericLiteral: 6, | |
15972 Punctuator: 7, | |
15973 StringLiteral: 8 | |
15974 }; | |
15975 | |
15976 TokenName = {}; | |
15977 TokenName[Token.BooleanLiteral] = 'Boolean'; | |
15978 TokenName[Token.EOF] = '<end>'; | |
15979 TokenName[Token.Identifier] = 'Identifier'; | |
15980 TokenName[Token.Keyword] = 'Keyword'; | |
15981 TokenName[Token.NullLiteral] = 'Null'; | |
15982 TokenName[Token.NumericLiteral] = 'Numeric'; | |
15983 TokenName[Token.Punctuator] = 'Punctuator'; | |
15984 TokenName[Token.StringLiteral] = 'String'; | |
15985 | |
15986 Syntax = { | |
15987 ArrayExpression: 'ArrayExpression', | |
15988 BinaryExpression: 'BinaryExpression', | |
15989 CallExpression: 'CallExpression', | |
15990 ConditionalExpression: 'ConditionalExpression', | |
15991 EmptyStatement: 'EmptyStatement', | |
15992 ExpressionStatement: 'ExpressionStatement', | |
15993 Identifier: 'Identifier', | |
15994 Literal: 'Literal', | |
15995 LabeledStatement: 'LabeledStatement', | |
15996 LogicalExpression: 'LogicalExpression', | |
15997 MemberExpression: 'MemberExpression', | |
15998 ObjectExpression: 'ObjectExpression', | |
15999 Program: 'Program', | |
16000 Property: 'Property', | |
16001 ThisExpression: 'ThisExpression', | |
16002 UnaryExpression: 'UnaryExpression' | |
16003 }; | |
16004 | |
16005 // Error messages should be identical to V8. | |
16006 Messages = { | |
16007 UnexpectedToken: 'Unexpected token %0', | |
16008 UnknownLabel: 'Undefined label \'%0\'', | |
16009 Redeclaration: '%0 \'%1\' has already been declared' | |
16010 }; | |
16011 | |
16012 // Ensure the condition is true, otherwise throw an error. | |
16013 // This is only to have a better contract semantic, i.e. another safety net | |
16014 // to catch a logic error. The condition shall be fulfilled in normal case. | |
16015 // Do NOT use this to enforce a certain condition on any user input. | |
16016 | |
16017 function assert(condition, message) { | |
16018 if (!condition) { | |
16019 throw new Error('ASSERT: ' + message); | |
16020 } | |
16021 } | |
16022 | |
16023 function isDecimalDigit(ch) { | |
16024 return (ch >= 48 && ch <= 57); // 0..9 | |
16025 } | |
16026 | |
16027 | |
16028 // 7.2 White Space | |
16029 | |
16030 function isWhiteSpace(ch) { | |
16031 return (ch === 32) || // space | |
16032 (ch === 9) || // tab | |
16033 (ch === 0xB) || | |
16034 (ch === 0xC) || | |
16035 (ch === 0xA0) || | |
16036 (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u
2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCod
e(ch)) > 0); | |
16037 } | |
16038 | |
16039 // 7.3 Line Terminators | |
16040 | |
16041 function isLineTerminator(ch) { | |
16042 return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); | |
16043 } | |
16044 | |
16045 // 7.6 Identifier Names and Identifiers | |
16046 | |
16047 function isIdentifierStart(ch) { | |
16048 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) | |
16049 (ch >= 65 && ch <= 90) || // A..Z | |
16050 (ch >= 97 && ch <= 122); // a..z | |
16051 } | |
16052 | |
16053 function isIdentifierPart(ch) { | |
16054 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) | |
16055 (ch >= 65 && ch <= 90) || // A..Z | |
16056 (ch >= 97 && ch <= 122) || // a..z | |
16057 (ch >= 48 && ch <= 57); // 0..9 | |
16058 } | |
16059 | |
16060 // 7.6.1.1 Keywords | |
16061 | |
16062 function isKeyword(id) { | |
16063 return (id === 'this') | |
16064 } | |
16065 | |
16066 // 7.4 Comments | |
16067 | |
16068 function skipWhitespace() { | |
16069 while (index < length && isWhiteSpace(source.charCodeAt(index))) { | |
16070 ++index; | |
16071 } | |
16072 } | |
16073 | |
16074 function getIdentifier() { | |
16075 var start, ch; | |
16076 | |
16077 start = index++; | |
16078 while (index < length) { | |
16079 ch = source.charCodeAt(index); | |
16080 if (isIdentifierPart(ch)) { | |
16081 ++index; | |
16082 } else { | |
16083 break; | |
16084 } | |
16085 } | |
16086 | |
16087 return source.slice(start, index); | |
16088 } | |
16089 | |
16090 function scanIdentifier() { | |
16091 var start, id, type; | |
16092 | |
16093 start = index; | |
16094 | |
16095 id = getIdentifier(); | |
16096 | |
16097 // There is no keyword or literal with only one character. | |
16098 // Thus, it must be an identifier. | |
16099 if (id.length === 1) { | |
16100 type = Token.Identifier; | |
16101 } else if (isKeyword(id)) { | |
16102 type = Token.Keyword; | |
16103 } else if (id === 'null') { | |
16104 type = Token.NullLiteral; | |
16105 } else if (id === 'true' || id === 'false') { | |
16106 type = Token.BooleanLiteral; | |
16107 } else { | |
16108 type = Token.Identifier; | |
16109 } | |
16110 | |
16111 return { | |
16112 type: type, | |
16113 value: id, | |
16114 range: [start, index] | |
16115 }; | |
16116 } | |
16117 | |
16118 | |
16119 // 7.7 Punctuators | |
16120 | |
16121 function scanPunctuator() { | |
16122 var start = index, | |
16123 code = source.charCodeAt(index), | |
16124 code2, | |
16125 ch1 = source[index], | |
16126 ch2; | |
16127 | |
16128 switch (code) { | |
16129 | |
16130 // Check for most common single-character punctuators. | |
16131 case 46: // . dot | |
16132 case 40: // ( open bracket | |
16133 case 41: // ) close bracket | |
16134 case 59: // ; semicolon | |
16135 case 44: // , comma | |
16136 case 123: // { open curly brace | |
16137 case 125: // } close curly brace | |
16138 case 91: // [ | |
16139 case 93: // ] | |
16140 case 58: // : | |
16141 case 63: // ? | |
16142 ++index; | |
16143 return { | |
16144 type: Token.Punctuator, | |
16145 value: String.fromCharCode(code), | |
16146 range: [start, index] | |
16147 }; | |
16148 | |
16149 default: | |
16150 code2 = source.charCodeAt(index + 1); | |
16151 | |
16152 // '=' (char #61) marks an assignment or comparison operator. | |
16153 if (code2 === 61) { | |
16154 switch (code) { | |
16155 case 37: // % | |
16156 case 38: // & | |
16157 case 42: // *: | |
16158 case 43: // + | |
16159 case 45: // - | |
16160 case 47: // / | |
16161 case 60: // < | |
16162 case 62: // > | |
16163 case 124: // | | |
16164 index += 2; | |
16165 return { | |
16166 type: Token.Punctuator, | |
16167 value: String.fromCharCode(code) + String.fromCharCode(c
ode2), | |
16168 range: [start, index] | |
16169 }; | |
16170 | |
16171 case 33: // ! | |
16172 case 61: // = | |
16173 index += 2; | |
16174 | |
16175 // !== and === | |
16176 if (source.charCodeAt(index) === 61) { | |
16177 ++index; | |
16178 } | |
16179 return { | |
16180 type: Token.Punctuator, | |
16181 value: source.slice(start, index), | |
16182 range: [start, index] | |
16183 }; | |
16184 default: | |
16185 break; | |
16186 } | |
16187 } | |
16188 break; | |
16189 } | |
16190 | |
16191 // Peek more characters. | |
16192 | |
16193 ch2 = source[index + 1]; | |
16194 | |
16195 // Other 2-character punctuators: && || | |
16196 | |
16197 if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { | |
16198 index += 2; | |
16199 return { | |
16200 type: Token.Punctuator, | |
16201 value: ch1 + ch2, | |
16202 range: [start, index] | |
16203 }; | |
16204 } | |
16205 | |
16206 if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { | |
16207 ++index; | |
16208 return { | |
16209 type: Token.Punctuator, | |
16210 value: ch1, | |
16211 range: [start, index] | |
16212 }; | |
16213 } | |
16214 | |
16215 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
16216 } | |
16217 | |
16218 // 7.8.3 Numeric Literals | |
16219 function scanNumericLiteral() { | |
16220 var number, start, ch; | |
16221 | |
16222 ch = source[index]; | |
16223 assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), | |
16224 'Numeric literal must start with a decimal digit or a decimal point'
); | |
16225 | |
16226 start = index; | |
16227 number = ''; | |
16228 if (ch !== '.') { | |
16229 number = source[index++]; | |
16230 ch = source[index]; | |
16231 | |
16232 // Hex number starts with '0x'. | |
16233 // Octal number starts with '0'. | |
16234 if (number === '0') { | |
16235 // decimal number starts with '0' such as '09' is illegal. | |
16236 if (ch && isDecimalDigit(ch.charCodeAt(0))) { | |
16237 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
16238 } | |
16239 } | |
16240 | |
16241 while (isDecimalDigit(source.charCodeAt(index))) { | |
16242 number += source[index++]; | |
16243 } | |
16244 ch = source[index]; | |
16245 } | |
16246 | |
16247 if (ch === '.') { | |
16248 number += source[index++]; | |
16249 while (isDecimalDigit(source.charCodeAt(index))) { | |
16250 number += source[index++]; | |
16251 } | |
16252 ch = source[index]; | |
16253 } | |
16254 | |
16255 if (ch === 'e' || ch === 'E') { | |
16256 number += source[index++]; | |
16257 | |
16258 ch = source[index]; | |
16259 if (ch === '+' || ch === '-') { | |
16260 number += source[index++]; | |
16261 } | |
16262 if (isDecimalDigit(source.charCodeAt(index))) { | |
16263 while (isDecimalDigit(source.charCodeAt(index))) { | |
16264 number += source[index++]; | |
16265 } | |
16266 } else { | |
16267 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
16268 } | |
16269 } | |
16270 | |
16271 if (isIdentifierStart(source.charCodeAt(index))) { | |
16272 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
16273 } | |
16274 | |
16275 return { | |
16276 type: Token.NumericLiteral, | |
16277 value: parseFloat(number), | |
16278 range: [start, index] | |
16279 }; | |
16280 } | |
16281 | |
16282 // 7.8.4 String Literals | |
16283 | |
16284 function scanStringLiteral() { | |
16285 var str = '', quote, start, ch, octal = false; | |
16286 | |
16287 quote = source[index]; | |
16288 assert((quote === '\'' || quote === '"'), | |
16289 'String literal must starts with a quote'); | |
16290 | |
16291 start = index; | |
16292 ++index; | |
16293 | |
16294 while (index < length) { | |
16295 ch = source[index++]; | |
16296 | |
16297 if (ch === quote) { | |
16298 quote = ''; | |
16299 break; | |
16300 } else if (ch === '\\') { | |
16301 ch = source[index++]; | |
16302 if (!ch || !isLineTerminator(ch.charCodeAt(0))) { | |
16303 switch (ch) { | |
16304 case 'n': | |
16305 str += '\n'; | |
16306 break; | |
16307 case 'r': | |
16308 str += '\r'; | |
16309 break; | |
16310 case 't': | |
16311 str += '\t'; | |
16312 break; | |
16313 case 'b': | |
16314 str += '\b'; | |
16315 break; | |
16316 case 'f': | |
16317 str += '\f'; | |
16318 break; | |
16319 case 'v': | |
16320 str += '\x0B'; | |
16321 break; | |
16322 | |
16323 default: | |
16324 str += ch; | |
16325 break; | |
16326 } | |
16327 } else { | |
16328 if (ch === '\r' && source[index] === '\n') { | |
16329 ++index; | |
16330 } | |
16331 } | |
16332 } else if (isLineTerminator(ch.charCodeAt(0))) { | |
16333 break; | |
16334 } else { | |
16335 str += ch; | |
16336 } | |
16337 } | |
16338 | |
16339 if (quote !== '') { | |
16340 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
16341 } | |
16342 | |
16343 return { | |
16344 type: Token.StringLiteral, | |
16345 value: str, | |
16346 octal: octal, | |
16347 range: [start, index] | |
16348 }; | |
16349 } | |
16350 | |
16351 function isIdentifierName(token) { | |
16352 return token.type === Token.Identifier || | |
16353 token.type === Token.Keyword || | |
16354 token.type === Token.BooleanLiteral || | |
16355 token.type === Token.NullLiteral; | |
16356 } | |
16357 | |
16358 function advance() { | |
16359 var ch; | |
16360 | |
16361 skipWhitespace(); | |
16362 | |
16363 if (index >= length) { | |
16364 return { | |
16365 type: Token.EOF, | |
16366 range: [index, index] | |
16367 }; | |
16368 } | |
16369 | |
16370 ch = source.charCodeAt(index); | |
16371 | |
16372 // Very common: ( and ) and ; | |
16373 if (ch === 40 || ch === 41 || ch === 58) { | |
16374 return scanPunctuator(); | |
16375 } | |
16376 | |
16377 // String literal starts with single quote (#39) or double quote (#34). | |
16378 if (ch === 39 || ch === 34) { | |
16379 return scanStringLiteral(); | |
16380 } | |
16381 | |
16382 if (isIdentifierStart(ch)) { | |
16383 return scanIdentifier(); | |
16384 } | |
16385 | |
16386 // Dot (.) char #46 can also start a floating-point number, hence the ne
ed | |
16387 // to check the next character. | |
16388 if (ch === 46) { | |
16389 if (isDecimalDigit(source.charCodeAt(index + 1))) { | |
16390 return scanNumericLiteral(); | |
16391 } | |
16392 return scanPunctuator(); | |
16393 } | |
16394 | |
16395 if (isDecimalDigit(ch)) { | |
16396 return scanNumericLiteral(); | |
16397 } | |
16398 | |
16399 return scanPunctuator(); | |
16400 } | |
16401 | |
16402 function lex() { | |
16403 var token; | |
16404 | |
16405 token = lookahead; | |
16406 index = token.range[1]; | |
16407 | |
16408 lookahead = advance(); | |
16409 | |
16410 index = token.range[1]; | |
16411 | |
16412 return token; | |
16413 } | |
16414 | |
16415 function peek() { | |
16416 var pos; | |
16417 | |
16418 pos = index; | |
16419 lookahead = advance(); | |
16420 index = pos; | |
16421 } | |
16422 | |
16423 // Throw an exception | |
16424 | |
16425 function throwError(token, messageFormat) { | |
16426 var error, | |
16427 args = Array.prototype.slice.call(arguments, 2), | |
16428 msg = messageFormat.replace( | |
16429 /%(\d)/g, | |
16430 function (whole, index) { | |
16431 assert(index < args.length, 'Message reference must be in ra
nge'); | |
16432 return args[index]; | |
16433 } | |
16434 ); | |
16435 | |
16436 error = new Error(msg); | |
16437 error.index = index; | |
16438 error.description = msg; | |
16439 throw error; | |
16440 } | |
16441 | |
16442 // Throw an exception because of the token. | |
16443 | |
16444 function throwUnexpected(token) { | |
16445 throwError(token, Messages.UnexpectedToken, token.value); | |
16446 } | |
16447 | |
16448 // Expect the next token to match the specified punctuator. | |
16449 // If not, an exception will be thrown. | |
16450 | |
16451 function expect(value) { | |
16452 var token = lex(); | |
16453 if (token.type !== Token.Punctuator || token.value !== value) { | |
16454 throwUnexpected(token); | |
16455 } | |
16456 } | |
16457 | |
16458 // Return true if the next token matches the specified punctuator. | |
16459 | |
16460 function match(value) { | |
16461 return lookahead.type === Token.Punctuator && lookahead.value === value; | |
16462 } | |
16463 | |
16464 // Return true if the next token matches the specified keyword | |
16465 | |
16466 function matchKeyword(keyword) { | |
16467 return lookahead.type === Token.Keyword && lookahead.value === keyword; | |
16468 } | |
16469 | |
16470 function consumeSemicolon() { | |
16471 // Catch the very common case first: immediately a semicolon (char #59). | |
16472 if (source.charCodeAt(index) === 59) { | |
16473 lex(); | |
16474 return; | |
16475 } | |
16476 | |
16477 skipWhitespace(); | |
16478 | |
16479 if (match(';')) { | |
16480 lex(); | |
16481 return; | |
16482 } | |
16483 | |
16484 if (lookahead.type !== Token.EOF && !match('}')) { | |
16485 throwUnexpected(lookahead); | |
16486 } | |
16487 } | |
16488 | |
16489 // 11.1.4 Array Initialiser | |
16490 | |
16491 function parseArrayInitialiser() { | |
16492 var elements = []; | |
16493 | |
16494 expect('['); | |
16495 | |
16496 while (!match(']')) { | |
16497 if (match(',')) { | |
16498 lex(); | |
16499 elements.push(null); | |
16500 } else { | |
16501 elements.push(parseExpression()); | |
16502 | |
16503 if (!match(']')) { | |
16504 expect(','); | |
16505 } | |
16506 } | |
16507 } | |
16508 | |
16509 expect(']'); | |
16510 | |
16511 return delegate.createArrayExpression(elements); | |
16512 } | |
16513 | |
16514 // 11.1.5 Object Initialiser | |
16515 | |
16516 function parseObjectPropertyKey() { | |
16517 var token; | |
16518 | |
16519 skipWhitespace(); | |
16520 token = lex(); | |
16521 | |
16522 // Note: This function is called only from parseObjectProperty(), where | |
16523 // EOF and Punctuator tokens are already filtered out. | |
16524 if (token.type === Token.StringLiteral || token.type === Token.NumericLi
teral) { | |
16525 return delegate.createLiteral(token); | |
16526 } | |
16527 | |
16528 return delegate.createIdentifier(token.value); | |
16529 } | |
16530 | |
16531 function parseObjectProperty() { | |
16532 var token, key; | |
16533 | |
16534 token = lookahead; | |
16535 skipWhitespace(); | |
16536 | |
16537 if (token.type === Token.EOF || token.type === Token.Punctuator) { | |
16538 throwUnexpected(token); | |
16539 } | |
16540 | |
16541 key = parseObjectPropertyKey(); | |
16542 expect(':'); | |
16543 return delegate.createProperty('init', key, parseExpression()); | |
16544 } | |
16545 | |
16546 function parseObjectInitialiser() { | |
16547 var properties = []; | |
16548 | |
16549 expect('{'); | |
16550 | |
16551 while (!match('}')) { | |
16552 properties.push(parseObjectProperty()); | |
16553 | |
16554 if (!match('}')) { | |
16555 expect(','); | |
16556 } | |
16557 } | |
16558 | |
16559 expect('}'); | |
16560 | |
16561 return delegate.createObjectExpression(properties); | |
16562 } | |
16563 | |
16564 // 11.1.6 The Grouping Operator | |
16565 | |
16566 function parseGroupExpression() { | |
16567 var expr; | |
16568 | |
16569 expect('('); | |
16570 | |
16571 expr = parseExpression(); | |
16572 | |
16573 expect(')'); | |
16574 | |
16575 return expr; | |
16576 } | |
16577 | |
16578 | |
16579 // 11.1 Primary Expressions | |
16580 | |
16581 function parsePrimaryExpression() { | |
16582 var type, token, expr; | |
16583 | |
16584 if (match('(')) { | |
16585 return parseGroupExpression(); | |
16586 } | |
16587 | |
16588 type = lookahead.type; | |
16589 | |
16590 if (type === Token.Identifier) { | |
16591 expr = delegate.createIdentifier(lex().value); | |
16592 } else if (type === Token.StringLiteral || type === Token.NumericLiteral
) { | |
16593 expr = delegate.createLiteral(lex()); | |
16594 } else if (type === Token.Keyword) { | |
16595 if (matchKeyword('this')) { | |
16596 lex(); | |
16597 expr = delegate.createThisExpression(); | |
16598 } | |
16599 } else if (type === Token.BooleanLiteral) { | |
16600 token = lex(); | |
16601 token.value = (token.value === 'true'); | |
16602 expr = delegate.createLiteral(token); | |
16603 } else if (type === Token.NullLiteral) { | |
16604 token = lex(); | |
16605 token.value = null; | |
16606 expr = delegate.createLiteral(token); | |
16607 } else if (match('[')) { | |
16608 expr = parseArrayInitialiser(); | |
16609 } else if (match('{')) { | |
16610 expr = parseObjectInitialiser(); | |
16611 } | |
16612 | |
16613 if (expr) { | |
16614 return expr; | |
16615 } | |
16616 | |
16617 throwUnexpected(lex()); | |
16618 } | |
16619 | |
16620 // 11.2 Left-Hand-Side Expressions | |
16621 | |
16622 function parseArguments() { | |
16623 var args = []; | |
16624 | |
16625 expect('('); | |
16626 | |
16627 if (!match(')')) { | |
16628 while (index < length) { | |
16629 args.push(parseExpression()); | |
16630 if (match(')')) { | |
16631 break; | |
16632 } | |
16633 expect(','); | |
16634 } | |
16635 } | |
16636 | |
16637 expect(')'); | |
16638 | |
16639 return args; | |
16640 } | |
16641 | |
16642 function parseNonComputedProperty() { | |
16643 var token; | |
16644 | |
16645 token = lex(); | |
16646 | |
16647 if (!isIdentifierName(token)) { | |
16648 throwUnexpected(token); | |
16649 } | |
16650 | |
16651 return delegate.createIdentifier(token.value); | |
16652 } | |
16653 | |
16654 function parseNonComputedMember() { | |
16655 expect('.'); | |
16656 | |
16657 return parseNonComputedProperty(); | |
16658 } | |
16659 | |
16660 function parseComputedMember() { | |
16661 var expr; | |
16662 | |
16663 expect('['); | |
16664 | |
16665 expr = parseExpression(); | |
16666 | |
16667 expect(']'); | |
16668 | |
16669 return expr; | |
16670 } | |
16671 | |
16672 function parseLeftHandSideExpression() { | |
16673 var expr, property; | |
16674 | |
16675 expr = parsePrimaryExpression(); | |
16676 | |
16677 while (match('.') || match('[')) { | |
16678 if (match('[')) { | |
16679 property = parseComputedMember(); | |
16680 expr = delegate.createMemberExpression('[', expr, property); | |
16681 } else { | |
16682 property = parseNonComputedMember(); | |
16683 expr = delegate.createMemberExpression('.', expr, property); | |
16684 } | |
16685 } | |
16686 | |
16687 return expr; | |
16688 } | |
16689 | |
16690 // 11.3 Postfix Expressions | |
16691 | |
16692 var parsePostfixExpression = parseLeftHandSideExpression; | |
16693 | |
16694 // 11.4 Unary Operators | |
16695 | |
16696 function parseUnaryExpression() { | |
16697 var token, expr; | |
16698 | |
16699 if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyw
ord) { | |
16700 expr = parsePostfixExpression(); | |
16701 } else if (match('+') || match('-') || match('!')) { | |
16702 token = lex(); | |
16703 expr = parseUnaryExpression(); | |
16704 expr = delegate.createUnaryExpression(token.value, expr); | |
16705 } else if (matchKeyword('delete') || matchKeyword('void') || matchKeywor
d('typeof')) { | |
16706 throwError({}, Messages.UnexpectedToken); | |
16707 } else { | |
16708 expr = parsePostfixExpression(); | |
16709 } | |
16710 | |
16711 return expr; | |
16712 } | |
16713 | |
16714 function binaryPrecedence(token) { | |
16715 var prec = 0; | |
16716 | |
16717 if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { | |
16718 return 0; | |
16719 } | |
16720 | |
16721 switch (token.value) { | |
16722 case '||': | |
16723 prec = 1; | |
16724 break; | |
16725 | |
16726 case '&&': | |
16727 prec = 2; | |
16728 break; | |
16729 | |
16730 case '==': | |
16731 case '!=': | |
16732 case '===': | |
16733 case '!==': | |
16734 prec = 6; | |
16735 break; | |
16736 | |
16737 case '<': | |
16738 case '>': | |
16739 case '<=': | |
16740 case '>=': | |
16741 case 'instanceof': | |
16742 prec = 7; | |
16743 break; | |
16744 | |
16745 case 'in': | |
16746 prec = 7; | |
16747 break; | |
16748 | |
16749 case '+': | |
16750 case '-': | |
16751 prec = 9; | |
16752 break; | |
16753 | |
16754 case '*': | |
16755 case '/': | |
16756 case '%': | |
16757 prec = 11; | |
16758 break; | |
16759 | |
16760 default: | |
16761 break; | |
16762 } | |
16763 | |
16764 return prec; | |
16765 } | |
16766 | |
16767 // 11.5 Multiplicative Operators | |
16768 // 11.6 Additive Operators | |
16769 // 11.7 Bitwise Shift Operators | |
16770 // 11.8 Relational Operators | |
16771 // 11.9 Equality Operators | |
16772 // 11.10 Binary Bitwise Operators | |
16773 // 11.11 Binary Logical Operators | |
16774 | |
16775 function parseBinaryExpression() { | |
16776 var expr, token, prec, stack, right, operator, left, i; | |
16777 | |
16778 left = parseUnaryExpression(); | |
16779 | |
16780 token = lookahead; | |
16781 prec = binaryPrecedence(token); | |
16782 if (prec === 0) { | |
16783 return left; | |
16784 } | |
16785 token.prec = prec; | |
16786 lex(); | |
16787 | |
16788 right = parseUnaryExpression(); | |
16789 | |
16790 stack = [left, token, right]; | |
16791 | |
16792 while ((prec = binaryPrecedence(lookahead)) > 0) { | |
16793 | |
16794 // Reduce: make a binary expression from the three topmost entries. | |
16795 while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec))
{ | |
16796 right = stack.pop(); | |
16797 operator = stack.pop().value; | |
16798 left = stack.pop(); | |
16799 expr = delegate.createBinaryExpression(operator, left, right); | |
16800 stack.push(expr); | |
16801 } | |
16802 | |
16803 // Shift. | |
16804 token = lex(); | |
16805 token.prec = prec; | |
16806 stack.push(token); | |
16807 expr = parseUnaryExpression(); | |
16808 stack.push(expr); | |
16809 } | |
16810 | |
16811 // Final reduce to clean-up the stack. | |
16812 i = stack.length - 1; | |
16813 expr = stack[i]; | |
16814 while (i > 1) { | |
16815 expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i -
2], expr); | |
16816 i -= 2; | |
16817 } | |
16818 | |
16819 return expr; | |
16820 } | |
16821 | |
16822 | |
16823 // 11.12 Conditional Operator | |
16824 | |
16825 function parseConditionalExpression() { | |
16826 var expr, consequent, alternate; | |
16827 | |
16828 expr = parseBinaryExpression(); | |
16829 | |
16830 if (match('?')) { | |
16831 lex(); | |
16832 consequent = parseConditionalExpression(); | |
16833 expect(':'); | |
16834 alternate = parseConditionalExpression(); | |
16835 | |
16836 expr = delegate.createConditionalExpression(expr, consequent, altern
ate); | |
16837 } | |
16838 | |
16839 return expr; | |
16840 } | |
16841 | |
16842 // Simplification since we do not support AssignmentExpression. | |
16843 var parseExpression = parseConditionalExpression; | |
16844 | |
16845 // Polymer Syntax extensions | |
16846 | |
16847 // Filter :: | |
16848 // Identifier | |
16849 // Identifier "(" ")" | |
16850 // Identifier "(" FilterArguments ")" | |
16851 | |
16852 function parseFilter() { | |
16853 var identifier, args; | |
16854 | |
16855 identifier = lex(); | |
16856 | |
16857 if (identifier.type !== Token.Identifier) { | |
16858 throwUnexpected(identifier); | |
16859 } | |
16860 | |
16861 args = match('(') ? parseArguments() : []; | |
16862 | |
16863 return delegate.createFilter(identifier.value, args); | |
16864 } | |
16865 | |
16866 // Filters :: | |
16867 // "|" Filter | |
16868 // Filters "|" Filter | |
16869 | |
16870 function parseFilters() { | |
16871 while (match('|')) { | |
16872 lex(); | |
16873 parseFilter(); | |
16874 } | |
16875 } | |
16876 | |
16877 // TopLevel :: | |
16878 // LabelledExpressions | |
16879 // AsExpression | |
16880 // InExpression | |
16881 // FilterExpression | |
16882 | |
16883 // AsExpression :: | |
16884 // FilterExpression as Identifier | |
16885 | |
16886 // InExpression :: | |
16887 // Identifier, Identifier in FilterExpression | |
16888 // Identifier in FilterExpression | |
16889 | |
16890 // FilterExpression :: | |
16891 // Expression | |
16892 // Expression Filters | |
16893 | |
16894 function parseTopLevel() { | |
16895 skipWhitespace(); | |
16896 peek(); | |
16897 | |
16898 var expr = parseExpression(); | |
16899 if (expr) { | |
16900 if (lookahead.value === ',' || lookahead.value == 'in' && | |
16901 expr.type === Syntax.Identifier) { | |
16902 parseInExpression(expr); | |
16903 } else { | |
16904 parseFilters(); | |
16905 if (lookahead.value === 'as') { | |
16906 parseAsExpression(expr); | |
16907 } else { | |
16908 delegate.createTopLevel(expr); | |
16909 } | |
16910 } | |
16911 } | |
16912 | |
16913 if (lookahead.type !== Token.EOF) { | |
16914 throwUnexpected(lookahead); | |
16915 } | |
16916 } | |
16917 | |
16918 function parseAsExpression(expr) { | |
16919 lex(); // as | |
16920 var identifier = lex().value; | |
16921 delegate.createAsExpression(expr, identifier); | |
16922 } | |
16923 | |
16924 function parseInExpression(identifier) { | |
16925 var indexName; | |
16926 if (lookahead.value === ',') { | |
16927 lex(); | |
16928 if (lookahead.type !== Token.Identifier) | |
16929 throwUnexpected(lookahead); | |
16930 indexName = lex().value; | |
16931 } | |
16932 | |
16933 lex(); // in | |
16934 var expr = parseExpression(); | |
16935 parseFilters(); | |
16936 delegate.createInExpression(identifier.name, indexName, expr); | |
16937 } | |
16938 | |
16939 function parse(code, inDelegate) { | |
16940 delegate = inDelegate; | |
16941 source = code; | |
16942 index = 0; | |
16943 length = source.length; | |
16944 lookahead = null; | |
16945 state = { | |
16946 labelSet: {} | |
16947 }; | |
16948 | |
16949 return parseTopLevel(); | |
16950 } | |
16951 | |
16952 global.esprima = { | |
16953 parse: parse | |
16954 }; | |
16955 })(this); | |
16956 | |
16957 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
16958 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
16959 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
16960 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
16961 // Code distributed by Google as part of the polymer project is also | |
16962 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
16963 | |
16964 (function (global) { | |
16965 'use strict'; | |
16966 | |
16967 // JScript does not have __proto__. We wrap all object literals with | |
16968 // createObject which uses Object.create, Object.defineProperty and | |
16969 // Object.getOwnPropertyDescriptor to create a new object that does the exact | |
16970 // same thing. The main downside to this solution is that we have to extract | |
16971 // all those property descriptors for IE. | |
16972 var createObject = ('__proto__' in {}) ? | |
16973 function(obj) { return obj; } : | |
16974 function(obj) { | |
16975 var proto = obj.__proto__; | |
16976 if (!proto) | |
16977 return obj; | |
16978 var newObject = Object.create(proto); | |
16979 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
16980 Object.defineProperty(newObject, name, | |
16981 Object.getOwnPropertyDescriptor(obj, name)); | |
16982 }); | |
16983 return newObject; | |
16984 }; | |
16985 | |
16986 function prepareBinding(expressionText, name, node, filterRegistry) { | |
16987 var expression; | |
16988 try { | |
16989 expression = getExpression(expressionText); | |
16990 if (expression.scopeIdent && | |
16991 (node.nodeType !== Node.ELEMENT_NODE || | |
16992 node.tagName !== 'TEMPLATE' || | |
16993 (name !== 'bind' && name !== 'repeat'))) { | |
16994 throw Error('as and in can only be used within <template bind/repeat>'); | |
16995 } | |
16996 } catch (ex) { | |
16997 console.error('Invalid expression syntax: ' + expressionText, ex); | |
16998 return; | |
16999 } | |
17000 | |
17001 return function(model, node, oneTime) { | |
17002 var binding = expression.getBinding(model, filterRegistry, oneTime); | |
17003 if (expression.scopeIdent && binding) { | |
17004 node.polymerExpressionScopeIdent_ = expression.scopeIdent; | |
17005 if (expression.indexIdent) | |
17006 node.polymerExpressionIndexIdent_ = expression.indexIdent; | |
17007 } | |
17008 | |
17009 return binding; | |
17010 } | |
17011 } | |
17012 | |
17013 // TODO(rafaelw): Implement simple LRU. | |
17014 var expressionParseCache = Object.create(null); | |
17015 | |
17016 function getExpression(expressionText) { | |
17017 var expression = expressionParseCache[expressionText]; | |
17018 if (!expression) { | |
17019 var delegate = new ASTDelegate(); | |
17020 esprima.parse(expressionText, delegate); | |
17021 expression = new Expression(delegate); | |
17022 expressionParseCache[expressionText] = expression; | |
17023 } | |
17024 return expression; | |
17025 } | |
17026 | |
17027 function Literal(value) { | |
17028 this.value = value; | |
17029 this.valueFn_ = undefined; | |
17030 } | |
17031 | |
17032 Literal.prototype = { | |
17033 valueFn: function() { | |
17034 if (!this.valueFn_) { | |
17035 var value = this.value; | |
17036 this.valueFn_ = function() { | |
17037 return value; | |
17038 } | |
17039 } | |
17040 | |
17041 return this.valueFn_; | |
17042 } | |
17043 } | |
17044 | |
17045 function IdentPath(name) { | |
17046 this.name = name; | |
17047 this.path = Path.get(name); | |
17048 } | |
17049 | |
17050 IdentPath.prototype = { | |
17051 valueFn: function() { | |
17052 if (!this.valueFn_) { | |
17053 var name = this.name; | |
17054 var path = this.path; | |
17055 this.valueFn_ = function(model, observer) { | |
17056 if (observer) | |
17057 observer.addPath(model, path); | |
17058 | |
17059 return path.getValueFrom(model); | |
17060 } | |
17061 } | |
17062 | |
17063 return this.valueFn_; | |
17064 }, | |
17065 | |
17066 setValue: function(model, newValue) { | |
17067 if (this.path.length == 1); | |
17068 model = findScope(model, this.path[0]); | |
17069 | |
17070 return this.path.setValueFrom(model, newValue); | |
17071 } | |
17072 }; | |
17073 | |
17074 function MemberExpression(object, property, accessor) { | |
17075 // convert literal computed property access where literal value is a value | |
17076 // path to ident dot-access. | |
17077 if (accessor == '[' && | |
17078 property instanceof Literal && | |
17079 Path.get(property.value).valid) { | |
17080 accessor = '.'; | |
17081 property = new IdentPath(property.value); | |
17082 } | |
17083 | |
17084 this.dynamicDeps = typeof object == 'function' || object.dynamic; | |
17085 | |
17086 this.dynamic = typeof property == 'function' || | |
17087 property.dynamic || | |
17088 accessor == '['; | |
17089 | |
17090 this.simplePath = | |
17091 !this.dynamic && | |
17092 !this.dynamicDeps && | |
17093 property instanceof IdentPath && | |
17094 (object instanceof MemberExpression || object instanceof IdentPath); | |
17095 | |
17096 this.object = this.simplePath ? object : getFn(object); | |
17097 this.property = accessor == '.' ? property : getFn(property); | |
17098 } | |
17099 | |
17100 MemberExpression.prototype = { | |
17101 get fullPath() { | |
17102 if (!this.fullPath_) { | |
17103 var last = this.object instanceof IdentPath ? | |
17104 this.object.name : this.object.fullPath; | |
17105 this.fullPath_ = Path.get(last + '.' + this.property.name); | |
17106 } | |
17107 | |
17108 return this.fullPath_; | |
17109 }, | |
17110 | |
17111 valueFn: function() { | |
17112 if (!this.valueFn_) { | |
17113 var object = this.object; | |
17114 | |
17115 if (this.simplePath) { | |
17116 var path = this.fullPath; | |
17117 | |
17118 this.valueFn_ = function(model, observer) { | |
17119 if (observer) | |
17120 observer.addPath(model, path); | |
17121 | |
17122 return path.getValueFrom(model); | |
17123 }; | |
17124 } else if (this.property instanceof IdentPath) { | |
17125 var path = Path.get(this.property.name); | |
17126 | |
17127 this.valueFn_ = function(model, observer) { | |
17128 var context = object(model, observer); | |
17129 | |
17130 if (observer) | |
17131 observer.addPath(context, path); | |
17132 | |
17133 return path.getValueFrom(context); | |
17134 } | |
17135 } else { | |
17136 // Computed property. | |
17137 var property = this.property; | |
17138 | |
17139 this.valueFn_ = function(model, observer) { | |
17140 var context = object(model, observer); | |
17141 var propName = property(model, observer); | |
17142 if (observer) | |
17143 observer.addPath(context, propName); | |
17144 | |
17145 return context ? context[propName] : undefined; | |
17146 }; | |
17147 } | |
17148 } | |
17149 return this.valueFn_; | |
17150 }, | |
17151 | |
17152 setValue: function(model, newValue) { | |
17153 if (this.simplePath) { | |
17154 this.fullPath.setValueFrom(model, newValue); | |
17155 return newValue; | |
17156 } | |
17157 | |
17158 var object = this.object(model); | |
17159 var propName = this.property instanceof IdentPath ? this.property.name : | |
17160 this.property(model); | |
17161 return object[propName] = newValue; | |
17162 } | |
17163 }; | |
17164 | |
17165 function Filter(name, args) { | |
17166 this.name = name; | |
17167 this.args = []; | |
17168 for (var i = 0; i < args.length; i++) { | |
17169 this.args[i] = getFn(args[i]); | |
17170 } | |
17171 } | |
17172 | |
17173 Filter.prototype = { | |
17174 transform: function(value, toModelDirection, filterRegistry, model, | |
17175 observer) { | |
17176 var fn = filterRegistry[this.name]; | |
17177 var context = model; | |
17178 if (fn) { | |
17179 context = undefined; | |
17180 } else { | |
17181 fn = context[this.name]; | |
17182 if (!fn) { | |
17183 console.error('Cannot find filter: ' + this.name); | |
17184 return; | |
17185 } | |
17186 } | |
17187 | |
17188 // If toModelDirection is falsey, then the "normal" (dom-bound) direction | |
17189 // is used. Otherwise, it looks for a 'toModel' property function on the | |
17190 // object. | |
17191 if (toModelDirection) { | |
17192 fn = fn.toModel; | |
17193 } else if (typeof fn.toDOM == 'function') { | |
17194 fn = fn.toDOM; | |
17195 } | |
17196 | |
17197 if (typeof fn != 'function') { | |
17198 console.error('No ' + (toModelDirection ? 'toModel' : 'toDOM') + | |
17199 ' found on' + this.name); | |
17200 return; | |
17201 } | |
17202 | |
17203 var args = [value]; | |
17204 for (var i = 0; i < this.args.length; i++) { | |
17205 args[i + 1] = getFn(this.args[i])(model, observer); | |
17206 } | |
17207 | |
17208 return fn.apply(context, args); | |
17209 } | |
17210 }; | |
17211 | |
17212 function notImplemented() { throw Error('Not Implemented'); } | |
17213 | |
17214 var unaryOperators = { | |
17215 '+': function(v) { return +v; }, | |
17216 '-': function(v) { return -v; }, | |
17217 '!': function(v) { return !v; } | |
17218 }; | |
17219 | |
17220 var binaryOperators = { | |
17221 '+': function(l, r) { return l+r; }, | |
17222 '-': function(l, r) { return l-r; }, | |
17223 '*': function(l, r) { return l*r; }, | |
17224 '/': function(l, r) { return l/r; }, | |
17225 '%': function(l, r) { return l%r; }, | |
17226 '<': function(l, r) { return l<r; }, | |
17227 '>': function(l, r) { return l>r; }, | |
17228 '<=': function(l, r) { return l<=r; }, | |
17229 '>=': function(l, r) { return l>=r; }, | |
17230 '==': function(l, r) { return l==r; }, | |
17231 '!=': function(l, r) { return l!=r; }, | |
17232 '===': function(l, r) { return l===r; }, | |
17233 '!==': function(l, r) { return l!==r; }, | |
17234 '&&': function(l, r) { return l&&r; }, | |
17235 '||': function(l, r) { return l||r; }, | |
17236 }; | |
17237 | |
17238 function getFn(arg) { | |
17239 return typeof arg == 'function' ? arg : arg.valueFn(); | |
17240 } | |
17241 | |
17242 function ASTDelegate() { | |
17243 this.expression = null; | |
17244 this.filters = []; | |
17245 this.deps = {}; | |
17246 this.currentPath = undefined; | |
17247 this.scopeIdent = undefined; | |
17248 this.indexIdent = undefined; | |
17249 this.dynamicDeps = false; | |
17250 } | |
17251 | |
17252 ASTDelegate.prototype = { | |
17253 createUnaryExpression: function(op, argument) { | |
17254 if (!unaryOperators[op]) | |
17255 throw Error('Disallowed operator: ' + op); | |
17256 | |
17257 argument = getFn(argument); | |
17258 | |
17259 return function(model, observer) { | |
17260 return unaryOperators[op](argument(model, observer)); | |
17261 }; | |
17262 }, | |
17263 | |
17264 createBinaryExpression: function(op, left, right) { | |
17265 if (!binaryOperators[op]) | |
17266 throw Error('Disallowed operator: ' + op); | |
17267 | |
17268 left = getFn(left); | |
17269 right = getFn(right); | |
17270 | |
17271 return function(model, observer) { | |
17272 return binaryOperators[op](left(model, observer), | |
17273 right(model, observer)); | |
17274 }; | |
17275 }, | |
17276 | |
17277 createConditionalExpression: function(test, consequent, alternate) { | |
17278 test = getFn(test); | |
17279 consequent = getFn(consequent); | |
17280 alternate = getFn(alternate); | |
17281 | |
17282 return function(model, observer) { | |
17283 return test(model, observer) ? | |
17284 consequent(model, observer) : alternate(model, observer); | |
17285 } | |
17286 }, | |
17287 | |
17288 createIdentifier: function(name) { | |
17289 var ident = new IdentPath(name); | |
17290 ident.type = 'Identifier'; | |
17291 return ident; | |
17292 }, | |
17293 | |
17294 createMemberExpression: function(accessor, object, property) { | |
17295 var ex = new MemberExpression(object, property, accessor); | |
17296 if (ex.dynamicDeps) | |
17297 this.dynamicDeps = true; | |
17298 return ex; | |
17299 }, | |
17300 | |
17301 createLiteral: function(token) { | |
17302 return new Literal(token.value); | |
17303 }, | |
17304 | |
17305 createArrayExpression: function(elements) { | |
17306 for (var i = 0; i < elements.length; i++) | |
17307 elements[i] = getFn(elements[i]); | |
17308 | |
17309 return function(model, observer) { | |
17310 var arr = [] | |
17311 for (var i = 0; i < elements.length; i++) | |
17312 arr.push(elements[i](model, observer)); | |
17313 return arr; | |
17314 } | |
17315 }, | |
17316 | |
17317 createProperty: function(kind, key, value) { | |
17318 return { | |
17319 key: key instanceof IdentPath ? key.name : key.value, | |
17320 value: value | |
17321 }; | |
17322 }, | |
17323 | |
17324 createObjectExpression: function(properties) { | |
17325 for (var i = 0; i < properties.length; i++) | |
17326 properties[i].value = getFn(properties[i].value); | |
17327 | |
17328 return function(model, observer) { | |
17329 var obj = {}; | |
17330 for (var i = 0; i < properties.length; i++) | |
17331 obj[properties[i].key] = properties[i].value(model, observer); | |
17332 return obj; | |
17333 } | |
17334 }, | |
17335 | |
17336 createFilter: function(name, args) { | |
17337 this.filters.push(new Filter(name, args)); | |
17338 }, | |
17339 | |
17340 createAsExpression: function(expression, scopeIdent) { | |
17341 this.expression = expression; | |
17342 this.scopeIdent = scopeIdent; | |
17343 }, | |
17344 | |
17345 createInExpression: function(scopeIdent, indexIdent, expression) { | |
17346 this.expression = expression; | |
17347 this.scopeIdent = scopeIdent; | |
17348 this.indexIdent = indexIdent; | |
17349 }, | |
17350 | |
17351 createTopLevel: function(expression) { | |
17352 this.expression = expression; | |
17353 }, | |
17354 | |
17355 createThisExpression: notImplemented | |
17356 } | |
17357 | |
17358 function ConstantObservable(value) { | |
17359 this.value_ = value; | |
17360 } | |
17361 | |
17362 ConstantObservable.prototype = { | |
17363 open: function() { return this.value_; }, | |
17364 discardChanges: function() { return this.value_; }, | |
17365 deliver: function() {}, | |
17366 close: function() {}, | |
17367 } | |
17368 | |
17369 function Expression(delegate) { | |
17370 this.scopeIdent = delegate.scopeIdent; | |
17371 this.indexIdent = delegate.indexIdent; | |
17372 | |
17373 if (!delegate.expression) | |
17374 throw Error('No expression found.'); | |
17375 | |
17376 this.expression = delegate.expression; | |
17377 getFn(this.expression); // forces enumeration of path dependencies | |
17378 | |
17379 this.filters = delegate.filters; | |
17380 this.dynamicDeps = delegate.dynamicDeps; | |
17381 } | |
17382 | |
17383 Expression.prototype = { | |
17384 getBinding: function(model, filterRegistry, oneTime) { | |
17385 if (oneTime) | |
17386 return this.getValue(model, undefined, filterRegistry); | |
17387 | |
17388 var observer = new CompoundObserver(); | |
17389 // captures deps. | |
17390 var firstValue = this.getValue(model, observer, filterRegistry); | |
17391 var firstTime = true; | |
17392 var self = this; | |
17393 | |
17394 function valueFn() { | |
17395 // deps cannot have changed on first value retrieval. | |
17396 if (firstTime) { | |
17397 firstTime = false; | |
17398 return firstValue; | |
17399 } | |
17400 | |
17401 if (self.dynamicDeps) | |
17402 observer.startReset(); | |
17403 | |
17404 var value = self.getValue(model, | |
17405 self.dynamicDeps ? observer : undefined, | |
17406 filterRegistry); | |
17407 if (self.dynamicDeps) | |
17408 observer.finishReset(); | |
17409 | |
17410 return value; | |
17411 } | |
17412 | |
17413 function setValueFn(newValue) { | |
17414 self.setValue(model, newValue, filterRegistry); | |
17415 return newValue; | |
17416 } | |
17417 | |
17418 return new ObserverTransform(observer, valueFn, setValueFn, true); | |
17419 }, | |
17420 | |
17421 getValue: function(model, observer, filterRegistry) { | |
17422 var value = getFn(this.expression)(model, observer); | |
17423 for (var i = 0; i < this.filters.length; i++) { | |
17424 value = this.filters[i].transform(value, false, filterRegistry, model, | |
17425 observer); | |
17426 } | |
17427 | |
17428 return value; | |
17429 }, | |
17430 | |
17431 setValue: function(model, newValue, filterRegistry) { | |
17432 var count = this.filters ? this.filters.length : 0; | |
17433 while (count-- > 0) { | |
17434 newValue = this.filters[count].transform(newValue, true, filterRegistry, | |
17435 model); | |
17436 } | |
17437 | |
17438 if (this.expression.setValue) | |
17439 return this.expression.setValue(model, newValue); | |
17440 } | |
17441 } | |
17442 | |
17443 /** | |
17444 * Converts a style property name to a css property name. For example: | |
17445 * "WebkitUserSelect" to "-webkit-user-select" | |
17446 */ | |
17447 function convertStylePropertyName(name) { | |
17448 return String(name).replace(/[A-Z]/g, function(c) { | |
17449 return '-' + c.toLowerCase(); | |
17450 }); | |
17451 } | |
17452 | |
17453 function isEventHandler(name) { | |
17454 return name[0] === 'o' && | |
17455 name[1] === 'n' && | |
17456 name[2] === '-'; | |
17457 } | |
17458 | |
17459 var mixedCaseEventTypes = {}; | |
17460 [ | |
17461 'webkitAnimationStart', | |
17462 'webkitAnimationEnd', | |
17463 'webkitTransitionEnd', | |
17464 'DOMFocusOut', | |
17465 'DOMFocusIn', | |
17466 'DOMMouseScroll' | |
17467 ].forEach(function(e) { | |
17468 mixedCaseEventTypes[e.toLowerCase()] = e; | |
17469 }); | |
17470 | |
17471 var parentScopeName = '@' + Math.random().toString(36).slice(2); | |
17472 | |
17473 // Single ident paths must bind directly to the appropriate scope object. | |
17474 // I.e. Pushed values in two-bindings need to be assigned to the actual model | |
17475 // object. | |
17476 function findScope(model, prop) { | |
17477 while (model[parentScopeName] && | |
17478 !Object.prototype.hasOwnProperty.call(model, prop)) { | |
17479 model = model[parentScopeName]; | |
17480 } | |
17481 | |
17482 return model; | |
17483 } | |
17484 | |
17485 function resolveEventReceiver(model, path, node) { | |
17486 if (path.length == 0) | |
17487 return undefined; | |
17488 | |
17489 if (path.length == 1) | |
17490 return findScope(model, path[0]); | |
17491 | |
17492 for (var i = 0; model != null && i < path.length - 1; i++) { | |
17493 model = model[path[i]]; | |
17494 } | |
17495 | |
17496 return model; | |
17497 } | |
17498 | |
17499 function prepareEventBinding(path, name, polymerExpressions) { | |
17500 var eventType = name.substring(3); | |
17501 eventType = mixedCaseEventTypes[eventType] || eventType; | |
17502 | |
17503 return function(model, node, oneTime) { | |
17504 var fn, receiver, handler; | |
17505 if (typeof polymerExpressions.resolveEventHandler == 'function') { | |
17506 handler = function(e) { | |
17507 fn = fn || polymerExpressions.resolveEventHandler(model, path, node); | |
17508 fn(e, e.detail, e.currentTarget); | |
17509 | |
17510 if (Platform && typeof Platform.flush == 'function') | |
17511 Platform.flush(); | |
17512 }; | |
17513 } else { | |
17514 handler = function(e) { | |
17515 fn = fn || path.getValueFrom(model); | |
17516 receiver = receiver || resolveEventReceiver(model, path, node); | |
17517 | |
17518 fn.apply(receiver, [e, e.detail, e.currentTarget]); | |
17519 | |
17520 if (Platform && typeof Platform.flush == 'function') | |
17521 Platform.flush(); | |
17522 }; | |
17523 } | |
17524 | |
17525 node.addEventListener(eventType, handler); | |
17526 | |
17527 if (oneTime) | |
17528 return; | |
17529 | |
17530 function bindingValue() { | |
17531 return '{{ ' + path + ' }}'; | |
17532 } | |
17533 | |
17534 return { | |
17535 open: bindingValue, | |
17536 discardChanges: bindingValue, | |
17537 close: function() { | |
17538 node.removeEventListener(eventType, handler); | |
17539 } | |
17540 }; | |
17541 } | |
17542 } | |
17543 | |
17544 function isLiteralExpression(pathString) { | |
17545 switch (pathString) { | |
17546 case '': | |
17547 return false; | |
17548 | |
17549 case 'false': | |
17550 case 'null': | |
17551 case 'true': | |
17552 return true; | |
17553 } | |
17554 | |
17555 if (!isNaN(Number(pathString))) | |
17556 return true; | |
17557 | |
17558 return false; | |
17559 }; | |
17560 | |
17561 function PolymerExpressions() {} | |
17562 | |
17563 PolymerExpressions.prototype = { | |
17564 // "built-in" filters | |
17565 styleObject: function(value) { | |
17566 var parts = []; | |
17567 for (var key in value) { | |
17568 parts.push(convertStylePropertyName(key) + ': ' + value[key]); | |
17569 } | |
17570 return parts.join('; '); | |
17571 }, | |
17572 | |
17573 tokenList: function(value) { | |
17574 var tokens = []; | |
17575 for (var key in value) { | |
17576 if (value[key]) | |
17577 tokens.push(key); | |
17578 } | |
17579 return tokens.join(' '); | |
17580 }, | |
17581 | |
17582 // binding delegate API | |
17583 prepareInstancePositionChanged: function(template) { | |
17584 var indexIdent = template.polymerExpressionIndexIdent_; | |
17585 if (!indexIdent) | |
17586 return; | |
17587 | |
17588 return function(templateInstance, index) { | |
17589 templateInstance.model[indexIdent] = index; | |
17590 }; | |
17591 }, | |
17592 | |
17593 prepareBinding: function(pathString, name, node) { | |
17594 var path = Path.get(pathString); | |
17595 if (isEventHandler(name)) { | |
17596 if (!path.valid) { | |
17597 console.error('on-* bindings must be simple path expressions'); | |
17598 return; | |
17599 } | |
17600 | |
17601 return prepareEventBinding(path, name, this); | |
17602 } | |
17603 | |
17604 if (!isLiteralExpression(pathString) && path.valid) { | |
17605 if (path.length == 1) { | |
17606 return function(model, node, oneTime) { | |
17607 if (oneTime) | |
17608 return path.getValueFrom(model); | |
17609 | |
17610 var scope = findScope(model, path[0]); | |
17611 return new PathObserver(scope, path); | |
17612 }; | |
17613 } | |
17614 return; // bail out early if pathString is simple path. | |
17615 } | |
17616 | |
17617 return prepareBinding(pathString, name, node, this); | |
17618 }, | |
17619 | |
17620 prepareInstanceModel: function(template) { | |
17621 var scopeName = template.polymerExpressionScopeIdent_; | |
17622 if (!scopeName) | |
17623 return; | |
17624 | |
17625 var parentScope = template.templateInstance ? | |
17626 template.templateInstance.model : | |
17627 template.model; | |
17628 | |
17629 var indexName = template.polymerExpressionIndexIdent_; | |
17630 | |
17631 return function(model) { | |
17632 var scope = Object.create(parentScope); | |
17633 scope[scopeName] = model; | |
17634 scope[indexName] = undefined; | |
17635 scope[parentScopeName] = parentScope; | |
17636 return scope; | |
17637 }; | |
17638 } | |
17639 }; | |
17640 | |
17641 global.PolymerExpressions = PolymerExpressions; | |
17642 if (global.exposeGetExpression) | |
17643 global.getExpression_ = getExpression; | |
17644 | |
17645 global.PolymerExpressions.prepareEventBinding = prepareEventBinding; | |
17646 })(this); | |
17647 | |
17648 /* | |
17649 * Copyright 2013 The Polymer Authors. All rights reserved. | |
17650 * Use of this source code is governed by a BSD-style | |
17651 * license that can be found in the LICENSE file. | |
17652 */ | |
17653 (function(scope) { | |
17654 | |
17655 // inject style sheet | |
17656 var style = document.createElement('style'); | |
17657 style.textContent = 'template {display: none !important;} /* injected by platfor
m.js */'; | |
17658 var head = document.querySelector('head'); | |
17659 head.insertBefore(style, head.firstChild); | |
17660 | |
17661 // flush (with logging) | |
17662 var flushing; | |
17663 function flush() { | |
17664 if (!flushing) { | |
17665 flushing = true; | |
17666 scope.endOfMicrotask(function() { | |
17667 flushing = false; | |
17668 logFlags.data && console.group('Platform.flush()'); | |
17669 scope.performMicrotaskCheckpoint(); | |
17670 logFlags.data && console.groupEnd(); | |
17671 }); | |
17672 } | |
17673 }; | |
17674 | |
17675 // polling dirty checker | |
17676 // flush periodically if platform does not have object observe. | |
17677 if (!Observer.hasObjectObserve) { | |
17678 var FLUSH_POLL_INTERVAL = 125; | |
17679 window.addEventListener('WebComponentsReady', function() { | |
17680 flush(); | |
17681 scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL); | |
17682 }); | |
17683 } else { | |
17684 // make flush a no-op when we have Object.observe | |
17685 flush = function() {}; | |
17686 } | |
17687 | |
17688 if (window.CustomElements && !CustomElements.useNative) { | |
17689 var originalImportNode = Document.prototype.importNode; | |
17690 Document.prototype.importNode = function(node, deep) { | |
17691 var imported = originalImportNode.call(this, node, deep); | |
17692 CustomElements.upgradeAll(imported); | |
17693 return imported; | |
17694 } | |
17695 } | |
17696 | |
17697 // exports | |
17698 scope.flush = flush; | |
17699 | |
17700 })(window.Platform); | |
17701 | |
17702 | |
17703 //# sourceMappingURL=platform.concat.js.map | |
OLD | NEW |