OLD | NEW |
| (Empty) |
1 /** | |
2 * @license | |
3 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
4 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
5 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
6 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
7 * Code distributed by Google as part of the polymer project is also | |
8 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
9 */ | |
10 // @version 0.5.5 | |
11 window.PolymerGestures = {}; | |
12 | |
13 (function(scope) { | |
14 var hasFullPath = false; | |
15 | |
16 // test for full event path support | |
17 var pathTest = document.createElement('meta'); | |
18 if (pathTest.createShadowRoot) { | |
19 var sr = pathTest.createShadowRoot(); | |
20 var s = document.createElement('span'); | |
21 sr.appendChild(s); | |
22 pathTest.addEventListener('testpath', function(ev) { | |
23 if (ev.path) { | |
24 // if the span is in the event path, then path[0] is the real source for
all events | |
25 hasFullPath = ev.path[0] === s; | |
26 } | |
27 ev.stopPropagation(); | |
28 }); | |
29 var ev = new CustomEvent('testpath', {bubbles: true}); | |
30 // must add node to DOM to trigger event listener | |
31 document.head.appendChild(pathTest); | |
32 s.dispatchEvent(ev); | |
33 pathTest.parentNode.removeChild(pathTest); | |
34 sr = s = null; | |
35 } | |
36 pathTest = null; | |
37 | |
38 var target = { | |
39 shadow: function(inEl) { | |
40 if (inEl) { | |
41 return inEl.shadowRoot || inEl.webkitShadowRoot; | |
42 } | |
43 }, | |
44 canTarget: function(shadow) { | |
45 return shadow && Boolean(shadow.elementFromPoint); | |
46 }, | |
47 targetingShadow: function(inEl) { | |
48 var s = this.shadow(inEl); | |
49 if (this.canTarget(s)) { | |
50 return s; | |
51 } | |
52 }, | |
53 olderShadow: function(shadow) { | |
54 var os = shadow.olderShadowRoot; | |
55 if (!os) { | |
56 var se = shadow.querySelector('shadow'); | |
57 if (se) { | |
58 os = se.olderShadowRoot; | |
59 } | |
60 } | |
61 return os; | |
62 }, | |
63 allShadows: function(element) { | |
64 var shadows = [], s = this.shadow(element); | |
65 while(s) { | |
66 shadows.push(s); | |
67 s = this.olderShadow(s); | |
68 } | |
69 return shadows; | |
70 }, | |
71 searchRoot: function(inRoot, x, y) { | |
72 var t, st, sr, os; | |
73 if (inRoot) { | |
74 t = inRoot.elementFromPoint(x, y); | |
75 if (t) { | |
76 // found element, check if it has a ShadowRoot | |
77 sr = this.targetingShadow(t); | |
78 } else if (inRoot !== document) { | |
79 // check for sibling roots | |
80 sr = this.olderShadow(inRoot); | |
81 } | |
82 // search other roots, fall back to light dom element | |
83 return this.searchRoot(sr, x, y) || t; | |
84 } | |
85 }, | |
86 owner: function(element) { | |
87 if (!element) { | |
88 return document; | |
89 } | |
90 var s = element; | |
91 // walk up until you hit the shadow root or document | |
92 while (s.parentNode) { | |
93 s = s.parentNode; | |
94 } | |
95 // the owner element is expected to be a Document or ShadowRoot | |
96 if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGME
NT_NODE) { | |
97 s = document; | |
98 } | |
99 return s; | |
100 }, | |
101 findTarget: function(inEvent) { | |
102 if (hasFullPath && inEvent.path && inEvent.path.length) { | |
103 return inEvent.path[0]; | |
104 } | |
105 var x = inEvent.clientX, y = inEvent.clientY; | |
106 // if the listener is in the shadow root, it is much faster to start there | |
107 var s = this.owner(inEvent.target); | |
108 // if x, y is not in this root, fall back to document search | |
109 if (!s.elementFromPoint(x, y)) { | |
110 s = document; | |
111 } | |
112 return this.searchRoot(s, x, y); | |
113 }, | |
114 findTouchAction: function(inEvent) { | |
115 var n; | |
116 if (hasFullPath && inEvent.path && inEvent.path.length) { | |
117 var path = inEvent.path; | |
118 for (var i = 0; i < path.length; i++) { | |
119 n = path[i]; | |
120 if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')
) { | |
121 return n.getAttribute('touch-action'); | |
122 } | |
123 } | |
124 } else { | |
125 n = inEvent.target; | |
126 while(n) { | |
127 if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')
) { | |
128 return n.getAttribute('touch-action'); | |
129 } | |
130 n = n.parentNode || n.host; | |
131 } | |
132 } | |
133 // auto is default | |
134 return "auto"; | |
135 }, | |
136 LCA: function(a, b) { | |
137 if (a === b) { | |
138 return a; | |
139 } | |
140 if (a && !b) { | |
141 return a; | |
142 } | |
143 if (b && !a) { | |
144 return b; | |
145 } | |
146 if (!b && !a) { | |
147 return document; | |
148 } | |
149 // fast case, a is a direct descendant of b or vice versa | |
150 if (a.contains && a.contains(b)) { | |
151 return a; | |
152 } | |
153 if (b.contains && b.contains(a)) { | |
154 return b; | |
155 } | |
156 var adepth = this.depth(a); | |
157 var bdepth = this.depth(b); | |
158 var d = adepth - bdepth; | |
159 if (d >= 0) { | |
160 a = this.walk(a, d); | |
161 } else { | |
162 b = this.walk(b, -d); | |
163 } | |
164 while (a && b && a !== b) { | |
165 a = a.parentNode || a.host; | |
166 b = b.parentNode || b.host; | |
167 } | |
168 return a; | |
169 }, | |
170 walk: function(n, u) { | |
171 for (var i = 0; n && (i < u); i++) { | |
172 n = n.parentNode || n.host; | |
173 } | |
174 return n; | |
175 }, | |
176 depth: function(n) { | |
177 var d = 0; | |
178 while(n) { | |
179 d++; | |
180 n = n.parentNode || n.host; | |
181 } | |
182 return d; | |
183 }, | |
184 deepContains: function(a, b) { | |
185 var common = this.LCA(a, b); | |
186 // if a is the common ancestor, it must "deeply" contain b | |
187 return common === a; | |
188 }, | |
189 insideNode: function(node, x, y) { | |
190 var rect = node.getBoundingClientRect(); | |
191 return (rect.left <= x) && (x <= rect.right) && (rect.top <= y) && (y <= r
ect.bottom); | |
192 }, | |
193 path: function(event) { | |
194 var p; | |
195 if (hasFullPath && event.path && event.path.length) { | |
196 p = event.path; | |
197 } else { | |
198 p = []; | |
199 var n = this.findTarget(event); | |
200 while (n) { | |
201 p.push(n); | |
202 n = n.parentNode || n.host; | |
203 } | |
204 } | |
205 return p; | |
206 } | |
207 }; | |
208 scope.targetFinding = target; | |
209 /** | |
210 * Given an event, finds the "deepest" node that could have been the original
target before ShadowDOM retargetting | |
211 * | |
212 * @param {Event} Event An event object with clientX and clientY properties | |
213 * @return {Element} The probable event origninator | |
214 */ | |
215 scope.findTarget = target.findTarget.bind(target); | |
216 /** | |
217 * Determines if the "container" node deeply contains the "containee" node, in
cluding situations where the "containee" is contained by one or more ShadowDOM | |
218 * roots. | |
219 * | |
220 * @param {Node} container | |
221 * @param {Node} containee | |
222 * @return {Boolean} | |
223 */ | |
224 scope.deepContains = target.deepContains.bind(target); | |
225 | |
226 /** | |
227 * Determines if the x/y position is inside the given node. | |
228 * | |
229 * Example: | |
230 * | |
231 * function upHandler(event) { | |
232 * var innode = PolymerGestures.insideNode(event.target, event.clientX,
event.clientY); | |
233 * if (innode) { | |
234 * // wait for tap? | |
235 * } else { | |
236 * // tap will never happen | |
237 * } | |
238 * } | |
239 * | |
240 * @param {Node} node | |
241 * @param {Number} x Screen X position | |
242 * @param {Number} y screen Y position | |
243 * @return {Boolean} | |
244 */ | |
245 scope.insideNode = target.insideNode; | |
246 | |
247 })(window.PolymerGestures); | |
248 | |
249 (function() { | |
250 function shadowSelector(v) { | |
251 return 'html /deep/ ' + selector(v); | |
252 } | |
253 function selector(v) { | |
254 return '[touch-action="' + v + '"]'; | |
255 } | |
256 function rule(v) { | |
257 return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + ';}'; | |
258 } | |
259 var attrib2css = [ | |
260 'none', | |
261 'auto', | |
262 'pan-x', | |
263 'pan-y', | |
264 { | |
265 rule: 'pan-x pan-y', | |
266 selectors: [ | |
267 'pan-x pan-y', | |
268 'pan-y pan-x' | |
269 ] | |
270 }, | |
271 'manipulation' | |
272 ]; | |
273 var styles = ''; | |
274 // only install stylesheet if the browser has touch action support | |
275 var hasTouchAction = typeof document.head.style.touchAction === 'string'; | |
276 // only add shadow selectors if shadowdom is supported | |
277 var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoo
t; | |
278 | |
279 if (hasTouchAction) { | |
280 attrib2css.forEach(function(r) { | |
281 if (String(r) === r) { | |
282 styles += selector(r) + rule(r) + '\n'; | |
283 if (hasShadowRoot) { | |
284 styles += shadowSelector(r) + rule(r) + '\n'; | |
285 } | |
286 } else { | |
287 styles += r.selectors.map(selector) + rule(r.rule) + '\n'; | |
288 if (hasShadowRoot) { | |
289 styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; | |
290 } | |
291 } | |
292 }); | |
293 | |
294 var el = document.createElement('style'); | |
295 el.textContent = styles; | |
296 document.head.appendChild(el); | |
297 } | |
298 })(); | |
299 | |
300 /** | |
301 * This is the constructor for new PointerEvents. | |
302 * | |
303 * New Pointer Events must be given a type, and an optional dictionary of | |
304 * initialization properties. | |
305 * | |
306 * Due to certain platform requirements, events returned from the constructor | |
307 * identify as MouseEvents. | |
308 * | |
309 * @constructor | |
310 * @param {String} inType The type of the event to create. | |
311 * @param {Object} [inDict] An optional dictionary of initial event properties. | |
312 * @return {Event} A new PointerEvent of type `inType` and initialized with prop
erties from `inDict`. | |
313 */ | |
314 (function(scope) { | |
315 | |
316 var MOUSE_PROPS = [ | |
317 'bubbles', | |
318 'cancelable', | |
319 'view', | |
320 'detail', | |
321 'screenX', | |
322 'screenY', | |
323 'clientX', | |
324 'clientY', | |
325 'ctrlKey', | |
326 'altKey', | |
327 'shiftKey', | |
328 'metaKey', | |
329 'button', | |
330 'relatedTarget', | |
331 'pageX', | |
332 'pageY' | |
333 ]; | |
334 | |
335 var MOUSE_DEFAULTS = [ | |
336 false, | |
337 false, | |
338 null, | |
339 null, | |
340 0, | |
341 0, | |
342 0, | |
343 0, | |
344 false, | |
345 false, | |
346 false, | |
347 false, | |
348 0, | |
349 null, | |
350 0, | |
351 0 | |
352 ]; | |
353 | |
354 var NOP_FACTORY = function(){ return function(){}; }; | |
355 | |
356 var eventFactory = { | |
357 // TODO(dfreedm): this is overridden by tap recognizer, needs review | |
358 preventTap: NOP_FACTORY, | |
359 makeBaseEvent: function(inType, inDict) { | |
360 var e = document.createEvent('Event'); | |
361 e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); | |
362 e.preventTap = eventFactory.preventTap(e); | |
363 return e; | |
364 }, | |
365 makeGestureEvent: function(inType, inDict) { | |
366 inDict = inDict || Object.create(null); | |
367 | |
368 var e = this.makeBaseEvent(inType, inDict); | |
369 for (var i = 0, keys = Object.keys(inDict), k; i < keys.length; i++) { | |
370 k = keys[i]; | |
371 if( k !== 'bubbles' && k !== 'cancelable' ) { | |
372 e[k] = inDict[k]; | |
373 } | |
374 } | |
375 return e; | |
376 }, | |
377 makePointerEvent: function(inType, inDict) { | |
378 inDict = inDict || Object.create(null); | |
379 | |
380 var e = this.makeBaseEvent(inType, inDict); | |
381 // define inherited MouseEvent properties | |
382 for(var i = 2, p; i < MOUSE_PROPS.length; i++) { | |
383 p = MOUSE_PROPS[i]; | |
384 e[p] = inDict[p] || MOUSE_DEFAULTS[i]; | |
385 } | |
386 e.buttons = inDict.buttons || 0; | |
387 | |
388 // Spec requires that pointers without pressure specified use 0.5 for down | |
389 // state and 0 for up state. | |
390 var pressure = 0; | |
391 if (inDict.pressure) { | |
392 pressure = inDict.pressure; | |
393 } else { | |
394 pressure = e.buttons ? 0.5 : 0; | |
395 } | |
396 | |
397 // add x/y properties aliased to clientX/Y | |
398 e.x = e.clientX; | |
399 e.y = e.clientY; | |
400 | |
401 // define the properties of the PointerEvent interface | |
402 e.pointerId = inDict.pointerId || 0; | |
403 e.width = inDict.width || 0; | |
404 e.height = inDict.height || 0; | |
405 e.pressure = pressure; | |
406 e.tiltX = inDict.tiltX || 0; | |
407 e.tiltY = inDict.tiltY || 0; | |
408 e.pointerType = inDict.pointerType || ''; | |
409 e.hwTimestamp = inDict.hwTimestamp || 0; | |
410 e.isPrimary = inDict.isPrimary || false; | |
411 e._source = inDict._source || ''; | |
412 return e; | |
413 } | |
414 }; | |
415 | |
416 scope.eventFactory = eventFactory; | |
417 })(window.PolymerGestures); | |
418 | |
419 /** | |
420 * This module implements an map of pointer states | |
421 */ | |
422 (function(scope) { | |
423 var USE_MAP = window.Map && window.Map.prototype.forEach; | |
424 var POINTERS_FN = function(){ return this.size; }; | |
425 function PointerMap() { | |
426 if (USE_MAP) { | |
427 var m = new Map(); | |
428 m.pointers = POINTERS_FN; | |
429 return m; | |
430 } else { | |
431 this.keys = []; | |
432 this.values = []; | |
433 } | |
434 } | |
435 | |
436 PointerMap.prototype = { | |
437 set: function(inId, inEvent) { | |
438 var i = this.keys.indexOf(inId); | |
439 if (i > -1) { | |
440 this.values[i] = inEvent; | |
441 } else { | |
442 this.keys.push(inId); | |
443 this.values.push(inEvent); | |
444 } | |
445 }, | |
446 has: function(inId) { | |
447 return this.keys.indexOf(inId) > -1; | |
448 }, | |
449 'delete': function(inId) { | |
450 var i = this.keys.indexOf(inId); | |
451 if (i > -1) { | |
452 this.keys.splice(i, 1); | |
453 this.values.splice(i, 1); | |
454 } | |
455 }, | |
456 get: function(inId) { | |
457 var i = this.keys.indexOf(inId); | |
458 return this.values[i]; | |
459 }, | |
460 clear: function() { | |
461 this.keys.length = 0; | |
462 this.values.length = 0; | |
463 }, | |
464 // return value, key, map | |
465 forEach: function(callback, thisArg) { | |
466 this.values.forEach(function(v, i) { | |
467 callback.call(thisArg, v, this.keys[i], this); | |
468 }, this); | |
469 }, | |
470 pointers: function() { | |
471 return this.keys.length; | |
472 } | |
473 }; | |
474 | |
475 scope.PointerMap = PointerMap; | |
476 })(window.PolymerGestures); | |
477 | |
478 (function(scope) { | |
479 var CLONE_PROPS = [ | |
480 // MouseEvent | |
481 'bubbles', | |
482 'cancelable', | |
483 'view', | |
484 'detail', | |
485 'screenX', | |
486 'screenY', | |
487 'clientX', | |
488 'clientY', | |
489 'ctrlKey', | |
490 'altKey', | |
491 'shiftKey', | |
492 'metaKey', | |
493 'button', | |
494 'relatedTarget', | |
495 // DOM Level 3 | |
496 'buttons', | |
497 // PointerEvent | |
498 'pointerId', | |
499 'width', | |
500 'height', | |
501 'pressure', | |
502 'tiltX', | |
503 'tiltY', | |
504 'pointerType', | |
505 'hwTimestamp', | |
506 'isPrimary', | |
507 // event instance | |
508 'type', | |
509 'target', | |
510 'currentTarget', | |
511 'which', | |
512 'pageX', | |
513 'pageY', | |
514 'timeStamp', | |
515 // gesture addons | |
516 'preventTap', | |
517 'tapPrevented', | |
518 '_source' | |
519 ]; | |
520 | |
521 var CLONE_DEFAULTS = [ | |
522 // MouseEvent | |
523 false, | |
524 false, | |
525 null, | |
526 null, | |
527 0, | |
528 0, | |
529 0, | |
530 0, | |
531 false, | |
532 false, | |
533 false, | |
534 false, | |
535 0, | |
536 null, | |
537 // DOM Level 3 | |
538 0, | |
539 // PointerEvent | |
540 0, | |
541 0, | |
542 0, | |
543 0, | |
544 0, | |
545 0, | |
546 '', | |
547 0, | |
548 false, | |
549 // event instance | |
550 '', | |
551 null, | |
552 null, | |
553 0, | |
554 0, | |
555 0, | |
556 0, | |
557 function(){}, | |
558 false | |
559 ]; | |
560 | |
561 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); | |
562 | |
563 var eventFactory = scope.eventFactory; | |
564 | |
565 // set of recognizers to run for the currently handled event | |
566 var currentGestures; | |
567 | |
568 /** | |
569 * This module is for normalizing events. Mouse and Touch events will be | |
570 * collected here, and fire PointerEvents that have the same semantics, no | |
571 * matter the source. | |
572 * Events fired: | |
573 * - pointerdown: a pointing is added | |
574 * - pointerup: a pointer is removed | |
575 * - pointermove: a pointer is moved | |
576 * - pointerover: a pointer crosses into an element | |
577 * - pointerout: a pointer leaves an element | |
578 * - pointercancel: a pointer will no longer generate events | |
579 */ | |
580 var dispatcher = { | |
581 IS_IOS: false, | |
582 pointermap: new scope.PointerMap(), | |
583 requiredGestures: new scope.PointerMap(), | |
584 eventMap: Object.create(null), | |
585 // Scope objects for native events. | |
586 // This exists for ease of testing. | |
587 eventSources: Object.create(null), | |
588 eventSourceList: [], | |
589 gestures: [], | |
590 // map gesture event -> {listeners: int, index: gestures[int]} | |
591 dependencyMap: { | |
592 // make sure down and up are in the map to trigger "register" | |
593 down: {listeners: 0, index: -1}, | |
594 up: {listeners: 0, index: -1} | |
595 }, | |
596 gestureQueue: [], | |
597 /** | |
598 * Add a new event source that will generate pointer events. | |
599 * | |
600 * `inSource` must contain an array of event names named `events`, and | |
601 * functions with the names specified in the `events` array. | |
602 * @param {string} name A name for the event source | |
603 * @param {Object} source A new source of platform events. | |
604 */ | |
605 registerSource: function(name, source) { | |
606 var s = source; | |
607 var newEvents = s.events; | |
608 if (newEvents) { | |
609 newEvents.forEach(function(e) { | |
610 if (s[e]) { | |
611 this.eventMap[e] = s[e].bind(s); | |
612 } | |
613 }, this); | |
614 this.eventSources[name] = s; | |
615 this.eventSourceList.push(s); | |
616 } | |
617 }, | |
618 registerGesture: function(name, source) { | |
619 var obj = Object.create(null); | |
620 obj.listeners = 0; | |
621 obj.index = this.gestures.length; | |
622 for (var i = 0, g; i < source.exposes.length; i++) { | |
623 g = source.exposes[i].toLowerCase(); | |
624 this.dependencyMap[g] = obj; | |
625 } | |
626 this.gestures.push(source); | |
627 }, | |
628 register: function(element, initial) { | |
629 var l = this.eventSourceList.length; | |
630 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | |
631 // call eventsource register | |
632 es.register.call(es, element, initial); | |
633 } | |
634 }, | |
635 unregister: function(element) { | |
636 var l = this.eventSourceList.length; | |
637 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | |
638 // call eventsource register | |
639 es.unregister.call(es, element); | |
640 } | |
641 }, | |
642 // EVENTS | |
643 down: function(inEvent) { | |
644 this.requiredGestures.set(inEvent.pointerId, currentGestures); | |
645 this.fireEvent('down', inEvent); | |
646 }, | |
647 move: function(inEvent) { | |
648 // pipe move events into gesture queue directly | |
649 inEvent.type = 'move'; | |
650 this.fillGestureQueue(inEvent); | |
651 }, | |
652 up: function(inEvent) { | |
653 this.fireEvent('up', inEvent); | |
654 this.requiredGestures.delete(inEvent.pointerId); | |
655 }, | |
656 cancel: function(inEvent) { | |
657 inEvent.tapPrevented = true; | |
658 this.fireEvent('up', inEvent); | |
659 this.requiredGestures.delete(inEvent.pointerId); | |
660 }, | |
661 addGestureDependency: function(node, currentGestures) { | |
662 var gesturesWanted = node._pgEvents; | |
663 if (gesturesWanted && currentGestures) { | |
664 var gk = Object.keys(gesturesWanted); | |
665 for (var i = 0, r, ri, g; i < gk.length; i++) { | |
666 // gesture | |
667 g = gk[i]; | |
668 if (gesturesWanted[g] > 0) { | |
669 // lookup gesture recognizer | |
670 r = this.dependencyMap[g]; | |
671 // recognizer index | |
672 ri = r ? r.index : -1; | |
673 currentGestures[ri] = true; | |
674 } | |
675 } | |
676 } | |
677 }, | |
678 // LISTENER LOGIC | |
679 eventHandler: function(inEvent) { | |
680 // This is used to prevent multiple dispatch of events from | |
681 // platform events. This can happen when two elements in different scopes | |
682 // are set up to create pointer events, which is relevant to Shadow DOM. | |
683 | |
684 var type = inEvent.type; | |
685 | |
686 // only generate the list of desired events on "down" | |
687 if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown
' || type === 'MSPointerDown') { | |
688 if (!inEvent._handledByPG) { | |
689 currentGestures = {}; | |
690 } | |
691 | |
692 // in IOS mode, there is only a listener on the document, so this is not
re-entrant | |
693 if (this.IS_IOS) { | |
694 var ev = inEvent; | |
695 if (type === 'touchstart') { | |
696 var ct = inEvent.changedTouches[0]; | |
697 // set up a fake event to give to the path builder | |
698 ev = {target: inEvent.target, clientX: ct.clientX, clientY: ct.clien
tY, path: inEvent.path}; | |
699 } | |
700 // use event path if available, otherwise build a path from target fin
ding | |
701 var nodes = inEvent.path || scope.targetFinding.path(ev); | |
702 for (var i = 0, n; i < nodes.length; i++) { | |
703 n = nodes[i]; | |
704 this.addGestureDependency(n, currentGestures); | |
705 } | |
706 } else { | |
707 this.addGestureDependency(inEvent.currentTarget, currentGestures); | |
708 } | |
709 } | |
710 | |
711 if (inEvent._handledByPG) { | |
712 return; | |
713 } | |
714 var fn = this.eventMap && this.eventMap[type]; | |
715 if (fn) { | |
716 fn(inEvent); | |
717 } | |
718 inEvent._handledByPG = true; | |
719 }, | |
720 // set up event listeners | |
721 listen: function(target, events) { | |
722 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { | |
723 this.addEvent(target, e); | |
724 } | |
725 }, | |
726 // remove event listeners | |
727 unlisten: function(target, events) { | |
728 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { | |
729 this.removeEvent(target, e); | |
730 } | |
731 }, | |
732 addEvent: function(target, eventName) { | |
733 target.addEventListener(eventName, this.boundHandler); | |
734 }, | |
735 removeEvent: function(target, eventName) { | |
736 target.removeEventListener(eventName, this.boundHandler); | |
737 }, | |
738 // EVENT CREATION AND TRACKING | |
739 /** | |
740 * Creates a new Event of type `inType`, based on the information in | |
741 * `inEvent`. | |
742 * | |
743 * @param {string} inType A string representing the type of event to create | |
744 * @param {Event} inEvent A platform event with a target | |
745 * @return {Event} A PointerEvent of type `inType` | |
746 */ | |
747 makeEvent: function(inType, inEvent) { | |
748 var e = eventFactory.makePointerEvent(inType, inEvent); | |
749 e.preventDefault = inEvent.preventDefault; | |
750 e.tapPrevented = inEvent.tapPrevented; | |
751 e._target = e._target || inEvent.target; | |
752 return e; | |
753 }, | |
754 // make and dispatch an event in one call | |
755 fireEvent: function(inType, inEvent) { | |
756 var e = this.makeEvent(inType, inEvent); | |
757 return this.dispatchEvent(e); | |
758 }, | |
759 /** | |
760 * Returns a snapshot of inEvent, with writable properties. | |
761 * | |
762 * @param {Event} inEvent An event that contains properties to copy. | |
763 * @return {Object} An object containing shallow copies of `inEvent`'s | |
764 * properties. | |
765 */ | |
766 cloneEvent: function(inEvent) { | |
767 var eventCopy = Object.create(null), p; | |
768 for (var i = 0; i < CLONE_PROPS.length; i++) { | |
769 p = CLONE_PROPS[i]; | |
770 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; | |
771 // Work around SVGInstanceElement shadow tree | |
772 // Return the <use> element that is represented by the instance for Safa
ri, Chrome, IE. | |
773 // This is the behavior implemented by Firefox. | |
774 if (p === 'target' || p === 'relatedTarget') { | |
775 if (HAS_SVG_INSTANCE && eventCopy[p] instanceof SVGElementInstance) { | |
776 eventCopy[p] = eventCopy[p].correspondingUseElement; | |
777 } | |
778 } | |
779 } | |
780 // keep the semantics of preventDefault | |
781 eventCopy.preventDefault = function() { | |
782 inEvent.preventDefault(); | |
783 }; | |
784 return eventCopy; | |
785 }, | |
786 /** | |
787 * Dispatches the event to its target. | |
788 * | |
789 * @param {Event} inEvent The event to be dispatched. | |
790 * @return {Boolean} True if an event handler returns true, false otherwise. | |
791 */ | |
792 dispatchEvent: function(inEvent) { | |
793 var t = inEvent._target; | |
794 if (t) { | |
795 t.dispatchEvent(inEvent); | |
796 // clone the event for the gesture system to process | |
797 // clone after dispatch to pick up gesture prevention code | |
798 var clone = this.cloneEvent(inEvent); | |
799 clone.target = t; | |
800 this.fillGestureQueue(clone); | |
801 } | |
802 }, | |
803 gestureTrigger: function() { | |
804 // process the gesture queue | |
805 for (var i = 0, e, rg; i < this.gestureQueue.length; i++) { | |
806 e = this.gestureQueue[i]; | |
807 rg = e._requiredGestures; | |
808 if (rg) { | |
809 for (var j = 0, g, fn; j < this.gestures.length; j++) { | |
810 // only run recognizer if an element in the source event's path is l
istening for those gestures | |
811 if (rg[j]) { | |
812 g = this.gestures[j]; | |
813 fn = g[e.type]; | |
814 if (fn) { | |
815 fn.call(g, e); | |
816 } | |
817 } | |
818 } | |
819 } | |
820 } | |
821 this.gestureQueue.length = 0; | |
822 }, | |
823 fillGestureQueue: function(ev) { | |
824 // only trigger the gesture queue once | |
825 if (!this.gestureQueue.length) { | |
826 requestAnimationFrame(this.boundGestureTrigger); | |
827 } | |
828 ev._requiredGestures = this.requiredGestures.get(ev.pointerId); | |
829 this.gestureQueue.push(ev); | |
830 } | |
831 }; | |
832 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); | |
833 dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); | |
834 scope.dispatcher = dispatcher; | |
835 | |
836 /** | |
837 * Listen for `gesture` on `node` with the `handler` function | |
838 * | |
839 * If `handler` is the first listener for `gesture`, the underlying gesture re
cognizer is then enabled. | |
840 * | |
841 * @param {Element} node | |
842 * @param {string} gesture | |
843 * @return Boolean `gesture` is a valid gesture | |
844 */ | |
845 scope.activateGesture = function(node, gesture) { | |
846 var g = gesture.toLowerCase(); | |
847 var dep = dispatcher.dependencyMap[g]; | |
848 if (dep) { | |
849 var recognizer = dispatcher.gestures[dep.index]; | |
850 if (!node._pgListeners) { | |
851 dispatcher.register(node); | |
852 node._pgListeners = 0; | |
853 } | |
854 // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes | |
855 if (recognizer) { | |
856 var touchAction = recognizer.defaultActions && recognizer.defaultActions
[g]; | |
857 var actionNode; | |
858 switch(node.nodeType) { | |
859 case Node.ELEMENT_NODE: | |
860 actionNode = node; | |
861 break; | |
862 case Node.DOCUMENT_FRAGMENT_NODE: | |
863 actionNode = node.host; | |
864 break; | |
865 default: | |
866 actionNode = null; | |
867 break; | |
868 } | |
869 if (touchAction && actionNode && !actionNode.hasAttribute('touch-action'
)) { | |
870 actionNode.setAttribute('touch-action', touchAction); | |
871 } | |
872 } | |
873 if (!node._pgEvents) { | |
874 node._pgEvents = {}; | |
875 } | |
876 node._pgEvents[g] = (node._pgEvents[g] || 0) + 1; | |
877 node._pgListeners++; | |
878 } | |
879 return Boolean(dep); | |
880 }; | |
881 | |
882 /** | |
883 * | |
884 * Listen for `gesture` from `node` with `handler` function. | |
885 * | |
886 * @param {Element} node | |
887 * @param {string} gesture | |
888 * @param {Function} handler | |
889 * @param {Boolean} capture | |
890 */ | |
891 scope.addEventListener = function(node, gesture, handler, capture) { | |
892 if (handler) { | |
893 scope.activateGesture(node, gesture); | |
894 node.addEventListener(gesture, handler, capture); | |
895 } | |
896 }; | |
897 | |
898 /** | |
899 * Tears down the gesture configuration for `node` | |
900 * | |
901 * If `handler` is the last listener for `gesture`, the underlying gesture rec
ognizer is disabled. | |
902 * | |
903 * @param {Element} node | |
904 * @param {string} gesture | |
905 * @return Boolean `gesture` is a valid gesture | |
906 */ | |
907 scope.deactivateGesture = function(node, gesture) { | |
908 var g = gesture.toLowerCase(); | |
909 var dep = dispatcher.dependencyMap[g]; | |
910 if (dep) { | |
911 if (node._pgListeners > 0) { | |
912 node._pgListeners--; | |
913 } | |
914 if (node._pgListeners === 0) { | |
915 dispatcher.unregister(node); | |
916 } | |
917 if (node._pgEvents) { | |
918 if (node._pgEvents[g] > 0) { | |
919 node._pgEvents[g]--; | |
920 } else { | |
921 node._pgEvents[g] = 0; | |
922 } | |
923 } | |
924 } | |
925 return Boolean(dep); | |
926 }; | |
927 | |
928 /** | |
929 * Stop listening for `gesture` from `node` with `handler` function. | |
930 * | |
931 * @param {Element} node | |
932 * @param {string} gesture | |
933 * @param {Function} handler | |
934 * @param {Boolean} capture | |
935 */ | |
936 scope.removeEventListener = function(node, gesture, handler, capture) { | |
937 if (handler) { | |
938 scope.deactivateGesture(node, gesture); | |
939 node.removeEventListener(gesture, handler, capture); | |
940 } | |
941 }; | |
942 })(window.PolymerGestures); | |
943 | |
944 (function(scope) { | |
945 var dispatcher = scope.dispatcher; | |
946 var pointermap = dispatcher.pointermap; | |
947 // radius around touchend that swallows mouse events | |
948 var DEDUP_DIST = 25; | |
949 | |
950 var WHICH_TO_BUTTONS = [0, 1, 4, 2]; | |
951 | |
952 var currentButtons = 0; | |
953 | |
954 var FIREFOX_LINUX = /Linux.*Firefox\//i; | |
955 | |
956 var HAS_BUTTONS = (function() { | |
957 // firefox on linux returns spec-incorrect values for mouseup.buttons | |
958 // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.buttons#See_a
lso | |
959 // https://codereview.chromium.org/727593003/#msg16 | |
960 if (FIREFOX_LINUX.test(navigator.userAgent)) { | |
961 return false; | |
962 } | |
963 try { | |
964 return new MouseEvent('test', {buttons: 1}).buttons === 1; | |
965 } catch (e) { | |
966 return false; | |
967 } | |
968 })(); | |
969 | |
970 // handler block for native mouse events | |
971 var mouseEvents = { | |
972 POINTER_ID: 1, | |
973 POINTER_TYPE: 'mouse', | |
974 events: [ | |
975 'mousedown', | |
976 'mousemove', | |
977 'mouseup' | |
978 ], | |
979 exposes: [ | |
980 'down', | |
981 'up', | |
982 'move' | |
983 ], | |
984 register: function(target) { | |
985 dispatcher.listen(target, this.events); | |
986 }, | |
987 unregister: function(target) { | |
988 if (target.nodeType === Node.DOCUMENT_NODE) { | |
989 return; | |
990 } | |
991 dispatcher.unlisten(target, this.events); | |
992 }, | |
993 lastTouches: [], | |
994 // collide with the global mouse listener | |
995 isEventSimulatedFromTouch: function(inEvent) { | |
996 var lts = this.lastTouches; | |
997 var x = inEvent.clientX, y = inEvent.clientY; | |
998 for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { | |
999 // simulated mouse events will be swallowed near a primary touchend | |
1000 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); | |
1001 if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { | |
1002 return true; | |
1003 } | |
1004 } | |
1005 }, | |
1006 prepareEvent: function(inEvent) { | |
1007 var e = dispatcher.cloneEvent(inEvent); | |
1008 e.pointerId = this.POINTER_ID; | |
1009 e.isPrimary = true; | |
1010 e.pointerType = this.POINTER_TYPE; | |
1011 e._source = 'mouse'; | |
1012 if (!HAS_BUTTONS) { | |
1013 var type = inEvent.type; | |
1014 var bit = WHICH_TO_BUTTONS[inEvent.which] || 0; | |
1015 if (type === 'mousedown') { | |
1016 currentButtons |= bit; | |
1017 } else if (type === 'mouseup') { | |
1018 currentButtons &= ~bit; | |
1019 } | |
1020 e.buttons = currentButtons; | |
1021 } | |
1022 return e; | |
1023 }, | |
1024 mousedown: function(inEvent) { | |
1025 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
1026 var p = pointermap.has(this.POINTER_ID); | |
1027 var e = this.prepareEvent(inEvent); | |
1028 e.target = scope.findTarget(inEvent); | |
1029 pointermap.set(this.POINTER_ID, e.target); | |
1030 dispatcher.down(e); | |
1031 } | |
1032 }, | |
1033 mousemove: function(inEvent) { | |
1034 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
1035 var target = pointermap.get(this.POINTER_ID); | |
1036 if (target) { | |
1037 var e = this.prepareEvent(inEvent); | |
1038 e.target = target; | |
1039 // handle case where we missed a mouseup | |
1040 if ((HAS_BUTTONS ? e.buttons : e.which) === 0) { | |
1041 if (!HAS_BUTTONS) { | |
1042 currentButtons = e.buttons = 0; | |
1043 } | |
1044 dispatcher.cancel(e); | |
1045 this.cleanupMouse(e.buttons); | |
1046 } else { | |
1047 dispatcher.move(e); | |
1048 } | |
1049 } | |
1050 } | |
1051 }, | |
1052 mouseup: function(inEvent) { | |
1053 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
1054 var e = this.prepareEvent(inEvent); | |
1055 e.relatedTarget = scope.findTarget(inEvent); | |
1056 e.target = pointermap.get(this.POINTER_ID); | |
1057 dispatcher.up(e); | |
1058 this.cleanupMouse(e.buttons); | |
1059 } | |
1060 }, | |
1061 cleanupMouse: function(buttons) { | |
1062 if (buttons === 0) { | |
1063 pointermap.delete(this.POINTER_ID); | |
1064 } | |
1065 } | |
1066 }; | |
1067 | |
1068 scope.mouseEvents = mouseEvents; | |
1069 })(window.PolymerGestures); | |
1070 | |
1071 (function(scope) { | |
1072 var dispatcher = scope.dispatcher; | |
1073 var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); | |
1074 var pointermap = dispatcher.pointermap; | |
1075 var touchMap = Array.prototype.map.call.bind(Array.prototype.map); | |
1076 // This should be long enough to ignore compat mouse events made by touch | |
1077 var DEDUP_TIMEOUT = 2500; | |
1078 var DEDUP_DIST = 25; | |
1079 var CLICK_COUNT_TIMEOUT = 200; | |
1080 var HYSTERESIS = 20; | |
1081 var ATTRIB = 'touch-action'; | |
1082 // TODO(dfreedm): disable until http://crbug.com/399765 is resolved | |
1083 // var HAS_TOUCH_ACTION = ATTRIB in document.head.style; | |
1084 var HAS_TOUCH_ACTION = false; | |
1085 | |
1086 // handler block for native touch events | |
1087 var touchEvents = { | |
1088 IS_IOS: false, | |
1089 events: [ | |
1090 'touchstart', | |
1091 'touchmove', | |
1092 'touchend', | |
1093 'touchcancel' | |
1094 ], | |
1095 exposes: [ | |
1096 'down', | |
1097 'up', | |
1098 'move' | |
1099 ], | |
1100 register: function(target, initial) { | |
1101 if (this.IS_IOS ? initial : !initial) { | |
1102 dispatcher.listen(target, this.events); | |
1103 } | |
1104 }, | |
1105 unregister: function(target) { | |
1106 if (!this.IS_IOS) { | |
1107 dispatcher.unlisten(target, this.events); | |
1108 } | |
1109 }, | |
1110 scrollTypes: { | |
1111 EMITTER: 'none', | |
1112 XSCROLLER: 'pan-x', | |
1113 YSCROLLER: 'pan-y', | |
1114 }, | |
1115 touchActionToScrollType: function(touchAction) { | |
1116 var t = touchAction; | |
1117 var st = this.scrollTypes; | |
1118 if (t === st.EMITTER) { | |
1119 return 'none'; | |
1120 } else if (t === st.XSCROLLER) { | |
1121 return 'X'; | |
1122 } else if (t === st.YSCROLLER) { | |
1123 return 'Y'; | |
1124 } else { | |
1125 return 'XY'; | |
1126 } | |
1127 }, | |
1128 POINTER_TYPE: 'touch', | |
1129 firstTouch: null, | |
1130 isPrimaryTouch: function(inTouch) { | |
1131 return this.firstTouch === inTouch.identifier; | |
1132 }, | |
1133 setPrimaryTouch: function(inTouch) { | |
1134 // set primary touch if there no pointers, or the only pointer is the mous
e | |
1135 if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointer
map.has(1))) { | |
1136 this.firstTouch = inTouch.identifier; | |
1137 this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; | |
1138 this.firstTarget = inTouch.target; | |
1139 this.scrolling = null; | |
1140 this.cancelResetClickCount(); | |
1141 } | |
1142 }, | |
1143 removePrimaryPointer: function(inPointer) { | |
1144 if (inPointer.isPrimary) { | |
1145 this.firstTouch = null; | |
1146 this.firstXY = null; | |
1147 this.resetClickCount(); | |
1148 } | |
1149 }, | |
1150 clickCount: 0, | |
1151 resetId: null, | |
1152 resetClickCount: function() { | |
1153 var fn = function() { | |
1154 this.clickCount = 0; | |
1155 this.resetId = null; | |
1156 }.bind(this); | |
1157 this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); | |
1158 }, | |
1159 cancelResetClickCount: function() { | |
1160 if (this.resetId) { | |
1161 clearTimeout(this.resetId); | |
1162 } | |
1163 }, | |
1164 typeToButtons: function(type) { | |
1165 var ret = 0; | |
1166 if (type === 'touchstart' || type === 'touchmove') { | |
1167 ret = 1; | |
1168 } | |
1169 return ret; | |
1170 }, | |
1171 findTarget: function(touch, id) { | |
1172 if (this.currentTouchEvent.type === 'touchstart') { | |
1173 if (this.isPrimaryTouch(touch)) { | |
1174 var fastPath = { | |
1175 clientX: touch.clientX, | |
1176 clientY: touch.clientY, | |
1177 path: this.currentTouchEvent.path, | |
1178 target: this.currentTouchEvent.target | |
1179 }; | |
1180 return scope.findTarget(fastPath); | |
1181 } else { | |
1182 return scope.findTarget(touch); | |
1183 } | |
1184 } | |
1185 // reuse target we found in touchstart | |
1186 return pointermap.get(id); | |
1187 }, | |
1188 touchToPointer: function(inTouch) { | |
1189 var cte = this.currentTouchEvent; | |
1190 var e = dispatcher.cloneEvent(inTouch); | |
1191 // Spec specifies that pointerId 1 is reserved for Mouse. | |
1192 // Touch identifiers can start at 0. | |
1193 // Add 2 to the touch identifier for compatibility. | |
1194 var id = e.pointerId = inTouch.identifier + 2; | |
1195 e.target = this.findTarget(inTouch, id); | |
1196 e.bubbles = true; | |
1197 e.cancelable = true; | |
1198 e.detail = this.clickCount; | |
1199 e.buttons = this.typeToButtons(cte.type); | |
1200 e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; | |
1201 e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; | |
1202 e.pressure = inTouch.webkitForce || inTouch.force || 0.5; | |
1203 e.isPrimary = this.isPrimaryTouch(inTouch); | |
1204 e.pointerType = this.POINTER_TYPE; | |
1205 e._source = 'touch'; | |
1206 // forward touch preventDefaults | |
1207 var self = this; | |
1208 e.preventDefault = function() { | |
1209 self.scrolling = false; | |
1210 self.firstXY = null; | |
1211 cte.preventDefault(); | |
1212 }; | |
1213 return e; | |
1214 }, | |
1215 processTouches: function(inEvent, inFunction) { | |
1216 var tl = inEvent.changedTouches; | |
1217 this.currentTouchEvent = inEvent; | |
1218 for (var i = 0, t, p; i < tl.length; i++) { | |
1219 t = tl[i]; | |
1220 p = this.touchToPointer(t); | |
1221 if (inEvent.type === 'touchstart') { | |
1222 pointermap.set(p.pointerId, p.target); | |
1223 } | |
1224 if (pointermap.has(p.pointerId)) { | |
1225 inFunction.call(this, p); | |
1226 } | |
1227 if (inEvent.type === 'touchend' || inEvent._cancel) { | |
1228 this.cleanUpPointer(p); | |
1229 } | |
1230 } | |
1231 }, | |
1232 // For single axis scrollers, determines whether the element should emit | |
1233 // pointer events or behave as a scroller | |
1234 shouldScroll: function(inEvent) { | |
1235 if (this.firstXY) { | |
1236 var ret; | |
1237 var touchAction = scope.targetFinding.findTouchAction(inEvent); | |
1238 var scrollAxis = this.touchActionToScrollType(touchAction); | |
1239 if (scrollAxis === 'none') { | |
1240 // this element is a touch-action: none, should never scroll | |
1241 ret = false; | |
1242 } else if (scrollAxis === 'XY') { | |
1243 // this element should always scroll | |
1244 ret = true; | |
1245 } else { | |
1246 var t = inEvent.changedTouches[0]; | |
1247 // check the intended scroll axis, and other axis | |
1248 var a = scrollAxis; | |
1249 var oa = scrollAxis === 'Y' ? 'X' : 'Y'; | |
1250 var da = Math.abs(t['client' + a] - this.firstXY[a]); | |
1251 var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); | |
1252 // if delta in the scroll axis > delta other axis, scroll instead of | |
1253 // making events | |
1254 ret = da >= doa; | |
1255 } | |
1256 return ret; | |
1257 } | |
1258 }, | |
1259 findTouch: function(inTL, inId) { | |
1260 for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { | |
1261 if (t.identifier === inId) { | |
1262 return true; | |
1263 } | |
1264 } | |
1265 }, | |
1266 // In some instances, a touchstart can happen without a touchend. This | |
1267 // leaves the pointermap in a broken state. | |
1268 // Therefore, on every touchstart, we remove the touches that did not fire a | |
1269 // touchend event. | |
1270 // To keep state globally consistent, we fire a | |
1271 // pointercancel for this "abandoned" touch | |
1272 vacuumTouches: function(inEvent) { | |
1273 var tl = inEvent.touches; | |
1274 // pointermap.pointers() should be < tl.length here, as the touchstart has
not | |
1275 // been processed yet. | |
1276 if (pointermap.pointers() >= tl.length) { | |
1277 var d = []; | |
1278 pointermap.forEach(function(value, key) { | |
1279 // Never remove pointerId == 1, which is mouse. | |
1280 // Touch identifiers are 2 smaller than their pointerId, which is the | |
1281 // index in pointermap. | |
1282 if (key !== 1 && !this.findTouch(tl, key - 2)) { | |
1283 var p = value; | |
1284 d.push(p); | |
1285 } | |
1286 }, this); | |
1287 d.forEach(function(p) { | |
1288 this.cancel(p); | |
1289 pointermap.delete(p.pointerId); | |
1290 }, this); | |
1291 } | |
1292 }, | |
1293 touchstart: function(inEvent) { | |
1294 this.vacuumTouches(inEvent); | |
1295 this.setPrimaryTouch(inEvent.changedTouches[0]); | |
1296 this.dedupSynthMouse(inEvent); | |
1297 if (!this.scrolling) { | |
1298 this.clickCount++; | |
1299 this.processTouches(inEvent, this.down); | |
1300 } | |
1301 }, | |
1302 down: function(inPointer) { | |
1303 dispatcher.down(inPointer); | |
1304 }, | |
1305 touchmove: function(inEvent) { | |
1306 if (HAS_TOUCH_ACTION) { | |
1307 // touchevent.cancelable == false is sent when the page is scrolling und
er native Touch Action in Chrome 36 | |
1308 // https://groups.google.com/a/chromium.org/d/msg/input-dev/wHnyukcYBcA/
b9kmtwM1jJQJ | |
1309 if (inEvent.cancelable) { | |
1310 this.processTouches(inEvent, this.move); | |
1311 } | |
1312 } else { | |
1313 if (!this.scrolling) { | |
1314 if (this.scrolling === null && this.shouldScroll(inEvent)) { | |
1315 this.scrolling = true; | |
1316 } else { | |
1317 this.scrolling = false; | |
1318 inEvent.preventDefault(); | |
1319 this.processTouches(inEvent, this.move); | |
1320 } | |
1321 } else if (this.firstXY) { | |
1322 var t = inEvent.changedTouches[0]; | |
1323 var dx = t.clientX - this.firstXY.X; | |
1324 var dy = t.clientY - this.firstXY.Y; | |
1325 var dd = Math.sqrt(dx * dx + dy * dy); | |
1326 if (dd >= HYSTERESIS) { | |
1327 this.touchcancel(inEvent); | |
1328 this.scrolling = true; | |
1329 this.firstXY = null; | |
1330 } | |
1331 } | |
1332 } | |
1333 }, | |
1334 move: function(inPointer) { | |
1335 dispatcher.move(inPointer); | |
1336 }, | |
1337 touchend: function(inEvent) { | |
1338 this.dedupSynthMouse(inEvent); | |
1339 this.processTouches(inEvent, this.up); | |
1340 }, | |
1341 up: function(inPointer) { | |
1342 inPointer.relatedTarget = scope.findTarget(inPointer); | |
1343 dispatcher.up(inPointer); | |
1344 }, | |
1345 cancel: function(inPointer) { | |
1346 dispatcher.cancel(inPointer); | |
1347 }, | |
1348 touchcancel: function(inEvent) { | |
1349 inEvent._cancel = true; | |
1350 this.processTouches(inEvent, this.cancel); | |
1351 }, | |
1352 cleanUpPointer: function(inPointer) { | |
1353 pointermap['delete'](inPointer.pointerId); | |
1354 this.removePrimaryPointer(inPointer); | |
1355 }, | |
1356 // prevent synth mouse events from creating pointer events | |
1357 dedupSynthMouse: function(inEvent) { | |
1358 var lts = scope.mouseEvents.lastTouches; | |
1359 var t = inEvent.changedTouches[0]; | |
1360 // only the primary finger will synth mouse events | |
1361 if (this.isPrimaryTouch(t)) { | |
1362 // remember x/y of last touch | |
1363 var lt = {x: t.clientX, y: t.clientY}; | |
1364 lts.push(lt); | |
1365 var fn = (function(lts, lt){ | |
1366 var i = lts.indexOf(lt); | |
1367 if (i > -1) { | |
1368 lts.splice(i, 1); | |
1369 } | |
1370 }).bind(null, lts, lt); | |
1371 setTimeout(fn, DEDUP_TIMEOUT); | |
1372 } | |
1373 } | |
1374 }; | |
1375 | |
1376 // prevent "ghost clicks" that come from elements that were removed in a touch
handler | |
1377 var STOP_PROP_FN = Event.prototype.stopImmediatePropagation || Event.prototype
.stopPropagation; | |
1378 document.addEventListener('click', function(ev) { | |
1379 var x = ev.clientX, y = ev.clientY; | |
1380 // check if a click is within DEDUP_DIST px radius of the touchstart | |
1381 var closeTo = function(touch) { | |
1382 var dx = Math.abs(x - touch.x), dy = Math.abs(y - touch.y); | |
1383 return (dx <= DEDUP_DIST && dy <= DEDUP_DIST); | |
1384 }; | |
1385 // if click coordinates are close to touch coordinates, assume the click cam
e from a touch | |
1386 var wasTouched = scope.mouseEvents.lastTouches.some(closeTo); | |
1387 // if the click came from touch, and the touchstart target is not in the pat
h of the click event, | |
1388 // then the touchstart target was probably removed, and the click should be
"busted" | |
1389 var path = scope.targetFinding.path(ev); | |
1390 if (wasTouched) { | |
1391 for (var i = 0; i < path.length; i++) { | |
1392 if (path[i] === touchEvents.firstTarget) { | |
1393 return; | |
1394 } | |
1395 } | |
1396 ev.preventDefault(); | |
1397 STOP_PROP_FN.call(ev); | |
1398 } | |
1399 }, true); | |
1400 | |
1401 scope.touchEvents = touchEvents; | |
1402 })(window.PolymerGestures); | |
1403 | |
1404 (function(scope) { | |
1405 var dispatcher = scope.dispatcher; | |
1406 var pointermap = dispatcher.pointermap; | |
1407 var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MS
POINTER_TYPE_MOUSE === 'number'; | |
1408 var msEvents = { | |
1409 events: [ | |
1410 'MSPointerDown', | |
1411 'MSPointerMove', | |
1412 'MSPointerUp', | |
1413 'MSPointerCancel', | |
1414 ], | |
1415 register: function(target) { | |
1416 dispatcher.listen(target, this.events); | |
1417 }, | |
1418 unregister: function(target) { | |
1419 if (target.nodeType === Node.DOCUMENT_NODE) { | |
1420 return; | |
1421 } | |
1422 dispatcher.unlisten(target, this.events); | |
1423 }, | |
1424 POINTER_TYPES: [ | |
1425 '', | |
1426 'unavailable', | |
1427 'touch', | |
1428 'pen', | |
1429 'mouse' | |
1430 ], | |
1431 prepareEvent: function(inEvent) { | |
1432 var e = inEvent; | |
1433 e = dispatcher.cloneEvent(inEvent); | |
1434 if (HAS_BITMAP_TYPE) { | |
1435 e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; | |
1436 } | |
1437 e._source = 'ms'; | |
1438 return e; | |
1439 }, | |
1440 cleanup: function(id) { | |
1441 pointermap['delete'](id); | |
1442 }, | |
1443 MSPointerDown: function(inEvent) { | |
1444 var e = this.prepareEvent(inEvent); | |
1445 e.target = scope.findTarget(inEvent); | |
1446 pointermap.set(inEvent.pointerId, e.target); | |
1447 dispatcher.down(e); | |
1448 }, | |
1449 MSPointerMove: function(inEvent) { | |
1450 var target = pointermap.get(inEvent.pointerId); | |
1451 if (target) { | |
1452 var e = this.prepareEvent(inEvent); | |
1453 e.target = target; | |
1454 dispatcher.move(e); | |
1455 } | |
1456 }, | |
1457 MSPointerUp: function(inEvent) { | |
1458 var e = this.prepareEvent(inEvent); | |
1459 e.relatedTarget = scope.findTarget(inEvent); | |
1460 e.target = pointermap.get(e.pointerId); | |
1461 dispatcher.up(e); | |
1462 this.cleanup(inEvent.pointerId); | |
1463 }, | |
1464 MSPointerCancel: function(inEvent) { | |
1465 var e = this.prepareEvent(inEvent); | |
1466 e.relatedTarget = scope.findTarget(inEvent); | |
1467 e.target = pointermap.get(e.pointerId); | |
1468 dispatcher.cancel(e); | |
1469 this.cleanup(inEvent.pointerId); | |
1470 } | |
1471 }; | |
1472 | |
1473 scope.msEvents = msEvents; | |
1474 })(window.PolymerGestures); | |
1475 | |
1476 (function(scope) { | |
1477 var dispatcher = scope.dispatcher; | |
1478 var pointermap = dispatcher.pointermap; | |
1479 var pointerEvents = { | |
1480 events: [ | |
1481 'pointerdown', | |
1482 'pointermove', | |
1483 'pointerup', | |
1484 'pointercancel' | |
1485 ], | |
1486 prepareEvent: function(inEvent) { | |
1487 var e = dispatcher.cloneEvent(inEvent); | |
1488 e._source = 'pointer'; | |
1489 return e; | |
1490 }, | |
1491 register: function(target) { | |
1492 dispatcher.listen(target, this.events); | |
1493 }, | |
1494 unregister: function(target) { | |
1495 if (target.nodeType === Node.DOCUMENT_NODE) { | |
1496 return; | |
1497 } | |
1498 dispatcher.unlisten(target, this.events); | |
1499 }, | |
1500 cleanup: function(id) { | |
1501 pointermap['delete'](id); | |
1502 }, | |
1503 pointerdown: function(inEvent) { | |
1504 var e = this.prepareEvent(inEvent); | |
1505 e.target = scope.findTarget(inEvent); | |
1506 pointermap.set(e.pointerId, e.target); | |
1507 dispatcher.down(e); | |
1508 }, | |
1509 pointermove: function(inEvent) { | |
1510 var target = pointermap.get(inEvent.pointerId); | |
1511 if (target) { | |
1512 var e = this.prepareEvent(inEvent); | |
1513 e.target = target; | |
1514 dispatcher.move(e); | |
1515 } | |
1516 }, | |
1517 pointerup: function(inEvent) { | |
1518 var e = this.prepareEvent(inEvent); | |
1519 e.relatedTarget = scope.findTarget(inEvent); | |
1520 e.target = pointermap.get(e.pointerId); | |
1521 dispatcher.up(e); | |
1522 this.cleanup(inEvent.pointerId); | |
1523 }, | |
1524 pointercancel: function(inEvent) { | |
1525 var e = this.prepareEvent(inEvent); | |
1526 e.relatedTarget = scope.findTarget(inEvent); | |
1527 e.target = pointermap.get(e.pointerId); | |
1528 dispatcher.cancel(e); | |
1529 this.cleanup(inEvent.pointerId); | |
1530 } | |
1531 }; | |
1532 | |
1533 scope.pointerEvents = pointerEvents; | |
1534 })(window.PolymerGestures); | |
1535 | |
1536 /** | |
1537 * This module contains the handlers for native platform events. | |
1538 * From here, the dispatcher is called to create unified pointer events. | |
1539 * Included are touch events (v1), mouse events, and MSPointerEvents. | |
1540 */ | |
1541 (function(scope) { | |
1542 | |
1543 var dispatcher = scope.dispatcher; | |
1544 var nav = window.navigator; | |
1545 | |
1546 if (window.PointerEvent) { | |
1547 dispatcher.registerSource('pointer', scope.pointerEvents); | |
1548 } else if (nav.msPointerEnabled) { | |
1549 dispatcher.registerSource('ms', scope.msEvents); | |
1550 } else { | |
1551 dispatcher.registerSource('mouse', scope.mouseEvents); | |
1552 if (window.ontouchstart !== undefined) { | |
1553 dispatcher.registerSource('touch', scope.touchEvents); | |
1554 } | |
1555 } | |
1556 | |
1557 // Work around iOS bugs https://bugs.webkit.org/show_bug.cgi?id=135628 and htt
ps://bugs.webkit.org/show_bug.cgi?id=136506 | |
1558 var ua = navigator.userAgent; | |
1559 var IS_IOS = ua.match(/iPad|iPhone|iPod/) && 'ontouchstart' in window; | |
1560 | |
1561 dispatcher.IS_IOS = IS_IOS; | |
1562 scope.touchEvents.IS_IOS = IS_IOS; | |
1563 | |
1564 dispatcher.register(document, true); | |
1565 })(window.PolymerGestures); | |
1566 | |
1567 /** | |
1568 * This event denotes the beginning of a series of tracking events. | |
1569 * | |
1570 * @module PointerGestures | |
1571 * @submodule Events | |
1572 * @class trackstart | |
1573 */ | |
1574 /** | |
1575 * Pixels moved in the x direction since trackstart. | |
1576 * @type Number | |
1577 * @property dx | |
1578 */ | |
1579 /** | |
1580 * Pixes moved in the y direction since trackstart. | |
1581 * @type Number | |
1582 * @property dy | |
1583 */ | |
1584 /** | |
1585 * Pixels moved in the x direction since the last track. | |
1586 * @type Number | |
1587 * @property ddx | |
1588 */ | |
1589 /** | |
1590 * Pixles moved in the y direction since the last track. | |
1591 * @type Number | |
1592 * @property ddy | |
1593 */ | |
1594 /** | |
1595 * The clientX position of the track gesture. | |
1596 * @type Number | |
1597 * @property clientX | |
1598 */ | |
1599 /** | |
1600 * The clientY position of the track gesture. | |
1601 * @type Number | |
1602 * @property clientY | |
1603 */ | |
1604 /** | |
1605 * The pageX position of the track gesture. | |
1606 * @type Number | |
1607 * @property pageX | |
1608 */ | |
1609 /** | |
1610 * The pageY position of the track gesture. | |
1611 * @type Number | |
1612 * @property pageY | |
1613 */ | |
1614 /** | |
1615 * The screenX position of the track gesture. | |
1616 * @type Number | |
1617 * @property screenX | |
1618 */ | |
1619 /** | |
1620 * The screenY position of the track gesture. | |
1621 * @type Number | |
1622 * @property screenY | |
1623 */ | |
1624 /** | |
1625 * The last x axis direction of the pointer. | |
1626 * @type Number | |
1627 * @property xDirection | |
1628 */ | |
1629 /** | |
1630 * The last y axis direction of the pointer. | |
1631 * @type Number | |
1632 * @property yDirection | |
1633 */ | |
1634 /** | |
1635 * A shared object between all tracking events. | |
1636 * @type Object | |
1637 * @property trackInfo | |
1638 */ | |
1639 /** | |
1640 * The element currently under the pointer. | |
1641 * @type Element | |
1642 * @property relatedTarget | |
1643 */ | |
1644 /** | |
1645 * The type of pointer that make the track gesture. | |
1646 * @type String | |
1647 * @property pointerType | |
1648 */ | |
1649 /** | |
1650 * | |
1651 * This event fires for all pointer movement being tracked. | |
1652 * | |
1653 * @class track | |
1654 * @extends trackstart | |
1655 */ | |
1656 /** | |
1657 * This event fires when the pointer is no longer being tracked. | |
1658 * | |
1659 * @class trackend | |
1660 * @extends trackstart | |
1661 */ | |
1662 | |
1663 (function(scope) { | |
1664 var dispatcher = scope.dispatcher; | |
1665 var eventFactory = scope.eventFactory; | |
1666 var pointermap = new scope.PointerMap(); | |
1667 var track = { | |
1668 events: [ | |
1669 'down', | |
1670 'move', | |
1671 'up', | |
1672 ], | |
1673 exposes: [ | |
1674 'trackstart', | |
1675 'track', | |
1676 'trackx', | |
1677 'tracky', | |
1678 'trackend' | |
1679 ], | |
1680 defaultActions: { | |
1681 'track': 'none', | |
1682 'trackx': 'pan-y', | |
1683 'tracky': 'pan-x' | |
1684 }, | |
1685 WIGGLE_THRESHOLD: 4, | |
1686 clampDir: function(inDelta) { | |
1687 return inDelta > 0 ? 1 : -1; | |
1688 }, | |
1689 calcPositionDelta: function(inA, inB) { | |
1690 var x = 0, y = 0; | |
1691 if (inA && inB) { | |
1692 x = inB.pageX - inA.pageX; | |
1693 y = inB.pageY - inA.pageY; | |
1694 } | |
1695 return {x: x, y: y}; | |
1696 }, | |
1697 fireTrack: function(inType, inEvent, inTrackingData) { | |
1698 var t = inTrackingData; | |
1699 var d = this.calcPositionDelta(t.downEvent, inEvent); | |
1700 var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); | |
1701 if (dd.x) { | |
1702 t.xDirection = this.clampDir(dd.x); | |
1703 } else if (inType === 'trackx') { | |
1704 return; | |
1705 } | |
1706 if (dd.y) { | |
1707 t.yDirection = this.clampDir(dd.y); | |
1708 } else if (inType === 'tracky') { | |
1709 return; | |
1710 } | |
1711 var gestureProto = { | |
1712 bubbles: true, | |
1713 cancelable: true, | |
1714 trackInfo: t.trackInfo, | |
1715 relatedTarget: inEvent.relatedTarget, | |
1716 pointerType: inEvent.pointerType, | |
1717 pointerId: inEvent.pointerId, | |
1718 _source: 'track' | |
1719 }; | |
1720 if (inType !== 'tracky') { | |
1721 gestureProto.x = inEvent.x; | |
1722 gestureProto.dx = d.x; | |
1723 gestureProto.ddx = dd.x; | |
1724 gestureProto.clientX = inEvent.clientX; | |
1725 gestureProto.pageX = inEvent.pageX; | |
1726 gestureProto.screenX = inEvent.screenX; | |
1727 gestureProto.xDirection = t.xDirection; | |
1728 } | |
1729 if (inType !== 'trackx') { | |
1730 gestureProto.dy = d.y; | |
1731 gestureProto.ddy = dd.y; | |
1732 gestureProto.y = inEvent.y; | |
1733 gestureProto.clientY = inEvent.clientY; | |
1734 gestureProto.pageY = inEvent.pageY; | |
1735 gestureProto.screenY = inEvent.screenY; | |
1736 gestureProto.yDirection = t.yDirection; | |
1737 } | |
1738 var e = eventFactory.makeGestureEvent(inType, gestureProto); | |
1739 t.downTarget.dispatchEvent(e); | |
1740 }, | |
1741 down: function(inEvent) { | |
1742 if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.butto
ns === 1 : true)) { | |
1743 var p = { | |
1744 downEvent: inEvent, | |
1745 downTarget: inEvent.target, | |
1746 trackInfo: {}, | |
1747 lastMoveEvent: null, | |
1748 xDirection: 0, | |
1749 yDirection: 0, | |
1750 tracking: false | |
1751 }; | |
1752 pointermap.set(inEvent.pointerId, p); | |
1753 } | |
1754 }, | |
1755 move: function(inEvent) { | |
1756 var p = pointermap.get(inEvent.pointerId); | |
1757 if (p) { | |
1758 if (!p.tracking) { | |
1759 var d = this.calcPositionDelta(p.downEvent, inEvent); | |
1760 var move = d.x * d.x + d.y * d.y; | |
1761 // start tracking only if finger moves more than WIGGLE_THRESHOLD | |
1762 if (move > this.WIGGLE_THRESHOLD) { | |
1763 p.tracking = true; | |
1764 p.lastMoveEvent = p.downEvent; | |
1765 this.fireTrack('trackstart', inEvent, p); | |
1766 } | |
1767 } | |
1768 if (p.tracking) { | |
1769 this.fireTrack('track', inEvent, p); | |
1770 this.fireTrack('trackx', inEvent, p); | |
1771 this.fireTrack('tracky', inEvent, p); | |
1772 } | |
1773 p.lastMoveEvent = inEvent; | |
1774 } | |
1775 }, | |
1776 up: function(inEvent) { | |
1777 var p = pointermap.get(inEvent.pointerId); | |
1778 if (p) { | |
1779 if (p.tracking) { | |
1780 this.fireTrack('trackend', inEvent, p); | |
1781 } | |
1782 pointermap.delete(inEvent.pointerId); | |
1783 } | |
1784 } | |
1785 }; | |
1786 dispatcher.registerGesture('track', track); | |
1787 })(window.PolymerGestures); | |
1788 | |
1789 /** | |
1790 * This event is fired when a pointer is held down for 200ms. | |
1791 * | |
1792 * @module PointerGestures | |
1793 * @submodule Events | |
1794 * @class hold | |
1795 */ | |
1796 /** | |
1797 * Type of pointer that made the holding event. | |
1798 * @type String | |
1799 * @property pointerType | |
1800 */ | |
1801 /** | |
1802 * Screen X axis position of the held pointer | |
1803 * @type Number | |
1804 * @property clientX | |
1805 */ | |
1806 /** | |
1807 * Screen Y axis position of the held pointer | |
1808 * @type Number | |
1809 * @property clientY | |
1810 */ | |
1811 /** | |
1812 * Type of pointer that made the holding event. | |
1813 * @type String | |
1814 * @property pointerType | |
1815 */ | |
1816 /** | |
1817 * This event is fired every 200ms while a pointer is held down. | |
1818 * | |
1819 * @class holdpulse | |
1820 * @extends hold | |
1821 */ | |
1822 /** | |
1823 * Milliseconds pointer has been held down. | |
1824 * @type Number | |
1825 * @property holdTime | |
1826 */ | |
1827 /** | |
1828 * This event is fired when a held pointer is released or moved. | |
1829 * | |
1830 * @class release | |
1831 */ | |
1832 | |
1833 (function(scope) { | |
1834 var dispatcher = scope.dispatcher; | |
1835 var eventFactory = scope.eventFactory; | |
1836 var hold = { | |
1837 // wait at least HOLD_DELAY ms between hold and pulse events | |
1838 HOLD_DELAY: 200, | |
1839 // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold | |
1840 WIGGLE_THRESHOLD: 16, | |
1841 events: [ | |
1842 'down', | |
1843 'move', | |
1844 'up', | |
1845 ], | |
1846 exposes: [ | |
1847 'hold', | |
1848 'holdpulse', | |
1849 'release' | |
1850 ], | |
1851 heldPointer: null, | |
1852 holdJob: null, | |
1853 pulse: function() { | |
1854 var hold = Date.now() - this.heldPointer.timeStamp; | |
1855 var type = this.held ? 'holdpulse' : 'hold'; | |
1856 this.fireHold(type, hold); | |
1857 this.held = true; | |
1858 }, | |
1859 cancel: function() { | |
1860 clearInterval(this.holdJob); | |
1861 if (this.held) { | |
1862 this.fireHold('release'); | |
1863 } | |
1864 this.held = false; | |
1865 this.heldPointer = null; | |
1866 this.target = null; | |
1867 this.holdJob = null; | |
1868 }, | |
1869 down: function(inEvent) { | |
1870 if (inEvent.isPrimary && !this.heldPointer) { | |
1871 this.heldPointer = inEvent; | |
1872 this.target = inEvent.target; | |
1873 this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); | |
1874 } | |
1875 }, | |
1876 up: function(inEvent) { | |
1877 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ | |
1878 this.cancel(); | |
1879 } | |
1880 }, | |
1881 move: function(inEvent) { | |
1882 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ | |
1883 var x = inEvent.clientX - this.heldPointer.clientX; | |
1884 var y = inEvent.clientY - this.heldPointer.clientY; | |
1885 if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { | |
1886 this.cancel(); | |
1887 } | |
1888 } | |
1889 }, | |
1890 fireHold: function(inType, inHoldTime) { | |
1891 var p = { | |
1892 bubbles: true, | |
1893 cancelable: true, | |
1894 pointerType: this.heldPointer.pointerType, | |
1895 pointerId: this.heldPointer.pointerId, | |
1896 x: this.heldPointer.clientX, | |
1897 y: this.heldPointer.clientY, | |
1898 _source: 'hold' | |
1899 }; | |
1900 if (inHoldTime) { | |
1901 p.holdTime = inHoldTime; | |
1902 } | |
1903 var e = eventFactory.makeGestureEvent(inType, p); | |
1904 this.target.dispatchEvent(e); | |
1905 } | |
1906 }; | |
1907 dispatcher.registerGesture('hold', hold); | |
1908 })(window.PolymerGestures); | |
1909 | |
1910 /** | |
1911 * This event is fired when a pointer quickly goes down and up, and is used to | |
1912 * denote activation. | |
1913 * | |
1914 * Any gesture event can prevent the tap event from being created by calling | |
1915 * `event.preventTap`. | |
1916 * | |
1917 * Any pointer event can prevent the tap by setting the `tapPrevented` property | |
1918 * on itself. | |
1919 * | |
1920 * @module PointerGestures | |
1921 * @submodule Events | |
1922 * @class tap | |
1923 */ | |
1924 /** | |
1925 * X axis position of the tap. | |
1926 * @property x | |
1927 * @type Number | |
1928 */ | |
1929 /** | |
1930 * Y axis position of the tap. | |
1931 * @property y | |
1932 * @type Number | |
1933 */ | |
1934 /** | |
1935 * Type of the pointer that made the tap. | |
1936 * @property pointerType | |
1937 * @type String | |
1938 */ | |
1939 (function(scope) { | |
1940 var dispatcher = scope.dispatcher; | |
1941 var eventFactory = scope.eventFactory; | |
1942 var pointermap = new scope.PointerMap(); | |
1943 var tap = { | |
1944 events: [ | |
1945 'down', | |
1946 'up' | |
1947 ], | |
1948 exposes: [ | |
1949 'tap' | |
1950 ], | |
1951 down: function(inEvent) { | |
1952 if (inEvent.isPrimary && !inEvent.tapPrevented) { | |
1953 pointermap.set(inEvent.pointerId, { | |
1954 target: inEvent.target, | |
1955 buttons: inEvent.buttons, | |
1956 x: inEvent.clientX, | |
1957 y: inEvent.clientY | |
1958 }); | |
1959 } | |
1960 }, | |
1961 shouldTap: function(e, downState) { | |
1962 var tap = true; | |
1963 if (e.pointerType === 'mouse') { | |
1964 // only allow left click to tap for mouse | |
1965 tap = (e.buttons ^ 1) && (downState.buttons & 1); | |
1966 } | |
1967 return tap && !e.tapPrevented; | |
1968 }, | |
1969 up: function(inEvent) { | |
1970 var start = pointermap.get(inEvent.pointerId); | |
1971 if (start && this.shouldTap(inEvent, start)) { | |
1972 // up.relatedTarget is target currently under finger | |
1973 var t = scope.targetFinding.LCA(start.target, inEvent.relatedTarget); | |
1974 if (t) { | |
1975 var e = eventFactory.makeGestureEvent('tap', { | |
1976 bubbles: true, | |
1977 cancelable: true, | |
1978 x: inEvent.clientX, | |
1979 y: inEvent.clientY, | |
1980 detail: inEvent.detail, | |
1981 pointerType: inEvent.pointerType, | |
1982 pointerId: inEvent.pointerId, | |
1983 altKey: inEvent.altKey, | |
1984 ctrlKey: inEvent.ctrlKey, | |
1985 metaKey: inEvent.metaKey, | |
1986 shiftKey: inEvent.shiftKey, | |
1987 _source: 'tap' | |
1988 }); | |
1989 t.dispatchEvent(e); | |
1990 } | |
1991 } | |
1992 pointermap.delete(inEvent.pointerId); | |
1993 } | |
1994 }; | |
1995 // patch eventFactory to remove id from tap's pointermap for preventTap calls | |
1996 eventFactory.preventTap = function(e) { | |
1997 return function() { | |
1998 e.tapPrevented = true; | |
1999 pointermap.delete(e.pointerId); | |
2000 }; | |
2001 }; | |
2002 dispatcher.registerGesture('tap', tap); | |
2003 })(window.PolymerGestures); | |
2004 | |
2005 /* | |
2006 * Basic strategy: find the farthest apart points, use as diameter of circle | |
2007 * react to size change and rotation of the chord | |
2008 */ | |
2009 | |
2010 /** | |
2011 * @module pointer-gestures | |
2012 * @submodule Events | |
2013 * @class pinch | |
2014 */ | |
2015 /** | |
2016 * Scale of the pinch zoom gesture | |
2017 * @property scale | |
2018 * @type Number | |
2019 */ | |
2020 /** | |
2021 * Center X position of pointers causing pinch | |
2022 * @property centerX | |
2023 * @type Number | |
2024 */ | |
2025 /** | |
2026 * Center Y position of pointers causing pinch | |
2027 * @property centerY | |
2028 * @type Number | |
2029 */ | |
2030 | |
2031 /** | |
2032 * @module pointer-gestures | |
2033 * @submodule Events | |
2034 * @class rotate | |
2035 */ | |
2036 /** | |
2037 * Angle (in degrees) of rotation. Measured from starting positions of pointers. | |
2038 * @property angle | |
2039 * @type Number | |
2040 */ | |
2041 /** | |
2042 * Center X position of pointers causing rotation | |
2043 * @property centerX | |
2044 * @type Number | |
2045 */ | |
2046 /** | |
2047 * Center Y position of pointers causing rotation | |
2048 * @property centerY | |
2049 * @type Number | |
2050 */ | |
2051 (function(scope) { | |
2052 var dispatcher = scope.dispatcher; | |
2053 var eventFactory = scope.eventFactory; | |
2054 var pointermap = new scope.PointerMap(); | |
2055 var RAD_TO_DEG = 180 / Math.PI; | |
2056 var pinch = { | |
2057 events: [ | |
2058 'down', | |
2059 'up', | |
2060 'move', | |
2061 'cancel' | |
2062 ], | |
2063 exposes: [ | |
2064 'pinchstart', | |
2065 'pinch', | |
2066 'pinchend', | |
2067 'rotate' | |
2068 ], | |
2069 defaultActions: { | |
2070 'pinch': 'none', | |
2071 'rotate': 'none' | |
2072 }, | |
2073 reference: {}, | |
2074 down: function(inEvent) { | |
2075 pointermap.set(inEvent.pointerId, inEvent); | |
2076 if (pointermap.pointers() == 2) { | |
2077 var points = this.calcChord(); | |
2078 var angle = this.calcAngle(points); | |
2079 this.reference = { | |
2080 angle: angle, | |
2081 diameter: points.diameter, | |
2082 target: scope.targetFinding.LCA(points.a.target, points.b.target) | |
2083 }; | |
2084 | |
2085 this.firePinch('pinchstart', points.diameter, points); | |
2086 } | |
2087 }, | |
2088 up: function(inEvent) { | |
2089 var p = pointermap.get(inEvent.pointerId); | |
2090 var num = pointermap.pointers(); | |
2091 if (p) { | |
2092 if (num === 2) { | |
2093 // fire 'pinchend' before deleting pointer | |
2094 var points = this.calcChord(); | |
2095 this.firePinch('pinchend', points.diameter, points); | |
2096 } | |
2097 pointermap.delete(inEvent.pointerId); | |
2098 } | |
2099 }, | |
2100 move: function(inEvent) { | |
2101 if (pointermap.has(inEvent.pointerId)) { | |
2102 pointermap.set(inEvent.pointerId, inEvent); | |
2103 if (pointermap.pointers() > 1) { | |
2104 this.calcPinchRotate(); | |
2105 } | |
2106 } | |
2107 }, | |
2108 cancel: function(inEvent) { | |
2109 this.up(inEvent); | |
2110 }, | |
2111 firePinch: function(type, diameter, points) { | |
2112 var zoom = diameter / this.reference.diameter; | |
2113 var e = eventFactory.makeGestureEvent(type, { | |
2114 bubbles: true, | |
2115 cancelable: true, | |
2116 scale: zoom, | |
2117 centerX: points.center.x, | |
2118 centerY: points.center.y, | |
2119 _source: 'pinch' | |
2120 }); | |
2121 this.reference.target.dispatchEvent(e); | |
2122 }, | |
2123 fireRotate: function(angle, points) { | |
2124 var diff = Math.round((angle - this.reference.angle) % 360); | |
2125 var e = eventFactory.makeGestureEvent('rotate', { | |
2126 bubbles: true, | |
2127 cancelable: true, | |
2128 angle: diff, | |
2129 centerX: points.center.x, | |
2130 centerY: points.center.y, | |
2131 _source: 'pinch' | |
2132 }); | |
2133 this.reference.target.dispatchEvent(e); | |
2134 }, | |
2135 calcPinchRotate: function() { | |
2136 var points = this.calcChord(); | |
2137 var diameter = points.diameter; | |
2138 var angle = this.calcAngle(points); | |
2139 if (diameter != this.reference.diameter) { | |
2140 this.firePinch('pinch', diameter, points); | |
2141 } | |
2142 if (angle != this.reference.angle) { | |
2143 this.fireRotate(angle, points); | |
2144 } | |
2145 }, | |
2146 calcChord: function() { | |
2147 var pointers = []; | |
2148 pointermap.forEach(function(p) { | |
2149 pointers.push(p); | |
2150 }); | |
2151 var dist = 0; | |
2152 // start with at least two pointers | |
2153 var points = {a: pointers[0], b: pointers[1]}; | |
2154 var x, y, d; | |
2155 for (var i = 0; i < pointers.length; i++) { | |
2156 var a = pointers[i]; | |
2157 for (var j = i + 1; j < pointers.length; j++) { | |
2158 var b = pointers[j]; | |
2159 x = Math.abs(a.clientX - b.clientX); | |
2160 y = Math.abs(a.clientY - b.clientY); | |
2161 d = x + y; | |
2162 if (d > dist) { | |
2163 dist = d; | |
2164 points = {a: a, b: b}; | |
2165 } | |
2166 } | |
2167 } | |
2168 x = Math.abs(points.a.clientX + points.b.clientX) / 2; | |
2169 y = Math.abs(points.a.clientY + points.b.clientY) / 2; | |
2170 points.center = { x: x, y: y }; | |
2171 points.diameter = dist; | |
2172 return points; | |
2173 }, | |
2174 calcAngle: function(points) { | |
2175 var x = points.a.clientX - points.b.clientX; | |
2176 var y = points.a.clientY - points.b.clientY; | |
2177 return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; | |
2178 } | |
2179 }; | |
2180 dispatcher.registerGesture('pinch', pinch); | |
2181 })(window.PolymerGestures); | |
2182 | |
2183 (function (global) { | |
2184 'use strict'; | |
2185 | |
2186 var Token, | |
2187 TokenName, | |
2188 Syntax, | |
2189 Messages, | |
2190 source, | |
2191 index, | |
2192 length, | |
2193 delegate, | |
2194 lookahead, | |
2195 state; | |
2196 | |
2197 Token = { | |
2198 BooleanLiteral: 1, | |
2199 EOF: 2, | |
2200 Identifier: 3, | |
2201 Keyword: 4, | |
2202 NullLiteral: 5, | |
2203 NumericLiteral: 6, | |
2204 Punctuator: 7, | |
2205 StringLiteral: 8 | |
2206 }; | |
2207 | |
2208 TokenName = {}; | |
2209 TokenName[Token.BooleanLiteral] = 'Boolean'; | |
2210 TokenName[Token.EOF] = '<end>'; | |
2211 TokenName[Token.Identifier] = 'Identifier'; | |
2212 TokenName[Token.Keyword] = 'Keyword'; | |
2213 TokenName[Token.NullLiteral] = 'Null'; | |
2214 TokenName[Token.NumericLiteral] = 'Numeric'; | |
2215 TokenName[Token.Punctuator] = 'Punctuator'; | |
2216 TokenName[Token.StringLiteral] = 'String'; | |
2217 | |
2218 Syntax = { | |
2219 ArrayExpression: 'ArrayExpression', | |
2220 BinaryExpression: 'BinaryExpression', | |
2221 CallExpression: 'CallExpression', | |
2222 ConditionalExpression: 'ConditionalExpression', | |
2223 EmptyStatement: 'EmptyStatement', | |
2224 ExpressionStatement: 'ExpressionStatement', | |
2225 Identifier: 'Identifier', | |
2226 Literal: 'Literal', | |
2227 LabeledStatement: 'LabeledStatement', | |
2228 LogicalExpression: 'LogicalExpression', | |
2229 MemberExpression: 'MemberExpression', | |
2230 ObjectExpression: 'ObjectExpression', | |
2231 Program: 'Program', | |
2232 Property: 'Property', | |
2233 ThisExpression: 'ThisExpression', | |
2234 UnaryExpression: 'UnaryExpression' | |
2235 }; | |
2236 | |
2237 // Error messages should be identical to V8. | |
2238 Messages = { | |
2239 UnexpectedToken: 'Unexpected token %0', | |
2240 UnknownLabel: 'Undefined label \'%0\'', | |
2241 Redeclaration: '%0 \'%1\' has already been declared' | |
2242 }; | |
2243 | |
2244 // Ensure the condition is true, otherwise throw an error. | |
2245 // This is only to have a better contract semantic, i.e. another safety net | |
2246 // to catch a logic error. The condition shall be fulfilled in normal case. | |
2247 // Do NOT use this to enforce a certain condition on any user input. | |
2248 | |
2249 function assert(condition, message) { | |
2250 if (!condition) { | |
2251 throw new Error('ASSERT: ' + message); | |
2252 } | |
2253 } | |
2254 | |
2255 function isDecimalDigit(ch) { | |
2256 return (ch >= 48 && ch <= 57); // 0..9 | |
2257 } | |
2258 | |
2259 | |
2260 // 7.2 White Space | |
2261 | |
2262 function isWhiteSpace(ch) { | |
2263 return (ch === 32) || // space | |
2264 (ch === 9) || // tab | |
2265 (ch === 0xB) || | |
2266 (ch === 0xC) || | |
2267 (ch === 0xA0) || | |
2268 (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); | |
2269 } | |
2270 | |
2271 // 7.3 Line Terminators | |
2272 | |
2273 function isLineTerminator(ch) { | |
2274 return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); | |
2275 } | |
2276 | |
2277 // 7.6 Identifier Names and Identifiers | |
2278 | |
2279 function isIdentifierStart(ch) { | |
2280 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) | |
2281 (ch >= 65 && ch <= 90) || // A..Z | |
2282 (ch >= 97 && ch <= 122); // a..z | |
2283 } | |
2284 | |
2285 function isIdentifierPart(ch) { | |
2286 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) | |
2287 (ch >= 65 && ch <= 90) || // A..Z | |
2288 (ch >= 97 && ch <= 122) || // a..z | |
2289 (ch >= 48 && ch <= 57); // 0..9 | |
2290 } | |
2291 | |
2292 // 7.6.1.1 Keywords | |
2293 | |
2294 function isKeyword(id) { | |
2295 return (id === 'this') | |
2296 } | |
2297 | |
2298 // 7.4 Comments | |
2299 | |
2300 function skipWhitespace() { | |
2301 while (index < length && isWhiteSpace(source.charCodeAt(index))) { | |
2302 ++index; | |
2303 } | |
2304 } | |
2305 | |
2306 function getIdentifier() { | |
2307 var start, ch; | |
2308 | |
2309 start = index++; | |
2310 while (index < length) { | |
2311 ch = source.charCodeAt(index); | |
2312 if (isIdentifierPart(ch)) { | |
2313 ++index; | |
2314 } else { | |
2315 break; | |
2316 } | |
2317 } | |
2318 | |
2319 return source.slice(start, index); | |
2320 } | |
2321 | |
2322 function scanIdentifier() { | |
2323 var start, id, type; | |
2324 | |
2325 start = index; | |
2326 | |
2327 id = getIdentifier(); | |
2328 | |
2329 // There is no keyword or literal with only one character. | |
2330 // Thus, it must be an identifier. | |
2331 if (id.length === 1) { | |
2332 type = Token.Identifier; | |
2333 } else if (isKeyword(id)) { | |
2334 type = Token.Keyword; | |
2335 } else if (id === 'null') { | |
2336 type = Token.NullLiteral; | |
2337 } else if (id === 'true' || id === 'false') { | |
2338 type = Token.BooleanLiteral; | |
2339 } else { | |
2340 type = Token.Identifier; | |
2341 } | |
2342 | |
2343 return { | |
2344 type: type, | |
2345 value: id, | |
2346 range: [start, index] | |
2347 }; | |
2348 } | |
2349 | |
2350 | |
2351 // 7.7 Punctuators | |
2352 | |
2353 function scanPunctuator() { | |
2354 var start = index, | |
2355 code = source.charCodeAt(index), | |
2356 code2, | |
2357 ch1 = source[index], | |
2358 ch2; | |
2359 | |
2360 switch (code) { | |
2361 | |
2362 // Check for most common single-character punctuators. | |
2363 case 46: // . dot | |
2364 case 40: // ( open bracket | |
2365 case 41: // ) close bracket | |
2366 case 59: // ; semicolon | |
2367 case 44: // , comma | |
2368 case 123: // { open curly brace | |
2369 case 125: // } close curly brace | |
2370 case 91: // [ | |
2371 case 93: // ] | |
2372 case 58: // : | |
2373 case 63: // ? | |
2374 ++index; | |
2375 return { | |
2376 type: Token.Punctuator, | |
2377 value: String.fromCharCode(code), | |
2378 range: [start, index] | |
2379 }; | |
2380 | |
2381 default: | |
2382 code2 = source.charCodeAt(index + 1); | |
2383 | |
2384 // '=' (char #61) marks an assignment or comparison operator. | |
2385 if (code2 === 61) { | |
2386 switch (code) { | |
2387 case 37: // % | |
2388 case 38: // & | |
2389 case 42: // *: | |
2390 case 43: // + | |
2391 case 45: // - | |
2392 case 47: // / | |
2393 case 60: // < | |
2394 case 62: // > | |
2395 case 124: // | | |
2396 index += 2; | |
2397 return { | |
2398 type: Token.Punctuator, | |
2399 value: String.fromCharCode(code) + String.fromCharCode(c
ode2), | |
2400 range: [start, index] | |
2401 }; | |
2402 | |
2403 case 33: // ! | |
2404 case 61: // = | |
2405 index += 2; | |
2406 | |
2407 // !== and === | |
2408 if (source.charCodeAt(index) === 61) { | |
2409 ++index; | |
2410 } | |
2411 return { | |
2412 type: Token.Punctuator, | |
2413 value: source.slice(start, index), | |
2414 range: [start, index] | |
2415 }; | |
2416 default: | |
2417 break; | |
2418 } | |
2419 } | |
2420 break; | |
2421 } | |
2422 | |
2423 // Peek more characters. | |
2424 | |
2425 ch2 = source[index + 1]; | |
2426 | |
2427 // Other 2-character punctuators: && || | |
2428 | |
2429 if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { | |
2430 index += 2; | |
2431 return { | |
2432 type: Token.Punctuator, | |
2433 value: ch1 + ch2, | |
2434 range: [start, index] | |
2435 }; | |
2436 } | |
2437 | |
2438 if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { | |
2439 ++index; | |
2440 return { | |
2441 type: Token.Punctuator, | |
2442 value: ch1, | |
2443 range: [start, index] | |
2444 }; | |
2445 } | |
2446 | |
2447 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
2448 } | |
2449 | |
2450 // 7.8.3 Numeric Literals | |
2451 function scanNumericLiteral() { | |
2452 var number, start, ch; | |
2453 | |
2454 ch = source[index]; | |
2455 assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), | |
2456 'Numeric literal must start with a decimal digit or a decimal point'
); | |
2457 | |
2458 start = index; | |
2459 number = ''; | |
2460 if (ch !== '.') { | |
2461 number = source[index++]; | |
2462 ch = source[index]; | |
2463 | |
2464 // Hex number starts with '0x'. | |
2465 // Octal number starts with '0'. | |
2466 if (number === '0') { | |
2467 // decimal number starts with '0' such as '09' is illegal. | |
2468 if (ch && isDecimalDigit(ch.charCodeAt(0))) { | |
2469 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
2470 } | |
2471 } | |
2472 | |
2473 while (isDecimalDigit(source.charCodeAt(index))) { | |
2474 number += source[index++]; | |
2475 } | |
2476 ch = source[index]; | |
2477 } | |
2478 | |
2479 if (ch === '.') { | |
2480 number += source[index++]; | |
2481 while (isDecimalDigit(source.charCodeAt(index))) { | |
2482 number += source[index++]; | |
2483 } | |
2484 ch = source[index]; | |
2485 } | |
2486 | |
2487 if (ch === 'e' || ch === 'E') { | |
2488 number += source[index++]; | |
2489 | |
2490 ch = source[index]; | |
2491 if (ch === '+' || ch === '-') { | |
2492 number += source[index++]; | |
2493 } | |
2494 if (isDecimalDigit(source.charCodeAt(index))) { | |
2495 while (isDecimalDigit(source.charCodeAt(index))) { | |
2496 number += source[index++]; | |
2497 } | |
2498 } else { | |
2499 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
2500 } | |
2501 } | |
2502 | |
2503 if (isIdentifierStart(source.charCodeAt(index))) { | |
2504 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
2505 } | |
2506 | |
2507 return { | |
2508 type: Token.NumericLiteral, | |
2509 value: parseFloat(number), | |
2510 range: [start, index] | |
2511 }; | |
2512 } | |
2513 | |
2514 // 7.8.4 String Literals | |
2515 | |
2516 function scanStringLiteral() { | |
2517 var str = '', quote, start, ch, octal = false; | |
2518 | |
2519 quote = source[index]; | |
2520 assert((quote === '\'' || quote === '"'), | |
2521 'String literal must starts with a quote'); | |
2522 | |
2523 start = index; | |
2524 ++index; | |
2525 | |
2526 while (index < length) { | |
2527 ch = source[index++]; | |
2528 | |
2529 if (ch === quote) { | |
2530 quote = ''; | |
2531 break; | |
2532 } else if (ch === '\\') { | |
2533 ch = source[index++]; | |
2534 if (!ch || !isLineTerminator(ch.charCodeAt(0))) { | |
2535 switch (ch) { | |
2536 case 'n': | |
2537 str += '\n'; | |
2538 break; | |
2539 case 'r': | |
2540 str += '\r'; | |
2541 break; | |
2542 case 't': | |
2543 str += '\t'; | |
2544 break; | |
2545 case 'b': | |
2546 str += '\b'; | |
2547 break; | |
2548 case 'f': | |
2549 str += '\f'; | |
2550 break; | |
2551 case 'v': | |
2552 str += '\x0B'; | |
2553 break; | |
2554 | |
2555 default: | |
2556 str += ch; | |
2557 break; | |
2558 } | |
2559 } else { | |
2560 if (ch === '\r' && source[index] === '\n') { | |
2561 ++index; | |
2562 } | |
2563 } | |
2564 } else if (isLineTerminator(ch.charCodeAt(0))) { | |
2565 break; | |
2566 } else { | |
2567 str += ch; | |
2568 } | |
2569 } | |
2570 | |
2571 if (quote !== '') { | |
2572 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
2573 } | |
2574 | |
2575 return { | |
2576 type: Token.StringLiteral, | |
2577 value: str, | |
2578 octal: octal, | |
2579 range: [start, index] | |
2580 }; | |
2581 } | |
2582 | |
2583 function isIdentifierName(token) { | |
2584 return token.type === Token.Identifier || | |
2585 token.type === Token.Keyword || | |
2586 token.type === Token.BooleanLiteral || | |
2587 token.type === Token.NullLiteral; | |
2588 } | |
2589 | |
2590 function advance() { | |
2591 var ch; | |
2592 | |
2593 skipWhitespace(); | |
2594 | |
2595 if (index >= length) { | |
2596 return { | |
2597 type: Token.EOF, | |
2598 range: [index, index] | |
2599 }; | |
2600 } | |
2601 | |
2602 ch = source.charCodeAt(index); | |
2603 | |
2604 // Very common: ( and ) and ; | |
2605 if (ch === 40 || ch === 41 || ch === 58) { | |
2606 return scanPunctuator(); | |
2607 } | |
2608 | |
2609 // String literal starts with single quote (#39) or double quote (#34). | |
2610 if (ch === 39 || ch === 34) { | |
2611 return scanStringLiteral(); | |
2612 } | |
2613 | |
2614 if (isIdentifierStart(ch)) { | |
2615 return scanIdentifier(); | |
2616 } | |
2617 | |
2618 // Dot (.) char #46 can also start a floating-point number, hence the ne
ed | |
2619 // to check the next character. | |
2620 if (ch === 46) { | |
2621 if (isDecimalDigit(source.charCodeAt(index + 1))) { | |
2622 return scanNumericLiteral(); | |
2623 } | |
2624 return scanPunctuator(); | |
2625 } | |
2626 | |
2627 if (isDecimalDigit(ch)) { | |
2628 return scanNumericLiteral(); | |
2629 } | |
2630 | |
2631 return scanPunctuator(); | |
2632 } | |
2633 | |
2634 function lex() { | |
2635 var token; | |
2636 | |
2637 token = lookahead; | |
2638 index = token.range[1]; | |
2639 | |
2640 lookahead = advance(); | |
2641 | |
2642 index = token.range[1]; | |
2643 | |
2644 return token; | |
2645 } | |
2646 | |
2647 function peek() { | |
2648 var pos; | |
2649 | |
2650 pos = index; | |
2651 lookahead = advance(); | |
2652 index = pos; | |
2653 } | |
2654 | |
2655 // Throw an exception | |
2656 | |
2657 function throwError(token, messageFormat) { | |
2658 var error, | |
2659 args = Array.prototype.slice.call(arguments, 2), | |
2660 msg = messageFormat.replace( | |
2661 /%(\d)/g, | |
2662 function (whole, index) { | |
2663 assert(index < args.length, 'Message reference must be in ra
nge'); | |
2664 return args[index]; | |
2665 } | |
2666 ); | |
2667 | |
2668 error = new Error(msg); | |
2669 error.index = index; | |
2670 error.description = msg; | |
2671 throw error; | |
2672 } | |
2673 | |
2674 // Throw an exception because of the token. | |
2675 | |
2676 function throwUnexpected(token) { | |
2677 throwError(token, Messages.UnexpectedToken, token.value); | |
2678 } | |
2679 | |
2680 // Expect the next token to match the specified punctuator. | |
2681 // If not, an exception will be thrown. | |
2682 | |
2683 function expect(value) { | |
2684 var token = lex(); | |
2685 if (token.type !== Token.Punctuator || token.value !== value) { | |
2686 throwUnexpected(token); | |
2687 } | |
2688 } | |
2689 | |
2690 // Return true if the next token matches the specified punctuator. | |
2691 | |
2692 function match(value) { | |
2693 return lookahead.type === Token.Punctuator && lookahead.value === value; | |
2694 } | |
2695 | |
2696 // Return true if the next token matches the specified keyword | |
2697 | |
2698 function matchKeyword(keyword) { | |
2699 return lookahead.type === Token.Keyword && lookahead.value === keyword; | |
2700 } | |
2701 | |
2702 function consumeSemicolon() { | |
2703 // Catch the very common case first: immediately a semicolon (char #59). | |
2704 if (source.charCodeAt(index) === 59) { | |
2705 lex(); | |
2706 return; | |
2707 } | |
2708 | |
2709 skipWhitespace(); | |
2710 | |
2711 if (match(';')) { | |
2712 lex(); | |
2713 return; | |
2714 } | |
2715 | |
2716 if (lookahead.type !== Token.EOF && !match('}')) { | |
2717 throwUnexpected(lookahead); | |
2718 } | |
2719 } | |
2720 | |
2721 // 11.1.4 Array Initialiser | |
2722 | |
2723 function parseArrayInitialiser() { | |
2724 var elements = []; | |
2725 | |
2726 expect('['); | |
2727 | |
2728 while (!match(']')) { | |
2729 if (match(',')) { | |
2730 lex(); | |
2731 elements.push(null); | |
2732 } else { | |
2733 elements.push(parseExpression()); | |
2734 | |
2735 if (!match(']')) { | |
2736 expect(','); | |
2737 } | |
2738 } | |
2739 } | |
2740 | |
2741 expect(']'); | |
2742 | |
2743 return delegate.createArrayExpression(elements); | |
2744 } | |
2745 | |
2746 // 11.1.5 Object Initialiser | |
2747 | |
2748 function parseObjectPropertyKey() { | |
2749 var token; | |
2750 | |
2751 skipWhitespace(); | |
2752 token = lex(); | |
2753 | |
2754 // Note: This function is called only from parseObjectProperty(), where | |
2755 // EOF and Punctuator tokens are already filtered out. | |
2756 if (token.type === Token.StringLiteral || token.type === Token.NumericLi
teral) { | |
2757 return delegate.createLiteral(token); | |
2758 } | |
2759 | |
2760 return delegate.createIdentifier(token.value); | |
2761 } | |
2762 | |
2763 function parseObjectProperty() { | |
2764 var token, key; | |
2765 | |
2766 token = lookahead; | |
2767 skipWhitespace(); | |
2768 | |
2769 if (token.type === Token.EOF || token.type === Token.Punctuator) { | |
2770 throwUnexpected(token); | |
2771 } | |
2772 | |
2773 key = parseObjectPropertyKey(); | |
2774 expect(':'); | |
2775 return delegate.createProperty('init', key, parseExpression()); | |
2776 } | |
2777 | |
2778 function parseObjectInitialiser() { | |
2779 var properties = []; | |
2780 | |
2781 expect('{'); | |
2782 | |
2783 while (!match('}')) { | |
2784 properties.push(parseObjectProperty()); | |
2785 | |
2786 if (!match('}')) { | |
2787 expect(','); | |
2788 } | |
2789 } | |
2790 | |
2791 expect('}'); | |
2792 | |
2793 return delegate.createObjectExpression(properties); | |
2794 } | |
2795 | |
2796 // 11.1.6 The Grouping Operator | |
2797 | |
2798 function parseGroupExpression() { | |
2799 var expr; | |
2800 | |
2801 expect('('); | |
2802 | |
2803 expr = parseExpression(); | |
2804 | |
2805 expect(')'); | |
2806 | |
2807 return expr; | |
2808 } | |
2809 | |
2810 | |
2811 // 11.1 Primary Expressions | |
2812 | |
2813 function parsePrimaryExpression() { | |
2814 var type, token, expr; | |
2815 | |
2816 if (match('(')) { | |
2817 return parseGroupExpression(); | |
2818 } | |
2819 | |
2820 type = lookahead.type; | |
2821 | |
2822 if (type === Token.Identifier) { | |
2823 expr = delegate.createIdentifier(lex().value); | |
2824 } else if (type === Token.StringLiteral || type === Token.NumericLiteral
) { | |
2825 expr = delegate.createLiteral(lex()); | |
2826 } else if (type === Token.Keyword) { | |
2827 if (matchKeyword('this')) { | |
2828 lex(); | |
2829 expr = delegate.createThisExpression(); | |
2830 } | |
2831 } else if (type === Token.BooleanLiteral) { | |
2832 token = lex(); | |
2833 token.value = (token.value === 'true'); | |
2834 expr = delegate.createLiteral(token); | |
2835 } else if (type === Token.NullLiteral) { | |
2836 token = lex(); | |
2837 token.value = null; | |
2838 expr = delegate.createLiteral(token); | |
2839 } else if (match('[')) { | |
2840 expr = parseArrayInitialiser(); | |
2841 } else if (match('{')) { | |
2842 expr = parseObjectInitialiser(); | |
2843 } | |
2844 | |
2845 if (expr) { | |
2846 return expr; | |
2847 } | |
2848 | |
2849 throwUnexpected(lex()); | |
2850 } | |
2851 | |
2852 // 11.2 Left-Hand-Side Expressions | |
2853 | |
2854 function parseArguments() { | |
2855 var args = []; | |
2856 | |
2857 expect('('); | |
2858 | |
2859 if (!match(')')) { | |
2860 while (index < length) { | |
2861 args.push(parseExpression()); | |
2862 if (match(')')) { | |
2863 break; | |
2864 } | |
2865 expect(','); | |
2866 } | |
2867 } | |
2868 | |
2869 expect(')'); | |
2870 | |
2871 return args; | |
2872 } | |
2873 | |
2874 function parseNonComputedProperty() { | |
2875 var token; | |
2876 | |
2877 token = lex(); | |
2878 | |
2879 if (!isIdentifierName(token)) { | |
2880 throwUnexpected(token); | |
2881 } | |
2882 | |
2883 return delegate.createIdentifier(token.value); | |
2884 } | |
2885 | |
2886 function parseNonComputedMember() { | |
2887 expect('.'); | |
2888 | |
2889 return parseNonComputedProperty(); | |
2890 } | |
2891 | |
2892 function parseComputedMember() { | |
2893 var expr; | |
2894 | |
2895 expect('['); | |
2896 | |
2897 expr = parseExpression(); | |
2898 | |
2899 expect(']'); | |
2900 | |
2901 return expr; | |
2902 } | |
2903 | |
2904 function parseLeftHandSideExpression() { | |
2905 var expr, args, property; | |
2906 | |
2907 expr = parsePrimaryExpression(); | |
2908 | |
2909 while (true) { | |
2910 if (match('[')) { | |
2911 property = parseComputedMember(); | |
2912 expr = delegate.createMemberExpression('[', expr, property); | |
2913 } else if (match('.')) { | |
2914 property = parseNonComputedMember(); | |
2915 expr = delegate.createMemberExpression('.', expr, property); | |
2916 } else if (match('(')) { | |
2917 args = parseArguments(); | |
2918 expr = delegate.createCallExpression(expr, args); | |
2919 } else { | |
2920 break; | |
2921 } | |
2922 } | |
2923 | |
2924 return expr; | |
2925 } | |
2926 | |
2927 // 11.3 Postfix Expressions | |
2928 | |
2929 var parsePostfixExpression = parseLeftHandSideExpression; | |
2930 | |
2931 // 11.4 Unary Operators | |
2932 | |
2933 function parseUnaryExpression() { | |
2934 var token, expr; | |
2935 | |
2936 if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyw
ord) { | |
2937 expr = parsePostfixExpression(); | |
2938 } else if (match('+') || match('-') || match('!')) { | |
2939 token = lex(); | |
2940 expr = parseUnaryExpression(); | |
2941 expr = delegate.createUnaryExpression(token.value, expr); | |
2942 } else if (matchKeyword('delete') || matchKeyword('void') || matchKeywor
d('typeof')) { | |
2943 throwError({}, Messages.UnexpectedToken); | |
2944 } else { | |
2945 expr = parsePostfixExpression(); | |
2946 } | |
2947 | |
2948 return expr; | |
2949 } | |
2950 | |
2951 function binaryPrecedence(token) { | |
2952 var prec = 0; | |
2953 | |
2954 if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { | |
2955 return 0; | |
2956 } | |
2957 | |
2958 switch (token.value) { | |
2959 case '||': | |
2960 prec = 1; | |
2961 break; | |
2962 | |
2963 case '&&': | |
2964 prec = 2; | |
2965 break; | |
2966 | |
2967 case '==': | |
2968 case '!=': | |
2969 case '===': | |
2970 case '!==': | |
2971 prec = 6; | |
2972 break; | |
2973 | |
2974 case '<': | |
2975 case '>': | |
2976 case '<=': | |
2977 case '>=': | |
2978 case 'instanceof': | |
2979 prec = 7; | |
2980 break; | |
2981 | |
2982 case 'in': | |
2983 prec = 7; | |
2984 break; | |
2985 | |
2986 case '+': | |
2987 case '-': | |
2988 prec = 9; | |
2989 break; | |
2990 | |
2991 case '*': | |
2992 case '/': | |
2993 case '%': | |
2994 prec = 11; | |
2995 break; | |
2996 | |
2997 default: | |
2998 break; | |
2999 } | |
3000 | |
3001 return prec; | |
3002 } | |
3003 | |
3004 // 11.5 Multiplicative Operators | |
3005 // 11.6 Additive Operators | |
3006 // 11.7 Bitwise Shift Operators | |
3007 // 11.8 Relational Operators | |
3008 // 11.9 Equality Operators | |
3009 // 11.10 Binary Bitwise Operators | |
3010 // 11.11 Binary Logical Operators | |
3011 | |
3012 function parseBinaryExpression() { | |
3013 var expr, token, prec, stack, right, operator, left, i; | |
3014 | |
3015 left = parseUnaryExpression(); | |
3016 | |
3017 token = lookahead; | |
3018 prec = binaryPrecedence(token); | |
3019 if (prec === 0) { | |
3020 return left; | |
3021 } | |
3022 token.prec = prec; | |
3023 lex(); | |
3024 | |
3025 right = parseUnaryExpression(); | |
3026 | |
3027 stack = [left, token, right]; | |
3028 | |
3029 while ((prec = binaryPrecedence(lookahead)) > 0) { | |
3030 | |
3031 // Reduce: make a binary expression from the three topmost entries. | |
3032 while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec))
{ | |
3033 right = stack.pop(); | |
3034 operator = stack.pop().value; | |
3035 left = stack.pop(); | |
3036 expr = delegate.createBinaryExpression(operator, left, right); | |
3037 stack.push(expr); | |
3038 } | |
3039 | |
3040 // Shift. | |
3041 token = lex(); | |
3042 token.prec = prec; | |
3043 stack.push(token); | |
3044 expr = parseUnaryExpression(); | |
3045 stack.push(expr); | |
3046 } | |
3047 | |
3048 // Final reduce to clean-up the stack. | |
3049 i = stack.length - 1; | |
3050 expr = stack[i]; | |
3051 while (i > 1) { | |
3052 expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i -
2], expr); | |
3053 i -= 2; | |
3054 } | |
3055 | |
3056 return expr; | |
3057 } | |
3058 | |
3059 | |
3060 // 11.12 Conditional Operator | |
3061 | |
3062 function parseConditionalExpression() { | |
3063 var expr, consequent, alternate; | |
3064 | |
3065 expr = parseBinaryExpression(); | |
3066 | |
3067 if (match('?')) { | |
3068 lex(); | |
3069 consequent = parseConditionalExpression(); | |
3070 expect(':'); | |
3071 alternate = parseConditionalExpression(); | |
3072 | |
3073 expr = delegate.createConditionalExpression(expr, consequent, altern
ate); | |
3074 } | |
3075 | |
3076 return expr; | |
3077 } | |
3078 | |
3079 // Simplification since we do not support AssignmentExpression. | |
3080 var parseExpression = parseConditionalExpression; | |
3081 | |
3082 // Polymer Syntax extensions | |
3083 | |
3084 // Filter :: | |
3085 // Identifier | |
3086 // Identifier "(" ")" | |
3087 // Identifier "(" FilterArguments ")" | |
3088 | |
3089 function parseFilter() { | |
3090 var identifier, args; | |
3091 | |
3092 identifier = lex(); | |
3093 | |
3094 if (identifier.type !== Token.Identifier) { | |
3095 throwUnexpected(identifier); | |
3096 } | |
3097 | |
3098 args = match('(') ? parseArguments() : []; | |
3099 | |
3100 return delegate.createFilter(identifier.value, args); | |
3101 } | |
3102 | |
3103 // Filters :: | |
3104 // "|" Filter | |
3105 // Filters "|" Filter | |
3106 | |
3107 function parseFilters() { | |
3108 while (match('|')) { | |
3109 lex(); | |
3110 parseFilter(); | |
3111 } | |
3112 } | |
3113 | |
3114 // TopLevel :: | |
3115 // LabelledExpressions | |
3116 // AsExpression | |
3117 // InExpression | |
3118 // FilterExpression | |
3119 | |
3120 // AsExpression :: | |
3121 // FilterExpression as Identifier | |
3122 | |
3123 // InExpression :: | |
3124 // Identifier, Identifier in FilterExpression | |
3125 // Identifier in FilterExpression | |
3126 | |
3127 // FilterExpression :: | |
3128 // Expression | |
3129 // Expression Filters | |
3130 | |
3131 function parseTopLevel() { | |
3132 skipWhitespace(); | |
3133 peek(); | |
3134 | |
3135 var expr = parseExpression(); | |
3136 if (expr) { | |
3137 if (lookahead.value === ',' || lookahead.value == 'in' && | |
3138 expr.type === Syntax.Identifier) { | |
3139 parseInExpression(expr); | |
3140 } else { | |
3141 parseFilters(); | |
3142 if (lookahead.value === 'as') { | |
3143 parseAsExpression(expr); | |
3144 } else { | |
3145 delegate.createTopLevel(expr); | |
3146 } | |
3147 } | |
3148 } | |
3149 | |
3150 if (lookahead.type !== Token.EOF) { | |
3151 throwUnexpected(lookahead); | |
3152 } | |
3153 } | |
3154 | |
3155 function parseAsExpression(expr) { | |
3156 lex(); // as | |
3157 var identifier = lex().value; | |
3158 delegate.createAsExpression(expr, identifier); | |
3159 } | |
3160 | |
3161 function parseInExpression(identifier) { | |
3162 var indexName; | |
3163 if (lookahead.value === ',') { | |
3164 lex(); | |
3165 if (lookahead.type !== Token.Identifier) | |
3166 throwUnexpected(lookahead); | |
3167 indexName = lex().value; | |
3168 } | |
3169 | |
3170 lex(); // in | |
3171 var expr = parseExpression(); | |
3172 parseFilters(); | |
3173 delegate.createInExpression(identifier.name, indexName, expr); | |
3174 } | |
3175 | |
3176 function parse(code, inDelegate) { | |
3177 delegate = inDelegate; | |
3178 source = code; | |
3179 index = 0; | |
3180 length = source.length; | |
3181 lookahead = null; | |
3182 state = { | |
3183 labelSet: {} | |
3184 }; | |
3185 | |
3186 return parseTopLevel(); | |
3187 } | |
3188 | |
3189 global.esprima = { | |
3190 parse: parse | |
3191 }; | |
3192 })(this); | |
3193 | |
3194 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
3195 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
3196 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
3197 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
3198 // Code distributed by Google as part of the polymer project is also | |
3199 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
3200 | |
3201 (function (global) { | |
3202 'use strict'; | |
3203 | |
3204 function prepareBinding(expressionText, name, node, filterRegistry) { | |
3205 var expression; | |
3206 try { | |
3207 expression = getExpression(expressionText); | |
3208 if (expression.scopeIdent && | |
3209 (node.nodeType !== Node.ELEMENT_NODE || | |
3210 node.tagName !== 'TEMPLATE' || | |
3211 (name !== 'bind' && name !== 'repeat'))) { | |
3212 throw Error('as and in can only be used within <template bind/repeat>'); | |
3213 } | |
3214 } catch (ex) { | |
3215 console.error('Invalid expression syntax: ' + expressionText, ex); | |
3216 return; | |
3217 } | |
3218 | |
3219 return function(model, node, oneTime) { | |
3220 var binding = expression.getBinding(model, filterRegistry, oneTime); | |
3221 if (expression.scopeIdent && binding) { | |
3222 node.polymerExpressionScopeIdent_ = expression.scopeIdent; | |
3223 if (expression.indexIdent) | |
3224 node.polymerExpressionIndexIdent_ = expression.indexIdent; | |
3225 } | |
3226 | |
3227 return binding; | |
3228 } | |
3229 } | |
3230 | |
3231 // TODO(rafaelw): Implement simple LRU. | |
3232 var expressionParseCache = Object.create(null); | |
3233 | |
3234 function getExpression(expressionText) { | |
3235 var expression = expressionParseCache[expressionText]; | |
3236 if (!expression) { | |
3237 var delegate = new ASTDelegate(); | |
3238 esprima.parse(expressionText, delegate); | |
3239 expression = new Expression(delegate); | |
3240 expressionParseCache[expressionText] = expression; | |
3241 } | |
3242 return expression; | |
3243 } | |
3244 | |
3245 function Literal(value) { | |
3246 this.value = value; | |
3247 this.valueFn_ = undefined; | |
3248 } | |
3249 | |
3250 Literal.prototype = { | |
3251 valueFn: function() { | |
3252 if (!this.valueFn_) { | |
3253 var value = this.value; | |
3254 this.valueFn_ = function() { | |
3255 return value; | |
3256 } | |
3257 } | |
3258 | |
3259 return this.valueFn_; | |
3260 } | |
3261 } | |
3262 | |
3263 function IdentPath(name) { | |
3264 this.name = name; | |
3265 this.path = Path.get(name); | |
3266 } | |
3267 | |
3268 IdentPath.prototype = { | |
3269 valueFn: function() { | |
3270 if (!this.valueFn_) { | |
3271 var name = this.name; | |
3272 var path = this.path; | |
3273 this.valueFn_ = function(model, observer) { | |
3274 if (observer) | |
3275 observer.addPath(model, path); | |
3276 | |
3277 return path.getValueFrom(model); | |
3278 } | |
3279 } | |
3280 | |
3281 return this.valueFn_; | |
3282 }, | |
3283 | |
3284 setValue: function(model, newValue) { | |
3285 if (this.path.length == 1) | |
3286 model = findScope(model, this.path[0]); | |
3287 | |
3288 return this.path.setValueFrom(model, newValue); | |
3289 } | |
3290 }; | |
3291 | |
3292 function MemberExpression(object, property, accessor) { | |
3293 this.computed = accessor == '['; | |
3294 | |
3295 this.dynamicDeps = typeof object == 'function' || | |
3296 object.dynamicDeps || | |
3297 (this.computed && !(property instanceof Literal)); | |
3298 | |
3299 this.simplePath = | |
3300 !this.dynamicDeps && | |
3301 (property instanceof IdentPath || property instanceof Literal) && | |
3302 (object instanceof MemberExpression || object instanceof IdentPath); | |
3303 | |
3304 this.object = this.simplePath ? object : getFn(object); | |
3305 this.property = !this.computed || this.simplePath ? | |
3306 property : getFn(property); | |
3307 } | |
3308 | |
3309 MemberExpression.prototype = { | |
3310 get fullPath() { | |
3311 if (!this.fullPath_) { | |
3312 | |
3313 var parts = this.object instanceof MemberExpression ? | |
3314 this.object.fullPath.slice() : [this.object.name]; | |
3315 parts.push(this.property instanceof IdentPath ? | |
3316 this.property.name : this.property.value); | |
3317 this.fullPath_ = Path.get(parts); | |
3318 } | |
3319 | |
3320 return this.fullPath_; | |
3321 }, | |
3322 | |
3323 valueFn: function() { | |
3324 if (!this.valueFn_) { | |
3325 var object = this.object; | |
3326 | |
3327 if (this.simplePath) { | |
3328 var path = this.fullPath; | |
3329 | |
3330 this.valueFn_ = function(model, observer) { | |
3331 if (observer) | |
3332 observer.addPath(model, path); | |
3333 | |
3334 return path.getValueFrom(model); | |
3335 }; | |
3336 } else if (!this.computed) { | |
3337 var path = Path.get(this.property.name); | |
3338 | |
3339 this.valueFn_ = function(model, observer, filterRegistry) { | |
3340 var context = object(model, observer, filterRegistry); | |
3341 | |
3342 if (observer) | |
3343 observer.addPath(context, path); | |
3344 | |
3345 return path.getValueFrom(context); | |
3346 } | |
3347 } else { | |
3348 // Computed property. | |
3349 var property = this.property; | |
3350 | |
3351 this.valueFn_ = function(model, observer, filterRegistry) { | |
3352 var context = object(model, observer, filterRegistry); | |
3353 var propName = property(model, observer, filterRegistry); | |
3354 if (observer) | |
3355 observer.addPath(context, [propName]); | |
3356 | |
3357 return context ? context[propName] : undefined; | |
3358 }; | |
3359 } | |
3360 } | |
3361 return this.valueFn_; | |
3362 }, | |
3363 | |
3364 setValue: function(model, newValue) { | |
3365 if (this.simplePath) { | |
3366 this.fullPath.setValueFrom(model, newValue); | |
3367 return newValue; | |
3368 } | |
3369 | |
3370 var object = this.object(model); | |
3371 var propName = this.property instanceof IdentPath ? this.property.name : | |
3372 this.property(model); | |
3373 return object[propName] = newValue; | |
3374 } | |
3375 }; | |
3376 | |
3377 function Filter(name, args) { | |
3378 this.name = name; | |
3379 this.args = []; | |
3380 for (var i = 0; i < args.length; i++) { | |
3381 this.args[i] = getFn(args[i]); | |
3382 } | |
3383 } | |
3384 | |
3385 Filter.prototype = { | |
3386 transform: function(model, observer, filterRegistry, toModelDirection, | |
3387 initialArgs) { | |
3388 var context = model; | |
3389 var fn = context[this.name]; | |
3390 | |
3391 if (!fn) { | |
3392 fn = filterRegistry[this.name]; | |
3393 if (!fn) { | |
3394 console.error('Cannot find function or filter: ' + this.name); | |
3395 return; | |
3396 } | |
3397 } | |
3398 | |
3399 // If toModelDirection is falsey, then the "normal" (dom-bound) direction | |
3400 // is used. Otherwise, it looks for a 'toModel' property function on the | |
3401 // object. | |
3402 if (toModelDirection) { | |
3403 fn = fn.toModel; | |
3404 } else if (typeof fn.toDOM == 'function') { | |
3405 fn = fn.toDOM; | |
3406 } | |
3407 | |
3408 if (typeof fn != 'function') { | |
3409 console.error('Cannot find function or filter: ' + this.name); | |
3410 return; | |
3411 } | |
3412 | |
3413 var args = initialArgs || []; | |
3414 for (var i = 0; i < this.args.length; i++) { | |
3415 args.push(getFn(this.args[i])(model, observer, filterRegistry)); | |
3416 } | |
3417 | |
3418 return fn.apply(context, args); | |
3419 } | |
3420 }; | |
3421 | |
3422 function notImplemented() { throw Error('Not Implemented'); } | |
3423 | |
3424 var unaryOperators = { | |
3425 '+': function(v) { return +v; }, | |
3426 '-': function(v) { return -v; }, | |
3427 '!': function(v) { return !v; } | |
3428 }; | |
3429 | |
3430 var binaryOperators = { | |
3431 '+': function(l, r) { return l+r; }, | |
3432 '-': function(l, r) { return l-r; }, | |
3433 '*': function(l, r) { return l*r; }, | |
3434 '/': function(l, r) { return l/r; }, | |
3435 '%': function(l, r) { return l%r; }, | |
3436 '<': function(l, r) { return l<r; }, | |
3437 '>': function(l, r) { return l>r; }, | |
3438 '<=': function(l, r) { return l<=r; }, | |
3439 '>=': function(l, r) { return l>=r; }, | |
3440 '==': function(l, r) { return l==r; }, | |
3441 '!=': function(l, r) { return l!=r; }, | |
3442 '===': function(l, r) { return l===r; }, | |
3443 '!==': function(l, r) { return l!==r; }, | |
3444 '&&': function(l, r) { return l&&r; }, | |
3445 '||': function(l, r) { return l||r; }, | |
3446 }; | |
3447 | |
3448 function getFn(arg) { | |
3449 return typeof arg == 'function' ? arg : arg.valueFn(); | |
3450 } | |
3451 | |
3452 function ASTDelegate() { | |
3453 this.expression = null; | |
3454 this.filters = []; | |
3455 this.deps = {}; | |
3456 this.currentPath = undefined; | |
3457 this.scopeIdent = undefined; | |
3458 this.indexIdent = undefined; | |
3459 this.dynamicDeps = false; | |
3460 } | |
3461 | |
3462 ASTDelegate.prototype = { | |
3463 createUnaryExpression: function(op, argument) { | |
3464 if (!unaryOperators[op]) | |
3465 throw Error('Disallowed operator: ' + op); | |
3466 | |
3467 argument = getFn(argument); | |
3468 | |
3469 return function(model, observer, filterRegistry) { | |
3470 return unaryOperators[op](argument(model, observer, filterRegistry)); | |
3471 }; | |
3472 }, | |
3473 | |
3474 createBinaryExpression: function(op, left, right) { | |
3475 if (!binaryOperators[op]) | |
3476 throw Error('Disallowed operator: ' + op); | |
3477 | |
3478 left = getFn(left); | |
3479 right = getFn(right); | |
3480 | |
3481 switch (op) { | |
3482 case '||': | |
3483 this.dynamicDeps = true; | |
3484 return function(model, observer, filterRegistry) { | |
3485 return left(model, observer, filterRegistry) || | |
3486 right(model, observer, filterRegistry); | |
3487 }; | |
3488 case '&&': | |
3489 this.dynamicDeps = true; | |
3490 return function(model, observer, filterRegistry) { | |
3491 return left(model, observer, filterRegistry) && | |
3492 right(model, observer, filterRegistry); | |
3493 }; | |
3494 } | |
3495 | |
3496 return function(model, observer, filterRegistry) { | |
3497 return binaryOperators[op](left(model, observer, filterRegistry), | |
3498 right(model, observer, filterRegistry)); | |
3499 }; | |
3500 }, | |
3501 | |
3502 createConditionalExpression: function(test, consequent, alternate) { | |
3503 test = getFn(test); | |
3504 consequent = getFn(consequent); | |
3505 alternate = getFn(alternate); | |
3506 | |
3507 this.dynamicDeps = true; | |
3508 | |
3509 return function(model, observer, filterRegistry) { | |
3510 return test(model, observer, filterRegistry) ? | |
3511 consequent(model, observer, filterRegistry) : | |
3512 alternate(model, observer, filterRegistry); | |
3513 } | |
3514 }, | |
3515 | |
3516 createIdentifier: function(name) { | |
3517 var ident = new IdentPath(name); | |
3518 ident.type = 'Identifier'; | |
3519 return ident; | |
3520 }, | |
3521 | |
3522 createMemberExpression: function(accessor, object, property) { | |
3523 var ex = new MemberExpression(object, property, accessor); | |
3524 if (ex.dynamicDeps) | |
3525 this.dynamicDeps = true; | |
3526 return ex; | |
3527 }, | |
3528 | |
3529 createCallExpression: function(expression, args) { | |
3530 if (!(expression instanceof IdentPath)) | |
3531 throw Error('Only identifier function invocations are allowed'); | |
3532 | |
3533 var filter = new Filter(expression.name, args); | |
3534 | |
3535 return function(model, observer, filterRegistry) { | |
3536 return filter.transform(model, observer, filterRegistry, false); | |
3537 }; | |
3538 }, | |
3539 | |
3540 createLiteral: function(token) { | |
3541 return new Literal(token.value); | |
3542 }, | |
3543 | |
3544 createArrayExpression: function(elements) { | |
3545 for (var i = 0; i < elements.length; i++) | |
3546 elements[i] = getFn(elements[i]); | |
3547 | |
3548 return function(model, observer, filterRegistry) { | |
3549 var arr = [] | |
3550 for (var i = 0; i < elements.length; i++) | |
3551 arr.push(elements[i](model, observer, filterRegistry)); | |
3552 return arr; | |
3553 } | |
3554 }, | |
3555 | |
3556 createProperty: function(kind, key, value) { | |
3557 return { | |
3558 key: key instanceof IdentPath ? key.name : key.value, | |
3559 value: value | |
3560 }; | |
3561 }, | |
3562 | |
3563 createObjectExpression: function(properties) { | |
3564 for (var i = 0; i < properties.length; i++) | |
3565 properties[i].value = getFn(properties[i].value); | |
3566 | |
3567 return function(model, observer, filterRegistry) { | |
3568 var obj = {}; | |
3569 for (var i = 0; i < properties.length; i++) | |
3570 obj[properties[i].key] = | |
3571 properties[i].value(model, observer, filterRegistry); | |
3572 return obj; | |
3573 } | |
3574 }, | |
3575 | |
3576 createFilter: function(name, args) { | |
3577 this.filters.push(new Filter(name, args)); | |
3578 }, | |
3579 | |
3580 createAsExpression: function(expression, scopeIdent) { | |
3581 this.expression = expression; | |
3582 this.scopeIdent = scopeIdent; | |
3583 }, | |
3584 | |
3585 createInExpression: function(scopeIdent, indexIdent, expression) { | |
3586 this.expression = expression; | |
3587 this.scopeIdent = scopeIdent; | |
3588 this.indexIdent = indexIdent; | |
3589 }, | |
3590 | |
3591 createTopLevel: function(expression) { | |
3592 this.expression = expression; | |
3593 }, | |
3594 | |
3595 createThisExpression: notImplemented | |
3596 } | |
3597 | |
3598 function ConstantObservable(value) { | |
3599 this.value_ = value; | |
3600 } | |
3601 | |
3602 ConstantObservable.prototype = { | |
3603 open: function() { return this.value_; }, | |
3604 discardChanges: function() { return this.value_; }, | |
3605 deliver: function() {}, | |
3606 close: function() {}, | |
3607 } | |
3608 | |
3609 function Expression(delegate) { | |
3610 this.scopeIdent = delegate.scopeIdent; | |
3611 this.indexIdent = delegate.indexIdent; | |
3612 | |
3613 if (!delegate.expression) | |
3614 throw Error('No expression found.'); | |
3615 | |
3616 this.expression = delegate.expression; | |
3617 getFn(this.expression); // forces enumeration of path dependencies | |
3618 | |
3619 this.filters = delegate.filters; | |
3620 this.dynamicDeps = delegate.dynamicDeps; | |
3621 } | |
3622 | |
3623 Expression.prototype = { | |
3624 getBinding: function(model, filterRegistry, oneTime) { | |
3625 if (oneTime) | |
3626 return this.getValue(model, undefined, filterRegistry); | |
3627 | |
3628 var observer = new CompoundObserver(); | |
3629 // captures deps. | |
3630 var firstValue = this.getValue(model, observer, filterRegistry); | |
3631 var firstTime = true; | |
3632 var self = this; | |
3633 | |
3634 function valueFn() { | |
3635 // deps cannot have changed on first value retrieval. | |
3636 if (firstTime) { | |
3637 firstTime = false; | |
3638 return firstValue; | |
3639 } | |
3640 | |
3641 if (self.dynamicDeps) | |
3642 observer.startReset(); | |
3643 | |
3644 var value = self.getValue(model, | |
3645 self.dynamicDeps ? observer : undefined, | |
3646 filterRegistry); | |
3647 if (self.dynamicDeps) | |
3648 observer.finishReset(); | |
3649 | |
3650 return value; | |
3651 } | |
3652 | |
3653 function setValueFn(newValue) { | |
3654 self.setValue(model, newValue, filterRegistry); | |
3655 return newValue; | |
3656 } | |
3657 | |
3658 return new ObserverTransform(observer, valueFn, setValueFn, true); | |
3659 }, | |
3660 | |
3661 getValue: function(model, observer, filterRegistry) { | |
3662 var value = getFn(this.expression)(model, observer, filterRegistry); | |
3663 for (var i = 0; i < this.filters.length; i++) { | |
3664 value = this.filters[i].transform(model, observer, filterRegistry, | |
3665 false, [value]); | |
3666 } | |
3667 | |
3668 return value; | |
3669 }, | |
3670 | |
3671 setValue: function(model, newValue, filterRegistry) { | |
3672 var count = this.filters ? this.filters.length : 0; | |
3673 while (count-- > 0) { | |
3674 newValue = this.filters[count].transform(model, undefined, | |
3675 filterRegistry, true, [newValue]); | |
3676 } | |
3677 | |
3678 if (this.expression.setValue) | |
3679 return this.expression.setValue(model, newValue); | |
3680 } | |
3681 } | |
3682 | |
3683 /** | |
3684 * Converts a style property name to a css property name. For example: | |
3685 * "WebkitUserSelect" to "-webkit-user-select" | |
3686 */ | |
3687 function convertStylePropertyName(name) { | |
3688 return String(name).replace(/[A-Z]/g, function(c) { | |
3689 return '-' + c.toLowerCase(); | |
3690 }); | |
3691 } | |
3692 | |
3693 var parentScopeName = '@' + Math.random().toString(36).slice(2); | |
3694 | |
3695 // Single ident paths must bind directly to the appropriate scope object. | |
3696 // I.e. Pushed values in two-bindings need to be assigned to the actual model | |
3697 // object. | |
3698 function findScope(model, prop) { | |
3699 while (model[parentScopeName] && | |
3700 !Object.prototype.hasOwnProperty.call(model, prop)) { | |
3701 model = model[parentScopeName]; | |
3702 } | |
3703 | |
3704 return model; | |
3705 } | |
3706 | |
3707 function isLiteralExpression(pathString) { | |
3708 switch (pathString) { | |
3709 case '': | |
3710 return false; | |
3711 | |
3712 case 'false': | |
3713 case 'null': | |
3714 case 'true': | |
3715 return true; | |
3716 } | |
3717 | |
3718 if (!isNaN(Number(pathString))) | |
3719 return true; | |
3720 | |
3721 return false; | |
3722 }; | |
3723 | |
3724 function PolymerExpressions() {} | |
3725 | |
3726 PolymerExpressions.prototype = { | |
3727 // "built-in" filters | |
3728 styleObject: function(value) { | |
3729 var parts = []; | |
3730 for (var key in value) { | |
3731 parts.push(convertStylePropertyName(key) + ': ' + value[key]); | |
3732 } | |
3733 return parts.join('; '); | |
3734 }, | |
3735 | |
3736 tokenList: function(value) { | |
3737 var tokens = []; | |
3738 for (var key in value) { | |
3739 if (value[key]) | |
3740 tokens.push(key); | |
3741 } | |
3742 return tokens.join(' '); | |
3743 }, | |
3744 | |
3745 // binding delegate API | |
3746 prepareInstancePositionChanged: function(template) { | |
3747 var indexIdent = template.polymerExpressionIndexIdent_; | |
3748 if (!indexIdent) | |
3749 return; | |
3750 | |
3751 return function(templateInstance, index) { | |
3752 templateInstance.model[indexIdent] = index; | |
3753 }; | |
3754 }, | |
3755 | |
3756 prepareBinding: function(pathString, name, node) { | |
3757 var path = Path.get(pathString); | |
3758 | |
3759 if (!isLiteralExpression(pathString) && path.valid) { | |
3760 if (path.length == 1) { | |
3761 return function(model, node, oneTime) { | |
3762 if (oneTime) | |
3763 return path.getValueFrom(model); | |
3764 | |
3765 var scope = findScope(model, path[0]); | |
3766 return new PathObserver(scope, path); | |
3767 }; | |
3768 } | |
3769 return; // bail out early if pathString is simple path. | |
3770 } | |
3771 | |
3772 return prepareBinding(pathString, name, node, this); | |
3773 }, | |
3774 | |
3775 prepareInstanceModel: function(template) { | |
3776 var scopeName = template.polymerExpressionScopeIdent_; | |
3777 if (!scopeName) | |
3778 return; | |
3779 | |
3780 var parentScope = template.templateInstance ? | |
3781 template.templateInstance.model : | |
3782 template.model; | |
3783 | |
3784 var indexName = template.polymerExpressionIndexIdent_; | |
3785 | |
3786 return function(model) { | |
3787 return createScopeObject(parentScope, model, scopeName, indexName); | |
3788 }; | |
3789 } | |
3790 }; | |
3791 | |
3792 var createScopeObject = ('__proto__' in {}) ? | |
3793 function(parentScope, model, scopeName, indexName) { | |
3794 var scope = {}; | |
3795 scope[scopeName] = model; | |
3796 scope[indexName] = undefined; | |
3797 scope[parentScopeName] = parentScope; | |
3798 scope.__proto__ = parentScope; | |
3799 return scope; | |
3800 } : | |
3801 function(parentScope, model, scopeName, indexName) { | |
3802 var scope = Object.create(parentScope); | |
3803 Object.defineProperty(scope, scopeName, | |
3804 { value: model, configurable: true, writable: true }); | |
3805 Object.defineProperty(scope, indexName, | |
3806 { value: undefined, configurable: true, writable: true }); | |
3807 Object.defineProperty(scope, parentScopeName, | |
3808 { value: parentScope, configurable: true, writable: true }); | |
3809 return scope; | |
3810 }; | |
3811 | |
3812 global.PolymerExpressions = PolymerExpressions; | |
3813 PolymerExpressions.getExpression = getExpression; | |
3814 })(this); | |
3815 | |
3816 Polymer = { | |
3817 version: '0.5.5' | |
3818 }; | |
3819 | |
3820 // TODO(sorvell): this ensures Polymer is an object and not a function | |
3821 // Platform is currently defining it as a function to allow for async loading | |
3822 // of polymer; once we refine the loading process this likely goes away. | |
3823 if (typeof window.Polymer === 'function') { | |
3824 Polymer = {}; | |
3825 } | |
3826 | |
3827 | |
3828 (function(scope) { | |
3829 | |
3830 function withDependencies(task, depends) { | |
3831 depends = depends || []; | |
3832 if (!depends.map) { | |
3833 depends = [depends]; | |
3834 } | |
3835 return task.apply(this, depends.map(marshal)); | |
3836 } | |
3837 | |
3838 function module(name, dependsOrFactory, moduleFactory) { | |
3839 var module; | |
3840 switch (arguments.length) { | |
3841 case 0: | |
3842 return; | |
3843 case 1: | |
3844 module = null; | |
3845 break; | |
3846 case 2: | |
3847 // dependsOrFactory is `factory` in this case | |
3848 module = dependsOrFactory.apply(this); | |
3849 break; | |
3850 default: | |
3851 // dependsOrFactory is `depends` in this case | |
3852 module = withDependencies(moduleFactory, dependsOrFactory); | |
3853 break; | |
3854 } | |
3855 modules[name] = module; | |
3856 }; | |
3857 | |
3858 function marshal(name) { | |
3859 return modules[name]; | |
3860 } | |
3861 | |
3862 var modules = {}; | |
3863 | |
3864 function using(depends, task) { | |
3865 HTMLImports.whenImportsReady(function() { | |
3866 withDependencies(task, depends); | |
3867 }); | |
3868 }; | |
3869 | |
3870 // exports | |
3871 | |
3872 scope.marshal = marshal; | |
3873 // `module` confuses commonjs detectors | |
3874 scope.modularize = module; | |
3875 scope.using = using; | |
3876 | |
3877 })(window); | |
3878 | |
3879 /* | |
3880 Build only script. | |
3881 | |
3882 Ensures scripts needed for basic x-platform compatibility | |
3883 will be run when platform.js is not loaded. | |
3884 */ | |
3885 if (!window.WebComponents) { | |
3886 | |
3887 /* | |
3888 On supported platforms, platform.js is not needed. To retain compatibili
ty | |
3889 with the polyfills, we stub out minimal functionality. | |
3890 */ | |
3891 if (!window.WebComponents) { | |
3892 | |
3893 WebComponents = { | |
3894 flush: function() {}, | |
3895 flags: {log: {}} | |
3896 }; | |
3897 | |
3898 Platform = WebComponents; | |
3899 | |
3900 CustomElements = { | |
3901 useNative: true, | |
3902 ready: true, | |
3903 takeRecords: function() {}, | |
3904 instanceof: function(obj, base) { | |
3905 return obj instanceof base; | |
3906 } | |
3907 }; | |
3908 | |
3909 HTMLImports = { | |
3910 useNative: true | |
3911 }; | |
3912 | |
3913 | |
3914 addEventListener('HTMLImportsLoaded', function() { | |
3915 document.dispatchEvent( | |
3916 new CustomEvent('WebComponentsReady', {bubbles: true}) | |
3917 ); | |
3918 }); | |
3919 | |
3920 | |
3921 // ShadowDOM | |
3922 ShadowDOMPolyfill = null; | |
3923 wrap = unwrap = function(n){ | |
3924 return n; | |
3925 }; | |
3926 | |
3927 } | |
3928 | |
3929 /* | |
3930 Create polyfill scope and feature detect native support. | |
3931 */ | |
3932 window.HTMLImports = window.HTMLImports || {flags:{}}; | |
3933 | |
3934 (function(scope) { | |
3935 | |
3936 /** | |
3937 Basic setup and simple module executer. We collect modules and then execute | |
3938 the code later, only if it's necessary for polyfilling. | |
3939 */ | |
3940 var IMPORT_LINK_TYPE = 'import'; | |
3941 var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement('link')); | |
3942 | |
3943 /** | |
3944 Support `currentScript` on all browsers as `document._currentScript.` | |
3945 | |
3946 NOTE: We cannot polyfill `document.currentScript` because it's not possible | |
3947 both to override and maintain the ability to capture the native value. | |
3948 Therefore we choose to expose `_currentScript` both when native imports | |
3949 and the polyfill are in use. | |
3950 */ | |
3951 // NOTE: ShadowDOMPolyfill intrusion. | |
3952 var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill); | |
3953 var wrap = function(node) { | |
3954 return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node; | |
3955 }; | |
3956 var rootDocument = wrap(document); | |
3957 | |
3958 var currentScriptDescriptor = { | |
3959 get: function() { | |
3960 var script = HTMLImports.currentScript || document.currentScript || | |
3961 // NOTE: only works when called in synchronously executing code. | |
3962 // readyState should check if `loading` but IE10 is | |
3963 // interactive when scripts run so we cheat. | |
3964 (document.readyState !== 'complete' ? | |
3965 document.scripts[document.scripts.length - 1] : null); | |
3966 return wrap(script); | |
3967 }, | |
3968 configurable: true | |
3969 }; | |
3970 | |
3971 Object.defineProperty(document, '_currentScript', currentScriptDescriptor); | |
3972 Object.defineProperty(rootDocument, '_currentScript', currentScriptDescriptor); | |
3973 | |
3974 /** | |
3975 Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady` | |
3976 method. This api is necessary because unlike the native implementation, | |
3977 script elements do not force imports to resolve. Instead, users should wrap | |
3978 code in either an `HTMLImportsLoaded` hander or after load time in an | |
3979 `HTMLImports.whenReady(callback)` call. | |
3980 | |
3981 NOTE: This module also supports these apis under the native implementation. | |
3982 Therefore, if this file is loaded, the same code can be used under both | |
3983 the polyfill and native implementation. | |
3984 */ | |
3985 | |
3986 var isIE = /Trident/.test(navigator.userAgent); | |
3987 | |
3988 // call a callback when all HTMLImports in the document at call time | |
3989 // (or at least document ready) have loaded. | |
3990 // 1. ensure the document is in a ready state (has dom), then | |
3991 // 2. watch for loading of imports and call callback when done | |
3992 function whenReady(callback, doc) { | |
3993 doc = doc || rootDocument; | |
3994 // if document is loading, wait and try again | |
3995 whenDocumentReady(function() { | |
3996 watchImportsLoad(callback, doc); | |
3997 }, doc); | |
3998 } | |
3999 | |
4000 // call the callback when the document is in a ready state (has dom) | |
4001 var requiredReadyState = isIE ? 'complete' : 'interactive'; | |
4002 var READY_EVENT = 'readystatechange'; | |
4003 function isDocumentReady(doc) { | |
4004 return (doc.readyState === 'complete' || | |
4005 doc.readyState === requiredReadyState); | |
4006 } | |
4007 | |
4008 // call <callback> when we ensure the document is in a ready state | |
4009 function whenDocumentReady(callback, doc) { | |
4010 if (!isDocumentReady(doc)) { | |
4011 var checkReady = function() { | |
4012 if (doc.readyState === 'complete' || | |
4013 doc.readyState === requiredReadyState) { | |
4014 doc.removeEventListener(READY_EVENT, checkReady); | |
4015 whenDocumentReady(callback, doc); | |
4016 } | |
4017 }; | |
4018 doc.addEventListener(READY_EVENT, checkReady); | |
4019 } else if (callback) { | |
4020 callback(); | |
4021 } | |
4022 } | |
4023 | |
4024 function markTargetLoaded(event) { | |
4025 event.target.__loaded = true; | |
4026 } | |
4027 | |
4028 // call <callback> when we ensure all imports have loaded | |
4029 function watchImportsLoad(callback, doc) { | |
4030 var imports = doc.querySelectorAll('link[rel=import]'); | |
4031 var loaded = 0, l = imports.length; | |
4032 function checkDone(d) { | |
4033 if ((loaded == l) && callback) { | |
4034 callback(); | |
4035 } | |
4036 } | |
4037 function loadedImport(e) { | |
4038 markTargetLoaded(e); | |
4039 loaded++; | |
4040 checkDone(); | |
4041 } | |
4042 if (l) { | |
4043 for (var i=0, imp; (i<l) && (imp=imports[i]); i++) { | |
4044 if (isImportLoaded(imp)) { | |
4045 loadedImport.call(imp, {target: imp}); | |
4046 } else { | |
4047 imp.addEventListener('load', loadedImport); | |
4048 imp.addEventListener('error', loadedImport); | |
4049 } | |
4050 } | |
4051 } else { | |
4052 checkDone(); | |
4053 } | |
4054 } | |
4055 | |
4056 // NOTE: test for native imports loading is based on explicitly watching | |
4057 // all imports (see below). | |
4058 // However, we cannot rely on this entirely without watching the entire document | |
4059 // for import links. For perf reasons, currently only head is watched. | |
4060 // Instead, we fallback to checking if the import property is available | |
4061 // and the document is not itself loading. | |
4062 function isImportLoaded(link) { | |
4063 return useNative ? link.__loaded || | |
4064 (link.import && link.import.readyState !== 'loading') : | |
4065 link.__importParsed; | |
4066 } | |
4067 | |
4068 // TODO(sorvell): Workaround for | |
4069 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007, should be removed when | |
4070 // this bug is addressed. | |
4071 // (1) Install a mutation observer to see when HTMLImports have loaded | |
4072 // (2) if this script is run during document load it will watch any existing | |
4073 // imports for loading. | |
4074 // | |
4075 // NOTE: The workaround has restricted functionality: (1) it's only compatible | |
4076 // with imports that are added to document.head since the mutation observer | |
4077 // watches only head for perf reasons, (2) it requires this script | |
4078 // to run before any imports have completed loading. | |
4079 if (useNative) { | |
4080 new MutationObserver(function(mxns) { | |
4081 for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) { | |
4082 if (m.addedNodes) { | |
4083 handleImports(m.addedNodes); | |
4084 } | |
4085 } | |
4086 }).observe(document.head, {childList: true}); | |
4087 | |
4088 function handleImports(nodes) { | |
4089 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { | |
4090 if (isImport(n)) { | |
4091 handleImport(n); | |
4092 } | |
4093 } | |
4094 } | |
4095 | |
4096 function isImport(element) { | |
4097 return element.localName === 'link' && element.rel === 'import'; | |
4098 } | |
4099 | |
4100 function handleImport(element) { | |
4101 var loaded = element.import; | |
4102 if (loaded) { | |
4103 markTargetLoaded({target: element}); | |
4104 } else { | |
4105 element.addEventListener('load', markTargetLoaded); | |
4106 element.addEventListener('error', markTargetLoaded); | |
4107 } | |
4108 } | |
4109 | |
4110 // make sure to catch any imports that are in the process of loading | |
4111 // when this script is run. | |
4112 (function() { | |
4113 if (document.readyState === 'loading') { | |
4114 var imports = document.querySelectorAll('link[rel=import]'); | |
4115 for (var i=0, l=imports.length, imp; (i<l) && (imp=imports[i]); i++) { | |
4116 handleImport(imp); | |
4117 } | |
4118 } | |
4119 })(); | |
4120 | |
4121 } | |
4122 | |
4123 // Fire the 'HTMLImportsLoaded' event when imports in document at load time | |
4124 // have loaded. This event is required to simulate the script blocking | |
4125 // behavior of native imports. A main document script that needs to be sure | |
4126 // imports have loaded should wait for this event. | |
4127 whenReady(function() { | |
4128 HTMLImports.ready = true; | |
4129 HTMLImports.readyTime = new Date().getTime(); | |
4130 rootDocument.dispatchEvent( | |
4131 new CustomEvent('HTMLImportsLoaded', {bubbles: true}) | |
4132 ); | |
4133 }); | |
4134 | |
4135 // exports | |
4136 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
4137 scope.useNative = useNative; | |
4138 scope.rootDocument = rootDocument; | |
4139 scope.whenReady = whenReady; | |
4140 scope.isIE = isIE; | |
4141 | |
4142 })(HTMLImports); | |
4143 | |
4144 (function(scope) { | |
4145 | |
4146 // TODO(sorvell): It's desireable to provide a default stylesheet | |
4147 // that's convenient for styling unresolved elements, but | |
4148 // it's cumbersome to have to include this manually in every page. | |
4149 // It would make sense to put inside some HTMLImport but | |
4150 // the HTMLImports polyfill does not allow loading of stylesheets | |
4151 // that block rendering. Therefore this injection is tolerated here. | |
4152 var style = document.createElement('style'); | |
4153 style.textContent = '' | |
4154 + 'body {' | |
4155 + 'transition: opacity ease-in 0.2s;' | |
4156 + ' } \n' | |
4157 + 'body[unresolved] {' | |
4158 + 'opacity: 0; display: block; overflow: hidden;' | |
4159 + ' } \n' | |
4160 ; | |
4161 var head = document.querySelector('head'); | |
4162 head.insertBefore(style, head.firstChild); | |
4163 | |
4164 })(Platform); | |
4165 | |
4166 /* | |
4167 Build only script. | |
4168 | |
4169 Ensures scripts needed for basic x-platform compatibility | |
4170 will be run when platform.js is not loaded. | |
4171 */ | |
4172 } | |
4173 (function(global) { | |
4174 'use strict'; | |
4175 | |
4176 var testingExposeCycleCount = global.testingExposeCycleCount; | |
4177 | |
4178 // Detect and do basic sanity checking on Object/Array.observe. | |
4179 function detectObjectObserve() { | |
4180 if (typeof Object.observe !== 'function' || | |
4181 typeof Array.observe !== 'function') { | |
4182 return false; | |
4183 } | |
4184 | |
4185 var records = []; | |
4186 | |
4187 function callback(recs) { | |
4188 records = recs; | |
4189 } | |
4190 | |
4191 var test = {}; | |
4192 var arr = []; | |
4193 Object.observe(test, callback); | |
4194 Array.observe(arr, callback); | |
4195 test.id = 1; | |
4196 test.id = 2; | |
4197 delete test.id; | |
4198 arr.push(1, 2); | |
4199 arr.length = 0; | |
4200 | |
4201 Object.deliverChangeRecords(callback); | |
4202 if (records.length !== 5) | |
4203 return false; | |
4204 | |
4205 if (records[0].type != 'add' || | |
4206 records[1].type != 'update' || | |
4207 records[2].type != 'delete' || | |
4208 records[3].type != 'splice' || | |
4209 records[4].type != 'splice') { | |
4210 return false; | |
4211 } | |
4212 | |
4213 Object.unobserve(test, callback); | |
4214 Array.unobserve(arr, callback); | |
4215 | |
4216 return true; | |
4217 } | |
4218 | |
4219 var hasObserve = detectObjectObserve(); | |
4220 | |
4221 function detectEval() { | |
4222 // Don't test for eval if we're running in a Chrome App environment. | |
4223 // We check for APIs set that only exist in a Chrome App context. | |
4224 if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { | |
4225 return false; | |
4226 } | |
4227 | |
4228 // Firefox OS Apps do not allow eval. This feature detection is very hacky | |
4229 // but even if some other platform adds support for this function this code | |
4230 // will continue to work. | |
4231 if (typeof navigator != 'undefined' && navigator.getDeviceStorage) { | |
4232 return false; | |
4233 } | |
4234 | |
4235 try { | |
4236 var f = new Function('', 'return true;'); | |
4237 return f(); | |
4238 } catch (ex) { | |
4239 return false; | |
4240 } | |
4241 } | |
4242 | |
4243 var hasEval = detectEval(); | |
4244 | |
4245 function isIndex(s) { | |
4246 return +s === s >>> 0 && s !== ''; | |
4247 } | |
4248 | |
4249 function toNumber(s) { | |
4250 return +s; | |
4251 } | |
4252 | |
4253 function isObject(obj) { | |
4254 return obj === Object(obj); | |
4255 } | |
4256 | |
4257 var numberIsNaN = global.Number.isNaN || function(value) { | |
4258 return typeof value === 'number' && global.isNaN(value); | |
4259 } | |
4260 | |
4261 function areSameValue(left, right) { | |
4262 if (left === right) | |
4263 return left !== 0 || 1 / left === 1 / right; | |
4264 if (numberIsNaN(left) && numberIsNaN(right)) | |
4265 return true; | |
4266 | |
4267 return left !== left && right !== right; | |
4268 } | |
4269 | |
4270 var createObject = ('__proto__' in {}) ? | |
4271 function(obj) { return obj; } : | |
4272 function(obj) { | |
4273 var proto = obj.__proto__; | |
4274 if (!proto) | |
4275 return obj; | |
4276 var newObject = Object.create(proto); | |
4277 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
4278 Object.defineProperty(newObject, name, | |
4279 Object.getOwnPropertyDescriptor(obj, name)); | |
4280 }); | |
4281 return newObject; | |
4282 }; | |
4283 | |
4284 var identStart = '[\$_a-zA-Z]'; | |
4285 var identPart = '[\$_a-zA-Z0-9]'; | |
4286 var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$'); | |
4287 | |
4288 function getPathCharType(char) { | |
4289 if (char === undefined) | |
4290 return 'eof'; | |
4291 | |
4292 var code = char.charCodeAt(0); | |
4293 | |
4294 switch(code) { | |
4295 case 0x5B: // [ | |
4296 case 0x5D: // ] | |
4297 case 0x2E: // . | |
4298 case 0x22: // " | |
4299 case 0x27: // ' | |
4300 case 0x30: // 0 | |
4301 return char; | |
4302 | |
4303 case 0x5F: // _ | |
4304 case 0x24: // $ | |
4305 return 'ident'; | |
4306 | |
4307 case 0x20: // Space | |
4308 case 0x09: // Tab | |
4309 case 0x0A: // Newline | |
4310 case 0x0D: // Return | |
4311 case 0xA0: // No-break space | |
4312 case 0xFEFF: // Byte Order Mark | |
4313 case 0x2028: // Line Separator | |
4314 case 0x2029: // Paragraph Separator | |
4315 return 'ws'; | |
4316 } | |
4317 | |
4318 // a-z, A-Z | |
4319 if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) | |
4320 return 'ident'; | |
4321 | |
4322 // 1-9 | |
4323 if (0x31 <= code && code <= 0x39) | |
4324 return 'number'; | |
4325 | |
4326 return 'else'; | |
4327 } | |
4328 | |
4329 var pathStateMachine = { | |
4330 'beforePath': { | |
4331 'ws': ['beforePath'], | |
4332 'ident': ['inIdent', 'append'], | |
4333 '[': ['beforeElement'], | |
4334 'eof': ['afterPath'] | |
4335 }, | |
4336 | |
4337 'inPath': { | |
4338 'ws': ['inPath'], | |
4339 '.': ['beforeIdent'], | |
4340 '[': ['beforeElement'], | |
4341 'eof': ['afterPath'] | |
4342 }, | |
4343 | |
4344 'beforeIdent': { | |
4345 'ws': ['beforeIdent'], | |
4346 'ident': ['inIdent', 'append'] | |
4347 }, | |
4348 | |
4349 'inIdent': { | |
4350 'ident': ['inIdent', 'append'], | |
4351 '0': ['inIdent', 'append'], | |
4352 'number': ['inIdent', 'append'], | |
4353 'ws': ['inPath', 'push'], | |
4354 '.': ['beforeIdent', 'push'], | |
4355 '[': ['beforeElement', 'push'], | |
4356 'eof': ['afterPath', 'push'] | |
4357 }, | |
4358 | |
4359 'beforeElement': { | |
4360 'ws': ['beforeElement'], | |
4361 '0': ['afterZero', 'append'], | |
4362 'number': ['inIndex', 'append'], | |
4363 "'": ['inSingleQuote', 'append', ''], | |
4364 '"': ['inDoubleQuote', 'append', ''] | |
4365 }, | |
4366 | |
4367 'afterZero': { | |
4368 'ws': ['afterElement', 'push'], | |
4369 ']': ['inPath', 'push'] | |
4370 }, | |
4371 | |
4372 'inIndex': { | |
4373 '0': ['inIndex', 'append'], | |
4374 'number': ['inIndex', 'append'], | |
4375 'ws': ['afterElement'], | |
4376 ']': ['inPath', 'push'] | |
4377 }, | |
4378 | |
4379 'inSingleQuote': { | |
4380 "'": ['afterElement'], | |
4381 'eof': ['error'], | |
4382 'else': ['inSingleQuote', 'append'] | |
4383 }, | |
4384 | |
4385 'inDoubleQuote': { | |
4386 '"': ['afterElement'], | |
4387 'eof': ['error'], | |
4388 'else': ['inDoubleQuote', 'append'] | |
4389 }, | |
4390 | |
4391 'afterElement': { | |
4392 'ws': ['afterElement'], | |
4393 ']': ['inPath', 'push'] | |
4394 } | |
4395 } | |
4396 | |
4397 function noop() {} | |
4398 | |
4399 function parsePath(path) { | |
4400 var keys = []; | |
4401 var index = -1; | |
4402 var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath'; | |
4403 | |
4404 var actions = { | |
4405 push: function() { | |
4406 if (key === undefined) | |
4407 return; | |
4408 | |
4409 keys.push(key); | |
4410 key = undefined; | |
4411 }, | |
4412 | |
4413 append: function() { | |
4414 if (key === undefined) | |
4415 key = newChar | |
4416 else | |
4417 key += newChar; | |
4418 } | |
4419 }; | |
4420 | |
4421 function maybeUnescapeQuote() { | |
4422 if (index >= path.length) | |
4423 return; | |
4424 | |
4425 var nextChar = path[index + 1]; | |
4426 if ((mode == 'inSingleQuote' && nextChar == "'") || | |
4427 (mode == 'inDoubleQuote' && nextChar == '"')) { | |
4428 index++; | |
4429 newChar = nextChar; | |
4430 actions.append(); | |
4431 return true; | |
4432 } | |
4433 } | |
4434 | |
4435 while (mode) { | |
4436 index++; | |
4437 c = path[index]; | |
4438 | |
4439 if (c == '\\' && maybeUnescapeQuote(mode)) | |
4440 continue; | |
4441 | |
4442 type = getPathCharType(c); | |
4443 typeMap = pathStateMachine[mode]; | |
4444 transition = typeMap[type] || typeMap['else'] || 'error'; | |
4445 | |
4446 if (transition == 'error') | |
4447 return; // parse error; | |
4448 | |
4449 mode = transition[0]; | |
4450 action = actions[transition[1]] || noop; | |
4451 newChar = transition[2] === undefined ? c : transition[2]; | |
4452 action(); | |
4453 | |
4454 if (mode === 'afterPath') { | |
4455 return keys; | |
4456 } | |
4457 } | |
4458 | |
4459 return; // parse error | |
4460 } | |
4461 | |
4462 function isIdent(s) { | |
4463 return identRegExp.test(s); | |
4464 } | |
4465 | |
4466 var constructorIsPrivate = {}; | |
4467 | |
4468 function Path(parts, privateToken) { | |
4469 if (privateToken !== constructorIsPrivate) | |
4470 throw Error('Use Path.get to retrieve path objects'); | |
4471 | |
4472 for (var i = 0; i < parts.length; i++) { | |
4473 this.push(String(parts[i])); | |
4474 } | |
4475 | |
4476 if (hasEval && this.length) { | |
4477 this.getValueFrom = this.compiledGetValueFromFn(); | |
4478 } | |
4479 } | |
4480 | |
4481 // TODO(rafaelw): Make simple LRU cache | |
4482 var pathCache = {}; | |
4483 | |
4484 function getPath(pathString) { | |
4485 if (pathString instanceof Path) | |
4486 return pathString; | |
4487 | |
4488 if (pathString == null || pathString.length == 0) | |
4489 pathString = ''; | |
4490 | |
4491 if (typeof pathString != 'string') { | |
4492 if (isIndex(pathString.length)) { | |
4493 // Constructed with array-like (pre-parsed) keys | |
4494 return new Path(pathString, constructorIsPrivate); | |
4495 } | |
4496 | |
4497 pathString = String(pathString); | |
4498 } | |
4499 | |
4500 var path = pathCache[pathString]; | |
4501 if (path) | |
4502 return path; | |
4503 | |
4504 var parts = parsePath(pathString); | |
4505 if (!parts) | |
4506 return invalidPath; | |
4507 | |
4508 var path = new Path(parts, constructorIsPrivate); | |
4509 pathCache[pathString] = path; | |
4510 return path; | |
4511 } | |
4512 | |
4513 Path.get = getPath; | |
4514 | |
4515 function formatAccessor(key) { | |
4516 if (isIndex(key)) { | |
4517 return '[' + key + ']'; | |
4518 } else { | |
4519 return '["' + key.replace(/"/g, '\\"') + '"]'; | |
4520 } | |
4521 } | |
4522 | |
4523 Path.prototype = createObject({ | |
4524 __proto__: [], | |
4525 valid: true, | |
4526 | |
4527 toString: function() { | |
4528 var pathString = ''; | |
4529 for (var i = 0; i < this.length; i++) { | |
4530 var key = this[i]; | |
4531 if (isIdent(key)) { | |
4532 pathString += i ? '.' + key : key; | |
4533 } else { | |
4534 pathString += formatAccessor(key); | |
4535 } | |
4536 } | |
4537 | |
4538 return pathString; | |
4539 }, | |
4540 | |
4541 getValueFrom: function(obj, directObserver) { | |
4542 for (var i = 0; i < this.length; i++) { | |
4543 if (obj == null) | |
4544 return; | |
4545 obj = obj[this[i]]; | |
4546 } | |
4547 return obj; | |
4548 }, | |
4549 | |
4550 iterateObjects: function(obj, observe) { | |
4551 for (var i = 0; i < this.length; i++) { | |
4552 if (i) | |
4553 obj = obj[this[i - 1]]; | |
4554 if (!isObject(obj)) | |
4555 return; | |
4556 observe(obj, this[i]); | |
4557 } | |
4558 }, | |
4559 | |
4560 compiledGetValueFromFn: function() { | |
4561 var str = ''; | |
4562 var pathString = 'obj'; | |
4563 str += 'if (obj != null'; | |
4564 var i = 0; | |
4565 var key; | |
4566 for (; i < (this.length - 1); i++) { | |
4567 key = this[i]; | |
4568 pathString += isIdent(key) ? '.' + key : formatAccessor(key); | |
4569 str += ' &&\n ' + pathString + ' != null'; | |
4570 } | |
4571 str += ')\n'; | |
4572 | |
4573 var key = this[i]; | |
4574 pathString += isIdent(key) ? '.' + key : formatAccessor(key); | |
4575 | |
4576 str += ' return ' + pathString + ';\nelse\n return undefined;'; | |
4577 return new Function('obj', str); | |
4578 }, | |
4579 | |
4580 setValueFrom: function(obj, value) { | |
4581 if (!this.length) | |
4582 return false; | |
4583 | |
4584 for (var i = 0; i < this.length - 1; i++) { | |
4585 if (!isObject(obj)) | |
4586 return false; | |
4587 obj = obj[this[i]]; | |
4588 } | |
4589 | |
4590 if (!isObject(obj)) | |
4591 return false; | |
4592 | |
4593 obj[this[i]] = value; | |
4594 return true; | |
4595 } | |
4596 }); | |
4597 | |
4598 var invalidPath = new Path('', constructorIsPrivate); | |
4599 invalidPath.valid = false; | |
4600 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; | |
4601 | |
4602 var MAX_DIRTY_CHECK_CYCLES = 1000; | |
4603 | |
4604 function dirtyCheck(observer) { | |
4605 var cycles = 0; | |
4606 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { | |
4607 cycles++; | |
4608 } | |
4609 if (testingExposeCycleCount) | |
4610 global.dirtyCheckCycleCount = cycles; | |
4611 | |
4612 return cycles > 0; | |
4613 } | |
4614 | |
4615 function objectIsEmpty(object) { | |
4616 for (var prop in object) | |
4617 return false; | |
4618 return true; | |
4619 } | |
4620 | |
4621 function diffIsEmpty(diff) { | |
4622 return objectIsEmpty(diff.added) && | |
4623 objectIsEmpty(diff.removed) && | |
4624 objectIsEmpty(diff.changed); | |
4625 } | |
4626 | |
4627 function diffObjectFromOldObject(object, oldObject) { | |
4628 var added = {}; | |
4629 var removed = {}; | |
4630 var changed = {}; | |
4631 | |
4632 for (var prop in oldObject) { | |
4633 var newValue = object[prop]; | |
4634 | |
4635 if (newValue !== undefined && newValue === oldObject[prop]) | |
4636 continue; | |
4637 | |
4638 if (!(prop in object)) { | |
4639 removed[prop] = undefined; | |
4640 continue; | |
4641 } | |
4642 | |
4643 if (newValue !== oldObject[prop]) | |
4644 changed[prop] = newValue; | |
4645 } | |
4646 | |
4647 for (var prop in object) { | |
4648 if (prop in oldObject) | |
4649 continue; | |
4650 | |
4651 added[prop] = object[prop]; | |
4652 } | |
4653 | |
4654 if (Array.isArray(object) && object.length !== oldObject.length) | |
4655 changed.length = object.length; | |
4656 | |
4657 return { | |
4658 added: added, | |
4659 removed: removed, | |
4660 changed: changed | |
4661 }; | |
4662 } | |
4663 | |
4664 var eomTasks = []; | |
4665 function runEOMTasks() { | |
4666 if (!eomTasks.length) | |
4667 return false; | |
4668 | |
4669 for (var i = 0; i < eomTasks.length; i++) { | |
4670 eomTasks[i](); | |
4671 } | |
4672 eomTasks.length = 0; | |
4673 return true; | |
4674 } | |
4675 | |
4676 var runEOM = hasObserve ? (function(){ | |
4677 return function(fn) { | |
4678 return Promise.resolve().then(fn); | |
4679 } | |
4680 })() : | |
4681 (function() { | |
4682 return function(fn) { | |
4683 eomTasks.push(fn); | |
4684 }; | |
4685 })(); | |
4686 | |
4687 var observedObjectCache = []; | |
4688 | |
4689 function newObservedObject() { | |
4690 var observer; | |
4691 var object; | |
4692 var discardRecords = false; | |
4693 var first = true; | |
4694 | |
4695 function callback(records) { | |
4696 if (observer && observer.state_ === OPENED && !discardRecords) | |
4697 observer.check_(records); | |
4698 } | |
4699 | |
4700 return { | |
4701 open: function(obs) { | |
4702 if (observer) | |
4703 throw Error('ObservedObject in use'); | |
4704 | |
4705 if (!first) | |
4706 Object.deliverChangeRecords(callback); | |
4707 | |
4708 observer = obs; | |
4709 first = false; | |
4710 }, | |
4711 observe: function(obj, arrayObserve) { | |
4712 object = obj; | |
4713 if (arrayObserve) | |
4714 Array.observe(object, callback); | |
4715 else | |
4716 Object.observe(object, callback); | |
4717 }, | |
4718 deliver: function(discard) { | |
4719 discardRecords = discard; | |
4720 Object.deliverChangeRecords(callback); | |
4721 discardRecords = false; | |
4722 }, | |
4723 close: function() { | |
4724 observer = undefined; | |
4725 Object.unobserve(object, callback); | |
4726 observedObjectCache.push(this); | |
4727 } | |
4728 }; | |
4729 } | |
4730 | |
4731 /* | |
4732 * The observedSet abstraction is a perf optimization which reduces the total | |
4733 * number of Object.observe observations of a set of objects. The idea is that | |
4734 * groups of Observers will have some object dependencies in common and this | |
4735 * observed set ensures that each object in the transitive closure of | |
4736 * dependencies is only observed once. The observedSet acts as a write barrier | |
4737 * such that whenever any change comes through, all Observers are checked for | |
4738 * changed values. | |
4739 * | |
4740 * Note that this optimization is explicitly moving work from setup-time to | |
4741 * change-time. | |
4742 * | |
4743 * TODO(rafaelw): Implement "garbage collection". In order to move work off | |
4744 * the critical path, when Observers are closed, their observed objects are | |
4745 * not Object.unobserve(d). As a result, it's possible that if the observedSet | |
4746 * is kept open, but some Observers have been closed, it could cause "leaks" | |
4747 * (prevent otherwise collectable objects from being collected). At some | |
4748 * point, we should implement incremental "gc" which keeps a list of | |
4749 * observedSets which may need clean-up and does small amounts of cleanup on a | |
4750 * timeout until all is clean. | |
4751 */ | |
4752 | |
4753 function getObservedObject(observer, object, arrayObserve) { | |
4754 var dir = observedObjectCache.pop() || newObservedObject(); | |
4755 dir.open(observer); | |
4756 dir.observe(object, arrayObserve); | |
4757 return dir; | |
4758 } | |
4759 | |
4760 var observedSetCache = []; | |
4761 | |
4762 function newObservedSet() { | |
4763 var observerCount = 0; | |
4764 var observers = []; | |
4765 var objects = []; | |
4766 var rootObj; | |
4767 var rootObjProps; | |
4768 | |
4769 function observe(obj, prop) { | |
4770 if (!obj) | |
4771 return; | |
4772 | |
4773 if (obj === rootObj) | |
4774 rootObjProps[prop] = true; | |
4775 | |
4776 if (objects.indexOf(obj) < 0) { | |
4777 objects.push(obj); | |
4778 Object.observe(obj, callback); | |
4779 } | |
4780 | |
4781 observe(Object.getPrototypeOf(obj), prop); | |
4782 } | |
4783 | |
4784 function allRootObjNonObservedProps(recs) { | |
4785 for (var i = 0; i < recs.length; i++) { | |
4786 var rec = recs[i]; | |
4787 if (rec.object !== rootObj || | |
4788 rootObjProps[rec.name] || | |
4789 rec.type === 'setPrototype') { | |
4790 return false; | |
4791 } | |
4792 } | |
4793 return true; | |
4794 } | |
4795 | |
4796 function callback(recs) { | |
4797 if (allRootObjNonObservedProps(recs)) | |
4798 return; | |
4799 | |
4800 var observer; | |
4801 for (var i = 0; i < observers.length; i++) { | |
4802 observer = observers[i]; | |
4803 if (observer.state_ == OPENED) { | |
4804 observer.iterateObjects_(observe); | |
4805 } | |
4806 } | |
4807 | |
4808 for (var i = 0; i < observers.length; i++) { | |
4809 observer = observers[i]; | |
4810 if (observer.state_ == OPENED) { | |
4811 observer.check_(); | |
4812 } | |
4813 } | |
4814 } | |
4815 | |
4816 var record = { | |
4817 objects: objects, | |
4818 get rootObject() { return rootObj; }, | |
4819 set rootObject(value) { | |
4820 rootObj = value; | |
4821 rootObjProps = {}; | |
4822 }, | |
4823 open: function(obs, object) { | |
4824 observers.push(obs); | |
4825 observerCount++; | |
4826 obs.iterateObjects_(observe); | |
4827 }, | |
4828 close: function(obs) { | |
4829 observerCount--; | |
4830 if (observerCount > 0) { | |
4831 return; | |
4832 } | |
4833 | |
4834 for (var i = 0; i < objects.length; i++) { | |
4835 Object.unobserve(objects[i], callback); | |
4836 Observer.unobservedCount++; | |
4837 } | |
4838 | |
4839 observers.length = 0; | |
4840 objects.length = 0; | |
4841 rootObj = undefined; | |
4842 rootObjProps = undefined; | |
4843 observedSetCache.push(this); | |
4844 if (lastObservedSet === this) | |
4845 lastObservedSet = null; | |
4846 }, | |
4847 }; | |
4848 | |
4849 return record; | |
4850 } | |
4851 | |
4852 var lastObservedSet; | |
4853 | |
4854 function getObservedSet(observer, obj) { | |
4855 if (!lastObservedSet || lastObservedSet.rootObject !== obj) { | |
4856 lastObservedSet = observedSetCache.pop() || newObservedSet(); | |
4857 lastObservedSet.rootObject = obj; | |
4858 } | |
4859 lastObservedSet.open(observer, obj); | |
4860 return lastObservedSet; | |
4861 } | |
4862 | |
4863 var UNOPENED = 0; | |
4864 var OPENED = 1; | |
4865 var CLOSED = 2; | |
4866 var RESETTING = 3; | |
4867 | |
4868 var nextObserverId = 1; | |
4869 | |
4870 function Observer() { | |
4871 this.state_ = UNOPENED; | |
4872 this.callback_ = undefined; | |
4873 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef | |
4874 this.directObserver_ = undefined; | |
4875 this.value_ = undefined; | |
4876 this.id_ = nextObserverId++; | |
4877 } | |
4878 | |
4879 Observer.prototype = { | |
4880 open: function(callback, target) { | |
4881 if (this.state_ != UNOPENED) | |
4882 throw Error('Observer has already been opened.'); | |
4883 | |
4884 addToAll(this); | |
4885 this.callback_ = callback; | |
4886 this.target_ = target; | |
4887 this.connect_(); | |
4888 this.state_ = OPENED; | |
4889 return this.value_; | |
4890 }, | |
4891 | |
4892 close: function() { | |
4893 if (this.state_ != OPENED) | |
4894 return; | |
4895 | |
4896 removeFromAll(this); | |
4897 this.disconnect_(); | |
4898 this.value_ = undefined; | |
4899 this.callback_ = undefined; | |
4900 this.target_ = undefined; | |
4901 this.state_ = CLOSED; | |
4902 }, | |
4903 | |
4904 deliver: function() { | |
4905 if (this.state_ != OPENED) | |
4906 return; | |
4907 | |
4908 dirtyCheck(this); | |
4909 }, | |
4910 | |
4911 report_: function(changes) { | |
4912 try { | |
4913 this.callback_.apply(this.target_, changes); | |
4914 } catch (ex) { | |
4915 Observer._errorThrownDuringCallback = true; | |
4916 console.error('Exception caught during observer callback: ' + | |
4917 (ex.stack || ex)); | |
4918 } | |
4919 }, | |
4920 | |
4921 discardChanges: function() { | |
4922 this.check_(undefined, true); | |
4923 return this.value_; | |
4924 } | |
4925 } | |
4926 | |
4927 var collectObservers = !hasObserve; | |
4928 var allObservers; | |
4929 Observer._allObserversCount = 0; | |
4930 | |
4931 if (collectObservers) { | |
4932 allObservers = []; | |
4933 } | |
4934 | |
4935 function addToAll(observer) { | |
4936 Observer._allObserversCount++; | |
4937 if (!collectObservers) | |
4938 return; | |
4939 | |
4940 allObservers.push(observer); | |
4941 } | |
4942 | |
4943 function removeFromAll(observer) { | |
4944 Observer._allObserversCount--; | |
4945 } | |
4946 | |
4947 var runningMicrotaskCheckpoint = false; | |
4948 | |
4949 global.Platform = global.Platform || {}; | |
4950 | |
4951 global.Platform.performMicrotaskCheckpoint = function() { | |
4952 if (runningMicrotaskCheckpoint) | |
4953 return; | |
4954 | |
4955 if (!collectObservers) | |
4956 return; | |
4957 | |
4958 runningMicrotaskCheckpoint = true; | |
4959 | |
4960 var cycles = 0; | |
4961 var anyChanged, toCheck; | |
4962 | |
4963 do { | |
4964 cycles++; | |
4965 toCheck = allObservers; | |
4966 allObservers = []; | |
4967 anyChanged = false; | |
4968 | |
4969 for (var i = 0; i < toCheck.length; i++) { | |
4970 var observer = toCheck[i]; | |
4971 if (observer.state_ != OPENED) | |
4972 continue; | |
4973 | |
4974 if (observer.check_()) | |
4975 anyChanged = true; | |
4976 | |
4977 allObservers.push(observer); | |
4978 } | |
4979 if (runEOMTasks()) | |
4980 anyChanged = true; | |
4981 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); | |
4982 | |
4983 if (testingExposeCycleCount) | |
4984 global.dirtyCheckCycleCount = cycles; | |
4985 | |
4986 runningMicrotaskCheckpoint = false; | |
4987 }; | |
4988 | |
4989 if (collectObservers) { | |
4990 global.Platform.clearObservers = function() { | |
4991 allObservers = []; | |
4992 }; | |
4993 } | |
4994 | |
4995 function ObjectObserver(object) { | |
4996 Observer.call(this); | |
4997 this.value_ = object; | |
4998 this.oldObject_ = undefined; | |
4999 } | |
5000 | |
5001 ObjectObserver.prototype = createObject({ | |
5002 __proto__: Observer.prototype, | |
5003 | |
5004 arrayObserve: false, | |
5005 | |
5006 connect_: function(callback, target) { | |
5007 if (hasObserve) { | |
5008 this.directObserver_ = getObservedObject(this, this.value_, | |
5009 this.arrayObserve); | |
5010 } else { | |
5011 this.oldObject_ = this.copyObject(this.value_); | |
5012 } | |
5013 | |
5014 }, | |
5015 | |
5016 copyObject: function(object) { | |
5017 var copy = Array.isArray(object) ? [] : {}; | |
5018 for (var prop in object) { | |
5019 copy[prop] = object[prop]; | |
5020 }; | |
5021 if (Array.isArray(object)) | |
5022 copy.length = object.length; | |
5023 return copy; | |
5024 }, | |
5025 | |
5026 check_: function(changeRecords, skipChanges) { | |
5027 var diff; | |
5028 var oldValues; | |
5029 if (hasObserve) { | |
5030 if (!changeRecords) | |
5031 return false; | |
5032 | |
5033 oldValues = {}; | |
5034 diff = diffObjectFromChangeRecords(this.value_, changeRecords, | |
5035 oldValues); | |
5036 } else { | |
5037 oldValues = this.oldObject_; | |
5038 diff = diffObjectFromOldObject(this.value_, this.oldObject_); | |
5039 } | |
5040 | |
5041 if (diffIsEmpty(diff)) | |
5042 return false; | |
5043 | |
5044 if (!hasObserve) | |
5045 this.oldObject_ = this.copyObject(this.value_); | |
5046 | |
5047 this.report_([ | |
5048 diff.added || {}, | |
5049 diff.removed || {}, | |
5050 diff.changed || {}, | |
5051 function(property) { | |
5052 return oldValues[property]; | |
5053 } | |
5054 ]); | |
5055 | |
5056 return true; | |
5057 }, | |
5058 | |
5059 disconnect_: function() { | |
5060 if (hasObserve) { | |
5061 this.directObserver_.close(); | |
5062 this.directObserver_ = undefined; | |
5063 } else { | |
5064 this.oldObject_ = undefined; | |
5065 } | |
5066 }, | |
5067 | |
5068 deliver: function() { | |
5069 if (this.state_ != OPENED) | |
5070 return; | |
5071 | |
5072 if (hasObserve) | |
5073 this.directObserver_.deliver(false); | |
5074 else | |
5075 dirtyCheck(this); | |
5076 }, | |
5077 | |
5078 discardChanges: function() { | |
5079 if (this.directObserver_) | |
5080 this.directObserver_.deliver(true); | |
5081 else | |
5082 this.oldObject_ = this.copyObject(this.value_); | |
5083 | |
5084 return this.value_; | |
5085 } | |
5086 }); | |
5087 | |
5088 function ArrayObserver(array) { | |
5089 if (!Array.isArray(array)) | |
5090 throw Error('Provided object is not an Array'); | |
5091 ObjectObserver.call(this, array); | |
5092 } | |
5093 | |
5094 ArrayObserver.prototype = createObject({ | |
5095 | |
5096 __proto__: ObjectObserver.prototype, | |
5097 | |
5098 arrayObserve: true, | |
5099 | |
5100 copyObject: function(arr) { | |
5101 return arr.slice(); | |
5102 }, | |
5103 | |
5104 check_: function(changeRecords) { | |
5105 var splices; | |
5106 if (hasObserve) { | |
5107 if (!changeRecords) | |
5108 return false; | |
5109 splices = projectArraySplices(this.value_, changeRecords); | |
5110 } else { | |
5111 splices = calcSplices(this.value_, 0, this.value_.length, | |
5112 this.oldObject_, 0, this.oldObject_.length); | |
5113 } | |
5114 | |
5115 if (!splices || !splices.length) | |
5116 return false; | |
5117 | |
5118 if (!hasObserve) | |
5119 this.oldObject_ = this.copyObject(this.value_); | |
5120 | |
5121 this.report_([splices]); | |
5122 return true; | |
5123 } | |
5124 }); | |
5125 | |
5126 ArrayObserver.applySplices = function(previous, current, splices) { | |
5127 splices.forEach(function(splice) { | |
5128 var spliceArgs = [splice.index, splice.removed.length]; | |
5129 var addIndex = splice.index; | |
5130 while (addIndex < splice.index + splice.addedCount) { | |
5131 spliceArgs.push(current[addIndex]); | |
5132 addIndex++; | |
5133 } | |
5134 | |
5135 Array.prototype.splice.apply(previous, spliceArgs); | |
5136 }); | |
5137 }; | |
5138 | |
5139 function PathObserver(object, path) { | |
5140 Observer.call(this); | |
5141 | |
5142 this.object_ = object; | |
5143 this.path_ = getPath(path); | |
5144 this.directObserver_ = undefined; | |
5145 } | |
5146 | |
5147 PathObserver.prototype = createObject({ | |
5148 __proto__: Observer.prototype, | |
5149 | |
5150 get path() { | |
5151 return this.path_; | |
5152 }, | |
5153 | |
5154 connect_: function() { | |
5155 if (hasObserve) | |
5156 this.directObserver_ = getObservedSet(this, this.object_); | |
5157 | |
5158 this.check_(undefined, true); | |
5159 }, | |
5160 | |
5161 disconnect_: function() { | |
5162 this.value_ = undefined; | |
5163 | |
5164 if (this.directObserver_) { | |
5165 this.directObserver_.close(this); | |
5166 this.directObserver_ = undefined; | |
5167 } | |
5168 }, | |
5169 | |
5170 iterateObjects_: function(observe) { | |
5171 this.path_.iterateObjects(this.object_, observe); | |
5172 }, | |
5173 | |
5174 check_: function(changeRecords, skipChanges) { | |
5175 var oldValue = this.value_; | |
5176 this.value_ = this.path_.getValueFrom(this.object_); | |
5177 if (skipChanges || areSameValue(this.value_, oldValue)) | |
5178 return false; | |
5179 | |
5180 this.report_([this.value_, oldValue, this]); | |
5181 return true; | |
5182 }, | |
5183 | |
5184 setValue: function(newValue) { | |
5185 if (this.path_) | |
5186 this.path_.setValueFrom(this.object_, newValue); | |
5187 } | |
5188 }); | |
5189 | |
5190 function CompoundObserver(reportChangesOnOpen) { | |
5191 Observer.call(this); | |
5192 | |
5193 this.reportChangesOnOpen_ = reportChangesOnOpen; | |
5194 this.value_ = []; | |
5195 this.directObserver_ = undefined; | |
5196 this.observed_ = []; | |
5197 } | |
5198 | |
5199 var observerSentinel = {}; | |
5200 | |
5201 CompoundObserver.prototype = createObject({ | |
5202 __proto__: Observer.prototype, | |
5203 | |
5204 connect_: function() { | |
5205 if (hasObserve) { | |
5206 var object; | |
5207 var needsDirectObserver = false; | |
5208 for (var i = 0; i < this.observed_.length; i += 2) { | |
5209 object = this.observed_[i] | |
5210 if (object !== observerSentinel) { | |
5211 needsDirectObserver = true; | |
5212 break; | |
5213 } | |
5214 } | |
5215 | |
5216 if (needsDirectObserver) | |
5217 this.directObserver_ = getObservedSet(this, object); | |
5218 } | |
5219 | |
5220 this.check_(undefined, !this.reportChangesOnOpen_); | |
5221 }, | |
5222 | |
5223 disconnect_: function() { | |
5224 for (var i = 0; i < this.observed_.length; i += 2) { | |
5225 if (this.observed_[i] === observerSentinel) | |
5226 this.observed_[i + 1].close(); | |
5227 } | |
5228 this.observed_.length = 0; | |
5229 this.value_.length = 0; | |
5230 | |
5231 if (this.directObserver_) { | |
5232 this.directObserver_.close(this); | |
5233 this.directObserver_ = undefined; | |
5234 } | |
5235 }, | |
5236 | |
5237 addPath: function(object, path) { | |
5238 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
5239 throw Error('Cannot add paths once started.'); | |
5240 | |
5241 var path = getPath(path); | |
5242 this.observed_.push(object, path); | |
5243 if (!this.reportChangesOnOpen_) | |
5244 return; | |
5245 var index = this.observed_.length / 2 - 1; | |
5246 this.value_[index] = path.getValueFrom(object); | |
5247 }, | |
5248 | |
5249 addObserver: function(observer) { | |
5250 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
5251 throw Error('Cannot add observers once started.'); | |
5252 | |
5253 this.observed_.push(observerSentinel, observer); | |
5254 if (!this.reportChangesOnOpen_) | |
5255 return; | |
5256 var index = this.observed_.length / 2 - 1; | |
5257 this.value_[index] = observer.open(this.deliver, this); | |
5258 }, | |
5259 | |
5260 startReset: function() { | |
5261 if (this.state_ != OPENED) | |
5262 throw Error('Can only reset while open'); | |
5263 | |
5264 this.state_ = RESETTING; | |
5265 this.disconnect_(); | |
5266 }, | |
5267 | |
5268 finishReset: function() { | |
5269 if (this.state_ != RESETTING) | |
5270 throw Error('Can only finishReset after startReset'); | |
5271 this.state_ = OPENED; | |
5272 this.connect_(); | |
5273 | |
5274 return this.value_; | |
5275 }, | |
5276 | |
5277 iterateObjects_: function(observe) { | |
5278 var object; | |
5279 for (var i = 0; i < this.observed_.length; i += 2) { | |
5280 object = this.observed_[i] | |
5281 if (object !== observerSentinel) | |
5282 this.observed_[i + 1].iterateObjects(object, observe) | |
5283 } | |
5284 }, | |
5285 | |
5286 check_: function(changeRecords, skipChanges) { | |
5287 var oldValues; | |
5288 for (var i = 0; i < this.observed_.length; i += 2) { | |
5289 var object = this.observed_[i]; | |
5290 var path = this.observed_[i+1]; | |
5291 var value; | |
5292 if (object === observerSentinel) { | |
5293 var observable = path; | |
5294 value = this.state_ === UNOPENED ? | |
5295 observable.open(this.deliver, this) : | |
5296 observable.discardChanges(); | |
5297 } else { | |
5298 value = path.getValueFrom(object); | |
5299 } | |
5300 | |
5301 if (skipChanges) { | |
5302 this.value_[i / 2] = value; | |
5303 continue; | |
5304 } | |
5305 | |
5306 if (areSameValue(value, this.value_[i / 2])) | |
5307 continue; | |
5308 | |
5309 oldValues = oldValues || []; | |
5310 oldValues[i / 2] = this.value_[i / 2]; | |
5311 this.value_[i / 2] = value; | |
5312 } | |
5313 | |
5314 if (!oldValues) | |
5315 return false; | |
5316 | |
5317 // TODO(rafaelw): Having observed_ as the third callback arg here is | |
5318 // pretty lame API. Fix. | |
5319 this.report_([this.value_, oldValues, this.observed_]); | |
5320 return true; | |
5321 } | |
5322 }); | |
5323 | |
5324 function identFn(value) { return value; } | |
5325 | |
5326 function ObserverTransform(observable, getValueFn, setValueFn, | |
5327 dontPassThroughSet) { | |
5328 this.callback_ = undefined; | |
5329 this.target_ = undefined; | |
5330 this.value_ = undefined; | |
5331 this.observable_ = observable; | |
5332 this.getValueFn_ = getValueFn || identFn; | |
5333 this.setValueFn_ = setValueFn || identFn; | |
5334 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this | |
5335 // at the moment because of a bug in it's dependency tracking. | |
5336 this.dontPassThroughSet_ = dontPassThroughSet; | |
5337 } | |
5338 | |
5339 ObserverTransform.prototype = { | |
5340 open: function(callback, target) { | |
5341 this.callback_ = callback; | |
5342 this.target_ = target; | |
5343 this.value_ = | |
5344 this.getValueFn_(this.observable_.open(this.observedCallback_, this)); | |
5345 return this.value_; | |
5346 }, | |
5347 | |
5348 observedCallback_: function(value) { | |
5349 value = this.getValueFn_(value); | |
5350 if (areSameValue(value, this.value_)) | |
5351 return; | |
5352 var oldValue = this.value_; | |
5353 this.value_ = value; | |
5354 this.callback_.call(this.target_, this.value_, oldValue); | |
5355 }, | |
5356 | |
5357 discardChanges: function() { | |
5358 this.value_ = this.getValueFn_(this.observable_.discardChanges()); | |
5359 return this.value_; | |
5360 }, | |
5361 | |
5362 deliver: function() { | |
5363 return this.observable_.deliver(); | |
5364 }, | |
5365 | |
5366 setValue: function(value) { | |
5367 value = this.setValueFn_(value); | |
5368 if (!this.dontPassThroughSet_ && this.observable_.setValue) | |
5369 return this.observable_.setValue(value); | |
5370 }, | |
5371 | |
5372 close: function() { | |
5373 if (this.observable_) | |
5374 this.observable_.close(); | |
5375 this.callback_ = undefined; | |
5376 this.target_ = undefined; | |
5377 this.observable_ = undefined; | |
5378 this.value_ = undefined; | |
5379 this.getValueFn_ = undefined; | |
5380 this.setValueFn_ = undefined; | |
5381 } | |
5382 } | |
5383 | |
5384 var expectedRecordTypes = { | |
5385 add: true, | |
5386 update: true, | |
5387 delete: true | |
5388 }; | |
5389 | |
5390 function diffObjectFromChangeRecords(object, changeRecords, oldValues) { | |
5391 var added = {}; | |
5392 var removed = {}; | |
5393 | |
5394 for (var i = 0; i < changeRecords.length; i++) { | |
5395 var record = changeRecords[i]; | |
5396 if (!expectedRecordTypes[record.type]) { | |
5397 console.error('Unknown changeRecord type: ' + record.type); | |
5398 console.error(record); | |
5399 continue; | |
5400 } | |
5401 | |
5402 if (!(record.name in oldValues)) | |
5403 oldValues[record.name] = record.oldValue; | |
5404 | |
5405 if (record.type == 'update') | |
5406 continue; | |
5407 | |
5408 if (record.type == 'add') { | |
5409 if (record.name in removed) | |
5410 delete removed[record.name]; | |
5411 else | |
5412 added[record.name] = true; | |
5413 | |
5414 continue; | |
5415 } | |
5416 | |
5417 // type = 'delete' | |
5418 if (record.name in added) { | |
5419 delete added[record.name]; | |
5420 delete oldValues[record.name]; | |
5421 } else { | |
5422 removed[record.name] = true; | |
5423 } | |
5424 } | |
5425 | |
5426 for (var prop in added) | |
5427 added[prop] = object[prop]; | |
5428 | |
5429 for (var prop in removed) | |
5430 removed[prop] = undefined; | |
5431 | |
5432 var changed = {}; | |
5433 for (var prop in oldValues) { | |
5434 if (prop in added || prop in removed) | |
5435 continue; | |
5436 | |
5437 var newValue = object[prop]; | |
5438 if (oldValues[prop] !== newValue) | |
5439 changed[prop] = newValue; | |
5440 } | |
5441 | |
5442 return { | |
5443 added: added, | |
5444 removed: removed, | |
5445 changed: changed | |
5446 }; | |
5447 } | |
5448 | |
5449 function newSplice(index, removed, addedCount) { | |
5450 return { | |
5451 index: index, | |
5452 removed: removed, | |
5453 addedCount: addedCount | |
5454 }; | |
5455 } | |
5456 | |
5457 var EDIT_LEAVE = 0; | |
5458 var EDIT_UPDATE = 1; | |
5459 var EDIT_ADD = 2; | |
5460 var EDIT_DELETE = 3; | |
5461 | |
5462 function ArraySplice() {} | |
5463 | |
5464 ArraySplice.prototype = { | |
5465 | |
5466 // Note: This function is *based* on the computation of the Levenshtein | |
5467 // "edit" distance. The one change is that "updates" are treated as two | |
5468 // edits - not one. With Array splices, an update is really a delete | |
5469 // followed by an add. By retaining this, we optimize for "keeping" the | |
5470 // maximum array items in the original array. For example: | |
5471 // | |
5472 // 'xxxx123' -> '123yyyy' | |
5473 // | |
5474 // With 1-edit updates, the shortest path would be just to update all seven | |
5475 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This | |
5476 // leaves the substring '123' intact. | |
5477 calcEditDistances: function(current, currentStart, currentEnd, | |
5478 old, oldStart, oldEnd) { | |
5479 // "Deletion" columns | |
5480 var rowCount = oldEnd - oldStart + 1; | |
5481 var columnCount = currentEnd - currentStart + 1; | |
5482 var distances = new Array(rowCount); | |
5483 | |
5484 // "Addition" rows. Initialize null column. | |
5485 for (var i = 0; i < rowCount; i++) { | |
5486 distances[i] = new Array(columnCount); | |
5487 distances[i][0] = i; | |
5488 } | |
5489 | |
5490 // Initialize null row | |
5491 for (var j = 0; j < columnCount; j++) | |
5492 distances[0][j] = j; | |
5493 | |
5494 for (var i = 1; i < rowCount; i++) { | |
5495 for (var j = 1; j < columnCount; j++) { | |
5496 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) | |
5497 distances[i][j] = distances[i - 1][j - 1]; | |
5498 else { | |
5499 var north = distances[i - 1][j] + 1; | |
5500 var west = distances[i][j - 1] + 1; | |
5501 distances[i][j] = north < west ? north : west; | |
5502 } | |
5503 } | |
5504 } | |
5505 | |
5506 return distances; | |
5507 }, | |
5508 | |
5509 // This starts at the final weight, and walks "backward" by finding | |
5510 // the minimum previous weight recursively until the origin of the weight | |
5511 // matrix. | |
5512 spliceOperationsFromEditDistances: function(distances) { | |
5513 var i = distances.length - 1; | |
5514 var j = distances[0].length - 1; | |
5515 var current = distances[i][j]; | |
5516 var edits = []; | |
5517 while (i > 0 || j > 0) { | |
5518 if (i == 0) { | |
5519 edits.push(EDIT_ADD); | |
5520 j--; | |
5521 continue; | |
5522 } | |
5523 if (j == 0) { | |
5524 edits.push(EDIT_DELETE); | |
5525 i--; | |
5526 continue; | |
5527 } | |
5528 var northWest = distances[i - 1][j - 1]; | |
5529 var west = distances[i - 1][j]; | |
5530 var north = distances[i][j - 1]; | |
5531 | |
5532 var min; | |
5533 if (west < north) | |
5534 min = west < northWest ? west : northWest; | |
5535 else | |
5536 min = north < northWest ? north : northWest; | |
5537 | |
5538 if (min == northWest) { | |
5539 if (northWest == current) { | |
5540 edits.push(EDIT_LEAVE); | |
5541 } else { | |
5542 edits.push(EDIT_UPDATE); | |
5543 current = northWest; | |
5544 } | |
5545 i--; | |
5546 j--; | |
5547 } else if (min == west) { | |
5548 edits.push(EDIT_DELETE); | |
5549 i--; | |
5550 current = west; | |
5551 } else { | |
5552 edits.push(EDIT_ADD); | |
5553 j--; | |
5554 current = north; | |
5555 } | |
5556 } | |
5557 | |
5558 edits.reverse(); | |
5559 return edits; | |
5560 }, | |
5561 | |
5562 /** | |
5563 * Splice Projection functions: | |
5564 * | |
5565 * A splice map is a representation of how a previous array of items | |
5566 * was transformed into a new array of items. Conceptually it is a list of | |
5567 * tuples of | |
5568 * | |
5569 * <index, removed, addedCount> | |
5570 * | |
5571 * which are kept in ascending index order of. The tuple represents that at | |
5572 * the |index|, |removed| sequence of items were removed, and counting forwa
rd | |
5573 * from |index|, |addedCount| items were added. | |
5574 */ | |
5575 | |
5576 /** | |
5577 * Lacking individual splice mutation information, the minimal set of | |
5578 * splices can be synthesized given the previous state and final state of an | |
5579 * array. The basic approach is to calculate the edit distance matrix and | |
5580 * choose the shortest path through it. | |
5581 * | |
5582 * Complexity: O(l * p) | |
5583 * l: The length of the current array | |
5584 * p: The length of the old array | |
5585 */ | |
5586 calcSplices: function(current, currentStart, currentEnd, | |
5587 old, oldStart, oldEnd) { | |
5588 var prefixCount = 0; | |
5589 var suffixCount = 0; | |
5590 | |
5591 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); | |
5592 if (currentStart == 0 && oldStart == 0) | |
5593 prefixCount = this.sharedPrefix(current, old, minLength); | |
5594 | |
5595 if (currentEnd == current.length && oldEnd == old.length) | |
5596 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); | |
5597 | |
5598 currentStart += prefixCount; | |
5599 oldStart += prefixCount; | |
5600 currentEnd -= suffixCount; | |
5601 oldEnd -= suffixCount; | |
5602 | |
5603 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) | |
5604 return []; | |
5605 | |
5606 if (currentStart == currentEnd) { | |
5607 var splice = newSplice(currentStart, [], 0); | |
5608 while (oldStart < oldEnd) | |
5609 splice.removed.push(old[oldStart++]); | |
5610 | |
5611 return [ splice ]; | |
5612 } else if (oldStart == oldEnd) | |
5613 return [ newSplice(currentStart, [], currentEnd - currentStart) ]; | |
5614 | |
5615 var ops = this.spliceOperationsFromEditDistances( | |
5616 this.calcEditDistances(current, currentStart, currentEnd, | |
5617 old, oldStart, oldEnd)); | |
5618 | |
5619 var splice = undefined; | |
5620 var splices = []; | |
5621 var index = currentStart; | |
5622 var oldIndex = oldStart; | |
5623 for (var i = 0; i < ops.length; i++) { | |
5624 switch(ops[i]) { | |
5625 case EDIT_LEAVE: | |
5626 if (splice) { | |
5627 splices.push(splice); | |
5628 splice = undefined; | |
5629 } | |
5630 | |
5631 index++; | |
5632 oldIndex++; | |
5633 break; | |
5634 case EDIT_UPDATE: | |
5635 if (!splice) | |
5636 splice = newSplice(index, [], 0); | |
5637 | |
5638 splice.addedCount++; | |
5639 index++; | |
5640 | |
5641 splice.removed.push(old[oldIndex]); | |
5642 oldIndex++; | |
5643 break; | |
5644 case EDIT_ADD: | |
5645 if (!splice) | |
5646 splice = newSplice(index, [], 0); | |
5647 | |
5648 splice.addedCount++; | |
5649 index++; | |
5650 break; | |
5651 case EDIT_DELETE: | |
5652 if (!splice) | |
5653 splice = newSplice(index, [], 0); | |
5654 | |
5655 splice.removed.push(old[oldIndex]); | |
5656 oldIndex++; | |
5657 break; | |
5658 } | |
5659 } | |
5660 | |
5661 if (splice) { | |
5662 splices.push(splice); | |
5663 } | |
5664 return splices; | |
5665 }, | |
5666 | |
5667 sharedPrefix: function(current, old, searchLength) { | |
5668 for (var i = 0; i < searchLength; i++) | |
5669 if (!this.equals(current[i], old[i])) | |
5670 return i; | |
5671 return searchLength; | |
5672 }, | |
5673 | |
5674 sharedSuffix: function(current, old, searchLength) { | |
5675 var index1 = current.length; | |
5676 var index2 = old.length; | |
5677 var count = 0; | |
5678 while (count < searchLength && this.equals(current[--index1], old[--index2
])) | |
5679 count++; | |
5680 | |
5681 return count; | |
5682 }, | |
5683 | |
5684 calculateSplices: function(current, previous) { | |
5685 return this.calcSplices(current, 0, current.length, previous, 0, | |
5686 previous.length); | |
5687 }, | |
5688 | |
5689 equals: function(currentValue, previousValue) { | |
5690 return currentValue === previousValue; | |
5691 } | |
5692 }; | |
5693 | |
5694 var arraySplice = new ArraySplice(); | |
5695 | |
5696 function calcSplices(current, currentStart, currentEnd, | |
5697 old, oldStart, oldEnd) { | |
5698 return arraySplice.calcSplices(current, currentStart, currentEnd, | |
5699 old, oldStart, oldEnd); | |
5700 } | |
5701 | |
5702 function intersect(start1, end1, start2, end2) { | |
5703 // Disjoint | |
5704 if (end1 < start2 || end2 < start1) | |
5705 return -1; | |
5706 | |
5707 // Adjacent | |
5708 if (end1 == start2 || end2 == start1) | |
5709 return 0; | |
5710 | |
5711 // Non-zero intersect, span1 first | |
5712 if (start1 < start2) { | |
5713 if (end1 < end2) | |
5714 return end1 - start2; // Overlap | |
5715 else | |
5716 return end2 - start2; // Contained | |
5717 } else { | |
5718 // Non-zero intersect, span2 first | |
5719 if (end2 < end1) | |
5720 return end2 - start1; // Overlap | |
5721 else | |
5722 return end1 - start1; // Contained | |
5723 } | |
5724 } | |
5725 | |
5726 function mergeSplice(splices, index, removed, addedCount) { | |
5727 | |
5728 var splice = newSplice(index, removed, addedCount); | |
5729 | |
5730 var inserted = false; | |
5731 var insertionOffset = 0; | |
5732 | |
5733 for (var i = 0; i < splices.length; i++) { | |
5734 var current = splices[i]; | |
5735 current.index += insertionOffset; | |
5736 | |
5737 if (inserted) | |
5738 continue; | |
5739 | |
5740 var intersectCount = intersect(splice.index, | |
5741 splice.index + splice.removed.length, | |
5742 current.index, | |
5743 current.index + current.addedCount); | |
5744 | |
5745 if (intersectCount >= 0) { | |
5746 // Merge the two splices | |
5747 | |
5748 splices.splice(i, 1); | |
5749 i--; | |
5750 | |
5751 insertionOffset -= current.addedCount - current.removed.length; | |
5752 | |
5753 splice.addedCount += current.addedCount - intersectCount; | |
5754 var deleteCount = splice.removed.length + | |
5755 current.removed.length - intersectCount; | |
5756 | |
5757 if (!splice.addedCount && !deleteCount) { | |
5758 // merged splice is a noop. discard. | |
5759 inserted = true; | |
5760 } else { | |
5761 var removed = current.removed; | |
5762 | |
5763 if (splice.index < current.index) { | |
5764 // some prefix of splice.removed is prepended to current.removed. | |
5765 var prepend = splice.removed.slice(0, current.index - splice.index); | |
5766 Array.prototype.push.apply(prepend, removed); | |
5767 removed = prepend; | |
5768 } | |
5769 | |
5770 if (splice.index + splice.removed.length > current.index + current.add
edCount) { | |
5771 // some suffix of splice.removed is appended to current.removed. | |
5772 var append = splice.removed.slice(current.index + current.addedCount
- splice.index); | |
5773 Array.prototype.push.apply(removed, append); | |
5774 } | |
5775 | |
5776 splice.removed = removed; | |
5777 if (current.index < splice.index) { | |
5778 splice.index = current.index; | |
5779 } | |
5780 } | |
5781 } else if (splice.index < current.index) { | |
5782 // Insert splice here. | |
5783 | |
5784 inserted = true; | |
5785 | |
5786 splices.splice(i, 0, splice); | |
5787 i++; | |
5788 | |
5789 var offset = splice.addedCount - splice.removed.length | |
5790 current.index += offset; | |
5791 insertionOffset += offset; | |
5792 } | |
5793 } | |
5794 | |
5795 if (!inserted) | |
5796 splices.push(splice); | |
5797 } | |
5798 | |
5799 function createInitialSplices(array, changeRecords) { | |
5800 var splices = []; | |
5801 | |
5802 for (var i = 0; i < changeRecords.length; i++) { | |
5803 var record = changeRecords[i]; | |
5804 switch(record.type) { | |
5805 case 'splice': | |
5806 mergeSplice(splices, record.index, record.removed.slice(), record.adde
dCount); | |
5807 break; | |
5808 case 'add': | |
5809 case 'update': | |
5810 case 'delete': | |
5811 if (!isIndex(record.name)) | |
5812 continue; | |
5813 var index = toNumber(record.name); | |
5814 if (index < 0) | |
5815 continue; | |
5816 mergeSplice(splices, index, [record.oldValue], 1); | |
5817 break; | |
5818 default: | |
5819 console.error('Unexpected record type: ' + JSON.stringify(record)); | |
5820 break; | |
5821 } | |
5822 } | |
5823 | |
5824 return splices; | |
5825 } | |
5826 | |
5827 function projectArraySplices(array, changeRecords) { | |
5828 var splices = []; | |
5829 | |
5830 createInitialSplices(array, changeRecords).forEach(function(splice) { | |
5831 if (splice.addedCount == 1 && splice.removed.length == 1) { | |
5832 if (splice.removed[0] !== array[splice.index]) | |
5833 splices.push(splice); | |
5834 | |
5835 return | |
5836 }; | |
5837 | |
5838 splices = splices.concat(calcSplices(array, splice.index, splice.index + s
plice.addedCount, | |
5839 splice.removed, 0, splice.removed.len
gth)); | |
5840 }); | |
5841 | |
5842 return splices; | |
5843 } | |
5844 | |
5845 // Export the observe-js object for **Node.js**, with backwards-compatibility | |
5846 // for the old `require()` API. Also ensure `exports` is not a DOM Element. | |
5847 // If we're in the browser, export as a global object. | |
5848 | |
5849 var expose = global; | |
5850 | |
5851 if (typeof exports !== 'undefined' && !exports.nodeType) { | |
5852 if (typeof module !== 'undefined' && module.exports) { | |
5853 exports = module.exports; | |
5854 } | |
5855 expose = exports; | |
5856 } | |
5857 | |
5858 expose.Observer = Observer; | |
5859 expose.Observer.runEOM_ = runEOM; | |
5860 expose.Observer.observerSentinel_ = observerSentinel; // for testing. | |
5861 expose.Observer.hasObjectObserve = hasObserve; | |
5862 expose.ArrayObserver = ArrayObserver; | |
5863 expose.ArrayObserver.calculateSplices = function(current, previous) { | |
5864 return arraySplice.calculateSplices(current, previous); | |
5865 }; | |
5866 | |
5867 expose.ArraySplice = ArraySplice; | |
5868 expose.ObjectObserver = ObjectObserver; | |
5869 expose.PathObserver = PathObserver; | |
5870 expose.CompoundObserver = CompoundObserver; | |
5871 expose.Path = Path; | |
5872 expose.ObserverTransform = ObserverTransform; | |
5873 | |
5874 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m
odule ? global : this || window); | |
5875 | |
5876 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
5877 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
5878 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
5879 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
5880 // Code distributed by Google as part of the polymer project is also | |
5881 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
5882 | |
5883 (function(global) { | |
5884 'use strict'; | |
5885 | |
5886 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); | |
5887 | |
5888 function getTreeScope(node) { | |
5889 while (node.parentNode) { | |
5890 node = node.parentNode; | |
5891 } | |
5892 | |
5893 return typeof node.getElementById === 'function' ? node : null; | |
5894 } | |
5895 | |
5896 Node.prototype.bind = function(name, observable) { | |
5897 console.error('Unhandled binding to Node: ', this, name, observable); | |
5898 }; | |
5899 | |
5900 Node.prototype.bindFinished = function() {}; | |
5901 | |
5902 function updateBindings(node, name, binding) { | |
5903 var bindings = node.bindings_; | |
5904 if (!bindings) | |
5905 bindings = node.bindings_ = {}; | |
5906 | |
5907 if (bindings[name]) | |
5908 binding[name].close(); | |
5909 | |
5910 return bindings[name] = binding; | |
5911 } | |
5912 | |
5913 function returnBinding(node, name, binding) { | |
5914 return binding; | |
5915 } | |
5916 | |
5917 function sanitizeValue(value) { | |
5918 return value == null ? '' : value; | |
5919 } | |
5920 | |
5921 function updateText(node, value) { | |
5922 node.data = sanitizeValue(value); | |
5923 } | |
5924 | |
5925 function textBinding(node) { | |
5926 return function(value) { | |
5927 return updateText(node, value); | |
5928 }; | |
5929 } | |
5930 | |
5931 var maybeUpdateBindings = returnBinding; | |
5932 | |
5933 Object.defineProperty(Platform, 'enableBindingsReflection', { | |
5934 get: function() { | |
5935 return maybeUpdateBindings === updateBindings; | |
5936 }, | |
5937 set: function(enable) { | |
5938 maybeUpdateBindings = enable ? updateBindings : returnBinding; | |
5939 return enable; | |
5940 }, | |
5941 configurable: true | |
5942 }); | |
5943 | |
5944 Text.prototype.bind = function(name, value, oneTime) { | |
5945 if (name !== 'textContent') | |
5946 return Node.prototype.bind.call(this, name, value, oneTime); | |
5947 | |
5948 if (oneTime) | |
5949 return updateText(this, value); | |
5950 | |
5951 var observable = value; | |
5952 updateText(this, observable.open(textBinding(this))); | |
5953 return maybeUpdateBindings(this, name, observable); | |
5954 } | |
5955 | |
5956 function updateAttribute(el, name, conditional, value) { | |
5957 if (conditional) { | |
5958 if (value) | |
5959 el.setAttribute(name, ''); | |
5960 else | |
5961 el.removeAttribute(name); | |
5962 return; | |
5963 } | |
5964 | |
5965 el.setAttribute(name, sanitizeValue(value)); | |
5966 } | |
5967 | |
5968 function attributeBinding(el, name, conditional) { | |
5969 return function(value) { | |
5970 updateAttribute(el, name, conditional, value); | |
5971 }; | |
5972 } | |
5973 | |
5974 Element.prototype.bind = function(name, value, oneTime) { | |
5975 var conditional = name[name.length - 1] == '?'; | |
5976 if (conditional) { | |
5977 this.removeAttribute(name); | |
5978 name = name.slice(0, -1); | |
5979 } | |
5980 | |
5981 if (oneTime) | |
5982 return updateAttribute(this, name, conditional, value); | |
5983 | |
5984 | |
5985 var observable = value; | |
5986 updateAttribute(this, name, conditional, | |
5987 observable.open(attributeBinding(this, name, conditional))); | |
5988 | |
5989 return maybeUpdateBindings(this, name, observable); | |
5990 }; | |
5991 | |
5992 var checkboxEventType; | |
5993 (function() { | |
5994 // Attempt to feature-detect which event (change or click) is fired first | |
5995 // for checkboxes. | |
5996 var div = document.createElement('div'); | |
5997 var checkbox = div.appendChild(document.createElement('input')); | |
5998 checkbox.setAttribute('type', 'checkbox'); | |
5999 var first; | |
6000 var count = 0; | |
6001 checkbox.addEventListener('click', function(e) { | |
6002 count++; | |
6003 first = first || 'click'; | |
6004 }); | |
6005 checkbox.addEventListener('change', function() { | |
6006 count++; | |
6007 first = first || 'change'; | |
6008 }); | |
6009 | |
6010 var event = document.createEvent('MouseEvent'); | |
6011 event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, | |
6012 false, false, false, 0, null); | |
6013 checkbox.dispatchEvent(event); | |
6014 // WebKit/Blink don't fire the change event if the element is outside the | |
6015 // document, so assume 'change' for that case. | |
6016 checkboxEventType = count == 1 ? 'change' : first; | |
6017 })(); | |
6018 | |
6019 function getEventForInputType(element) { | |
6020 switch (element.type) { | |
6021 case 'checkbox': | |
6022 return checkboxEventType; | |
6023 case 'radio': | |
6024 case 'select-multiple': | |
6025 case 'select-one': | |
6026 return 'change'; | |
6027 case 'range': | |
6028 if (/Trident|MSIE/.test(navigator.userAgent)) | |
6029 return 'change'; | |
6030 default: | |
6031 return 'input'; | |
6032 } | |
6033 } | |
6034 | |
6035 function updateInput(input, property, value, santizeFn) { | |
6036 input[property] = (santizeFn || sanitizeValue)(value); | |
6037 } | |
6038 | |
6039 function inputBinding(input, property, santizeFn) { | |
6040 return function(value) { | |
6041 return updateInput(input, property, value, santizeFn); | |
6042 } | |
6043 } | |
6044 | |
6045 function noop() {} | |
6046 | |
6047 function bindInputEvent(input, property, observable, postEventFn) { | |
6048 var eventType = getEventForInputType(input); | |
6049 | |
6050 function eventHandler() { | |
6051 var isNum = property == 'value' && input.type == 'number'; | |
6052 observable.setValue(isNum ? input.valueAsNumber : input[property]); | |
6053 observable.discardChanges(); | |
6054 (postEventFn || noop)(input); | |
6055 Platform.performMicrotaskCheckpoint(); | |
6056 } | |
6057 input.addEventListener(eventType, eventHandler); | |
6058 | |
6059 return { | |
6060 close: function() { | |
6061 input.removeEventListener(eventType, eventHandler); | |
6062 observable.close(); | |
6063 }, | |
6064 | |
6065 observable_: observable | |
6066 } | |
6067 } | |
6068 | |
6069 function booleanSanitize(value) { | |
6070 return Boolean(value); | |
6071 } | |
6072 | |
6073 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. | |
6074 // Returns an array containing all radio buttons other than |element| that | |
6075 // have the same |name|, either in the form that |element| belongs to or, | |
6076 // if no form, in the document tree to which |element| belongs. | |
6077 // | |
6078 // This implementation is based upon the HTML spec definition of a | |
6079 // "radio button group": | |
6080 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group | |
6081 // | |
6082 function getAssociatedRadioButtons(element) { | |
6083 if (element.form) { | |
6084 return filter(element.form.elements, function(el) { | |
6085 return el != element && | |
6086 el.tagName == 'INPUT' && | |
6087 el.type == 'radio' && | |
6088 el.name == element.name; | |
6089 }); | |
6090 } else { | |
6091 var treeScope = getTreeScope(element); | |
6092 if (!treeScope) | |
6093 return []; | |
6094 var radios = treeScope.querySelectorAll( | |
6095 'input[type="radio"][name="' + element.name + '"]'); | |
6096 return filter(radios, function(el) { | |
6097 return el != element && !el.form; | |
6098 }); | |
6099 } | |
6100 } | |
6101 | |
6102 function checkedPostEvent(input) { | |
6103 // Only the radio button that is getting checked gets an event. We | |
6104 // therefore find all the associated radio buttons and update their | |
6105 // check binding manually. | |
6106 if (input.tagName === 'INPUT' && | |
6107 input.type === 'radio') { | |
6108 getAssociatedRadioButtons(input).forEach(function(radio) { | |
6109 var checkedBinding = radio.bindings_.checked; | |
6110 if (checkedBinding) { | |
6111 // Set the value directly to avoid an infinite call stack. | |
6112 checkedBinding.observable_.setValue(false); | |
6113 } | |
6114 }); | |
6115 } | |
6116 } | |
6117 | |
6118 HTMLInputElement.prototype.bind = function(name, value, oneTime) { | |
6119 if (name !== 'value' && name !== 'checked') | |
6120 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
6121 | |
6122 this.removeAttribute(name); | |
6123 var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; | |
6124 var postEventFn = name == 'checked' ? checkedPostEvent : noop; | |
6125 | |
6126 if (oneTime) | |
6127 return updateInput(this, name, value, sanitizeFn); | |
6128 | |
6129 | |
6130 var observable = value; | |
6131 var binding = bindInputEvent(this, name, observable, postEventFn); | |
6132 updateInput(this, name, | |
6133 observable.open(inputBinding(this, name, sanitizeFn)), | |
6134 sanitizeFn); | |
6135 | |
6136 // Checkboxes may need to update bindings of other checkboxes. | |
6137 return updateBindings(this, name, binding); | |
6138 } | |
6139 | |
6140 HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { | |
6141 if (name !== 'value') | |
6142 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
6143 | |
6144 this.removeAttribute('value'); | |
6145 | |
6146 if (oneTime) | |
6147 return updateInput(this, 'value', value); | |
6148 | |
6149 var observable = value; | |
6150 var binding = bindInputEvent(this, 'value', observable); | |
6151 updateInput(this, 'value', | |
6152 observable.open(inputBinding(this, 'value', sanitizeValue))); | |
6153 return maybeUpdateBindings(this, name, binding); | |
6154 } | |
6155 | |
6156 function updateOption(option, value) { | |
6157 var parentNode = option.parentNode;; | |
6158 var select; | |
6159 var selectBinding; | |
6160 var oldValue; | |
6161 if (parentNode instanceof HTMLSelectElement && | |
6162 parentNode.bindings_ && | |
6163 parentNode.bindings_.value) { | |
6164 select = parentNode; | |
6165 selectBinding = select.bindings_.value; | |
6166 oldValue = select.value; | |
6167 } | |
6168 | |
6169 option.value = sanitizeValue(value); | |
6170 | |
6171 if (select && select.value != oldValue) { | |
6172 selectBinding.observable_.setValue(select.value); | |
6173 selectBinding.observable_.discardChanges(); | |
6174 Platform.performMicrotaskCheckpoint(); | |
6175 } | |
6176 } | |
6177 | |
6178 function optionBinding(option) { | |
6179 return function(value) { | |
6180 updateOption(option, value); | |
6181 } | |
6182 } | |
6183 | |
6184 HTMLOptionElement.prototype.bind = function(name, value, oneTime) { | |
6185 if (name !== 'value') | |
6186 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
6187 | |
6188 this.removeAttribute('value'); | |
6189 | |
6190 if (oneTime) | |
6191 return updateOption(this, value); | |
6192 | |
6193 var observable = value; | |
6194 var binding = bindInputEvent(this, 'value', observable); | |
6195 updateOption(this, observable.open(optionBinding(this))); | |
6196 return maybeUpdateBindings(this, name, binding); | |
6197 } | |
6198 | |
6199 HTMLSelectElement.prototype.bind = function(name, value, oneTime) { | |
6200 if (name === 'selectedindex') | |
6201 name = 'selectedIndex'; | |
6202 | |
6203 if (name !== 'selectedIndex' && name !== 'value') | |
6204 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
6205 | |
6206 this.removeAttribute(name); | |
6207 | |
6208 if (oneTime) | |
6209 return updateInput(this, name, value); | |
6210 | |
6211 var observable = value; | |
6212 var binding = bindInputEvent(this, name, observable); | |
6213 updateInput(this, name, | |
6214 observable.open(inputBinding(this, name))); | |
6215 | |
6216 // Option update events may need to access select bindings. | |
6217 return updateBindings(this, name, binding); | |
6218 } | |
6219 })(this); | |
6220 | |
6221 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
6222 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
6223 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
6224 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
6225 // Code distributed by Google as part of the polymer project is also | |
6226 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
6227 | |
6228 (function(global) { | |
6229 'use strict'; | |
6230 | |
6231 function assert(v) { | |
6232 if (!v) | |
6233 throw new Error('Assertion failed'); | |
6234 } | |
6235 | |
6236 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
6237 | |
6238 function getFragmentRoot(node) { | |
6239 var p; | |
6240 while (p = node.parentNode) { | |
6241 node = p; | |
6242 } | |
6243 | |
6244 return node; | |
6245 } | |
6246 | |
6247 function searchRefId(node, id) { | |
6248 if (!id) | |
6249 return; | |
6250 | |
6251 var ref; | |
6252 var selector = '#' + id; | |
6253 while (!ref) { | |
6254 node = getFragmentRoot(node); | |
6255 | |
6256 if (node.protoContent_) | |
6257 ref = node.protoContent_.querySelector(selector); | |
6258 else if (node.getElementById) | |
6259 ref = node.getElementById(id); | |
6260 | |
6261 if (ref || !node.templateCreator_) | |
6262 break | |
6263 | |
6264 node = node.templateCreator_; | |
6265 } | |
6266 | |
6267 return ref; | |
6268 } | |
6269 | |
6270 function getInstanceRoot(node) { | |
6271 while (node.parentNode) { | |
6272 node = node.parentNode; | |
6273 } | |
6274 return node.templateCreator_ ? node : null; | |
6275 } | |
6276 | |
6277 var Map; | |
6278 if (global.Map && typeof global.Map.prototype.forEach === 'function') { | |
6279 Map = global.Map; | |
6280 } else { | |
6281 Map = function() { | |
6282 this.keys = []; | |
6283 this.values = []; | |
6284 }; | |
6285 | |
6286 Map.prototype = { | |
6287 set: function(key, value) { | |
6288 var index = this.keys.indexOf(key); | |
6289 if (index < 0) { | |
6290 this.keys.push(key); | |
6291 this.values.push(value); | |
6292 } else { | |
6293 this.values[index] = value; | |
6294 } | |
6295 }, | |
6296 | |
6297 get: function(key) { | |
6298 var index = this.keys.indexOf(key); | |
6299 if (index < 0) | |
6300 return; | |
6301 | |
6302 return this.values[index]; | |
6303 }, | |
6304 | |
6305 delete: function(key, value) { | |
6306 var index = this.keys.indexOf(key); | |
6307 if (index < 0) | |
6308 return false; | |
6309 | |
6310 this.keys.splice(index, 1); | |
6311 this.values.splice(index, 1); | |
6312 return true; | |
6313 }, | |
6314 | |
6315 forEach: function(f, opt_this) { | |
6316 for (var i = 0; i < this.keys.length; i++) | |
6317 f.call(opt_this || this, this.values[i], this.keys[i], this); | |
6318 } | |
6319 }; | |
6320 } | |
6321 | |
6322 // JScript does not have __proto__. We wrap all object literals with | |
6323 // createObject which uses Object.create, Object.defineProperty and | |
6324 // Object.getOwnPropertyDescriptor to create a new object that does the exact | |
6325 // same thing. The main downside to this solution is that we have to extract | |
6326 // all those property descriptors for IE. | |
6327 var createObject = ('__proto__' in {}) ? | |
6328 function(obj) { return obj; } : | |
6329 function(obj) { | |
6330 var proto = obj.__proto__; | |
6331 if (!proto) | |
6332 return obj; | |
6333 var newObject = Object.create(proto); | |
6334 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
6335 Object.defineProperty(newObject, name, | |
6336 Object.getOwnPropertyDescriptor(obj, name)); | |
6337 }); | |
6338 return newObject; | |
6339 }; | |
6340 | |
6341 // IE does not support have Document.prototype.contains. | |
6342 if (typeof document.contains != 'function') { | |
6343 Document.prototype.contains = function(node) { | |
6344 if (node === this || node.parentNode === this) | |
6345 return true; | |
6346 return this.documentElement.contains(node); | |
6347 } | |
6348 } | |
6349 | |
6350 var BIND = 'bind'; | |
6351 var REPEAT = 'repeat'; | |
6352 var IF = 'if'; | |
6353 | |
6354 var templateAttributeDirectives = { | |
6355 'template': true, | |
6356 'repeat': true, | |
6357 'bind': true, | |
6358 'ref': true, | |
6359 'if': true | |
6360 }; | |
6361 | |
6362 var semanticTemplateElements = { | |
6363 'THEAD': true, | |
6364 'TBODY': true, | |
6365 'TFOOT': true, | |
6366 'TH': true, | |
6367 'TR': true, | |
6368 'TD': true, | |
6369 'COLGROUP': true, | |
6370 'COL': true, | |
6371 'CAPTION': true, | |
6372 'OPTION': true, | |
6373 'OPTGROUP': true | |
6374 }; | |
6375 | |
6376 var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined'; | |
6377 if (hasTemplateElement) { | |
6378 // TODO(rafaelw): Remove when fix for | |
6379 // https://codereview.chromium.org/164803002/ | |
6380 // makes it to Chrome release. | |
6381 (function() { | |
6382 var t = document.createElement('template'); | |
6383 var d = t.content.ownerDocument; | |
6384 var html = d.appendChild(d.createElement('html')); | |
6385 var head = html.appendChild(d.createElement('head')); | |
6386 var base = d.createElement('base'); | |
6387 base.href = document.baseURI; | |
6388 head.appendChild(base); | |
6389 })(); | |
6390 } | |
6391 | |
6392 var allTemplatesSelectors = 'template, ' + | |
6393 Object.keys(semanticTemplateElements).map(function(tagName) { | |
6394 return tagName.toLowerCase() + '[template]'; | |
6395 }).join(', '); | |
6396 | |
6397 function isSVGTemplate(el) { | |
6398 return el.tagName == 'template' && | |
6399 el.namespaceURI == 'http://www.w3.org/2000/svg'; | |
6400 } | |
6401 | |
6402 function isHTMLTemplate(el) { | |
6403 return el.tagName == 'TEMPLATE' && | |
6404 el.namespaceURI == 'http://www.w3.org/1999/xhtml'; | |
6405 } | |
6406 | |
6407 function isAttributeTemplate(el) { | |
6408 return Boolean(semanticTemplateElements[el.tagName] && | |
6409 el.hasAttribute('template')); | |
6410 } | |
6411 | |
6412 function isTemplate(el) { | |
6413 if (el.isTemplate_ === undefined) | |
6414 el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el); | |
6415 | |
6416 return el.isTemplate_; | |
6417 } | |
6418 | |
6419 // FIXME: Observe templates being added/removed from documents | |
6420 // FIXME: Expose imperative API to decorate and observe templates in | |
6421 // "disconnected tress" (e.g. ShadowRoot) | |
6422 document.addEventListener('DOMContentLoaded', function(e) { | |
6423 bootstrapTemplatesRecursivelyFrom(document); | |
6424 // FIXME: Is this needed? Seems like it shouldn't be. | |
6425 Platform.performMicrotaskCheckpoint(); | |
6426 }, false); | |
6427 | |
6428 function forAllTemplatesFrom(node, fn) { | |
6429 var subTemplates = node.querySelectorAll(allTemplatesSelectors); | |
6430 | |
6431 if (isTemplate(node)) | |
6432 fn(node) | |
6433 forEach(subTemplates, fn); | |
6434 } | |
6435 | |
6436 function bootstrapTemplatesRecursivelyFrom(node) { | |
6437 function bootstrap(template) { | |
6438 if (!HTMLTemplateElement.decorate(template)) | |
6439 bootstrapTemplatesRecursivelyFrom(template.content); | |
6440 } | |
6441 | |
6442 forAllTemplatesFrom(node, bootstrap); | |
6443 } | |
6444 | |
6445 if (!hasTemplateElement) { | |
6446 /** | |
6447 * This represents a <template> element. | |
6448 * @constructor | |
6449 * @extends {HTMLElement} | |
6450 */ | |
6451 global.HTMLTemplateElement = function() { | |
6452 throw TypeError('Illegal constructor'); | |
6453 }; | |
6454 } | |
6455 | |
6456 var hasProto = '__proto__' in {}; | |
6457 | |
6458 function mixin(to, from) { | |
6459 Object.getOwnPropertyNames(from).forEach(function(name) { | |
6460 Object.defineProperty(to, name, | |
6461 Object.getOwnPropertyDescriptor(from, name)); | |
6462 }); | |
6463 } | |
6464 | |
6465 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner | |
6466 function getOrCreateTemplateContentsOwner(template) { | |
6467 var doc = template.ownerDocument | |
6468 if (!doc.defaultView) | |
6469 return doc; | |
6470 var d = doc.templateContentsOwner_; | |
6471 if (!d) { | |
6472 // TODO(arv): This should either be a Document or HTMLDocument depending | |
6473 // on doc. | |
6474 d = doc.implementation.createHTMLDocument(''); | |
6475 while (d.lastChild) { | |
6476 d.removeChild(d.lastChild); | |
6477 } | |
6478 doc.templateContentsOwner_ = d; | |
6479 } | |
6480 return d; | |
6481 } | |
6482 | |
6483 function getTemplateStagingDocument(template) { | |
6484 if (!template.stagingDocument_) { | |
6485 var owner = template.ownerDocument; | |
6486 if (!owner.stagingDocument_) { | |
6487 owner.stagingDocument_ = owner.implementation.createHTMLDocument(''); | |
6488 owner.stagingDocument_.isStagingDocument = true; | |
6489 // TODO(rafaelw): Remove when fix for | |
6490 // https://codereview.chromium.org/164803002/ | |
6491 // makes it to Chrome release. | |
6492 var base = owner.stagingDocument_.createElement('base'); | |
6493 base.href = document.baseURI; | |
6494 owner.stagingDocument_.head.appendChild(base); | |
6495 | |
6496 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_; | |
6497 } | |
6498 | |
6499 template.stagingDocument_ = owner.stagingDocument_; | |
6500 } | |
6501 | |
6502 return template.stagingDocument_; | |
6503 } | |
6504 | |
6505 // For non-template browsers, the parser will disallow <template> in certain | |
6506 // locations, so we allow "attribute templates" which combine the template | |
6507 // element with the top-level container node of the content, e.g. | |
6508 // | |
6509 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr> | |
6510 // | |
6511 // becomes | |
6512 // | |
6513 // <template repeat="{{ foo }}"> | |
6514 // + #document-fragment | |
6515 // + <tr class="bar"> | |
6516 // + <td>Bar</td> | |
6517 // | |
6518 function extractTemplateFromAttributeTemplate(el) { | |
6519 var template = el.ownerDocument.createElement('template'); | |
6520 el.parentNode.insertBefore(template, el); | |
6521 | |
6522 var attribs = el.attributes; | |
6523 var count = attribs.length; | |
6524 while (count-- > 0) { | |
6525 var attrib = attribs[count]; | |
6526 if (templateAttributeDirectives[attrib.name]) { | |
6527 if (attrib.name !== 'template') | |
6528 template.setAttribute(attrib.name, attrib.value); | |
6529 el.removeAttribute(attrib.name); | |
6530 } | |
6531 } | |
6532 | |
6533 return template; | |
6534 } | |
6535 | |
6536 function extractTemplateFromSVGTemplate(el) { | |
6537 var template = el.ownerDocument.createElement('template'); | |
6538 el.parentNode.insertBefore(template, el); | |
6539 | |
6540 var attribs = el.attributes; | |
6541 var count = attribs.length; | |
6542 while (count-- > 0) { | |
6543 var attrib = attribs[count]; | |
6544 template.setAttribute(attrib.name, attrib.value); | |
6545 el.removeAttribute(attrib.name); | |
6546 } | |
6547 | |
6548 el.parentNode.removeChild(el); | |
6549 return template; | |
6550 } | |
6551 | |
6552 function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) { | |
6553 var content = template.content; | |
6554 if (useRoot) { | |
6555 content.appendChild(el); | |
6556 return; | |
6557 } | |
6558 | |
6559 var child; | |
6560 while (child = el.firstChild) { | |
6561 content.appendChild(child); | |
6562 } | |
6563 } | |
6564 | |
6565 var templateObserver; | |
6566 if (typeof MutationObserver == 'function') { | |
6567 templateObserver = new MutationObserver(function(records) { | |
6568 for (var i = 0; i < records.length; i++) { | |
6569 records[i].target.refChanged_(); | |
6570 } | |
6571 }); | |
6572 } | |
6573 | |
6574 /** | |
6575 * Ensures proper API and content model for template elements. | |
6576 * @param {HTMLTemplateElement} opt_instanceRef The template element which | |
6577 * |el| template element will return as the value of its ref(), and whose | |
6578 * content will be used as source when createInstance() is invoked. | |
6579 */ | |
6580 HTMLTemplateElement.decorate = function(el, opt_instanceRef) { | |
6581 if (el.templateIsDecorated_) | |
6582 return false; | |
6583 | |
6584 var templateElement = el; | |
6585 templateElement.templateIsDecorated_ = true; | |
6586 | |
6587 var isNativeHTMLTemplate = isHTMLTemplate(templateElement) && | |
6588 hasTemplateElement; | |
6589 var bootstrapContents = isNativeHTMLTemplate; | |
6590 var liftContents = !isNativeHTMLTemplate; | |
6591 var liftRoot = false; | |
6592 | |
6593 if (!isNativeHTMLTemplate) { | |
6594 if (isAttributeTemplate(templateElement)) { | |
6595 assert(!opt_instanceRef); | |
6596 templateElement = extractTemplateFromAttributeTemplate(el); | |
6597 templateElement.templateIsDecorated_ = true; | |
6598 isNativeHTMLTemplate = hasTemplateElement; | |
6599 liftRoot = true; | |
6600 } else if (isSVGTemplate(templateElement)) { | |
6601 templateElement = extractTemplateFromSVGTemplate(el); | |
6602 templateElement.templateIsDecorated_ = true; | |
6603 isNativeHTMLTemplate = hasTemplateElement; | |
6604 } | |
6605 } | |
6606 | |
6607 if (!isNativeHTMLTemplate) { | |
6608 fixTemplateElementPrototype(templateElement); | |
6609 var doc = getOrCreateTemplateContentsOwner(templateElement); | |
6610 templateElement.content_ = doc.createDocumentFragment(); | |
6611 } | |
6612 | |
6613 if (opt_instanceRef) { | |
6614 // template is contained within an instance, its direct content must be | |
6615 // empty | |
6616 templateElement.instanceRef_ = opt_instanceRef; | |
6617 } else if (liftContents) { | |
6618 liftNonNativeTemplateChildrenIntoContent(templateElement, | |
6619 el, | |
6620 liftRoot); | |
6621 } else if (bootstrapContents) { | |
6622 bootstrapTemplatesRecursivelyFrom(templateElement.content); | |
6623 } | |
6624 | |
6625 return true; | |
6626 }; | |
6627 | |
6628 // TODO(rafaelw): This used to decorate recursively all templates from a given | |
6629 // node. This happens by default on 'DOMContentLoaded', but may be needed | |
6630 // in subtrees not descendent from document (e.g. ShadowRoot). | |
6631 // Review whether this is the right public API. | |
6632 HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom; | |
6633 | |
6634 var htmlElement = global.HTMLUnknownElement || HTMLElement; | |
6635 | |
6636 var contentDescriptor = { | |
6637 get: function() { | |
6638 return this.content_; | |
6639 }, | |
6640 enumerable: true, | |
6641 configurable: true | |
6642 }; | |
6643 | |
6644 if (!hasTemplateElement) { | |
6645 // Gecko is more picky with the prototype than WebKit. Make sure to use the | |
6646 // same prototype as created in the constructor. | |
6647 HTMLTemplateElement.prototype = Object.create(htmlElement.prototype); | |
6648 | |
6649 Object.defineProperty(HTMLTemplateElement.prototype, 'content', | |
6650 contentDescriptor); | |
6651 } | |
6652 | |
6653 function fixTemplateElementPrototype(el) { | |
6654 if (hasProto) | |
6655 el.__proto__ = HTMLTemplateElement.prototype; | |
6656 else | |
6657 mixin(el, HTMLTemplateElement.prototype); | |
6658 } | |
6659 | |
6660 function ensureSetModelScheduled(template) { | |
6661 if (!template.setModelFn_) { | |
6662 template.setModelFn_ = function() { | |
6663 template.setModelFnScheduled_ = false; | |
6664 var map = getBindings(template, | |
6665 template.delegate_ && template.delegate_.prepareBinding); | |
6666 processBindings(template, map, template.model_); | |
6667 }; | |
6668 } | |
6669 | |
6670 if (!template.setModelFnScheduled_) { | |
6671 template.setModelFnScheduled_ = true; | |
6672 Observer.runEOM_(template.setModelFn_); | |
6673 } | |
6674 } | |
6675 | |
6676 mixin(HTMLTemplateElement.prototype, { | |
6677 bind: function(name, value, oneTime) { | |
6678 if (name != 'ref') | |
6679 return Element.prototype.bind.call(this, name, value, oneTime); | |
6680 | |
6681 var self = this; | |
6682 var ref = oneTime ? value : value.open(function(ref) { | |
6683 self.setAttribute('ref', ref); | |
6684 self.refChanged_(); | |
6685 }); | |
6686 | |
6687 this.setAttribute('ref', ref); | |
6688 this.refChanged_(); | |
6689 if (oneTime) | |
6690 return; | |
6691 | |
6692 if (!this.bindings_) { | |
6693 this.bindings_ = { ref: value }; | |
6694 } else { | |
6695 this.bindings_.ref = value; | |
6696 } | |
6697 | |
6698 return value; | |
6699 }, | |
6700 | |
6701 processBindingDirectives_: function(directives) { | |
6702 if (this.iterator_) | |
6703 this.iterator_.closeDeps(); | |
6704 | |
6705 if (!directives.if && !directives.bind && !directives.repeat) { | |
6706 if (this.iterator_) { | |
6707 this.iterator_.close(); | |
6708 this.iterator_ = undefined; | |
6709 } | |
6710 | |
6711 return; | |
6712 } | |
6713 | |
6714 if (!this.iterator_) { | |
6715 this.iterator_ = new TemplateIterator(this); | |
6716 } | |
6717 | |
6718 this.iterator_.updateDependencies(directives, this.model_); | |
6719 | |
6720 if (templateObserver) { | |
6721 templateObserver.observe(this, { attributes: true, | |
6722 attributeFilter: ['ref'] }); | |
6723 } | |
6724 | |
6725 return this.iterator_; | |
6726 }, | |
6727 | |
6728 createInstance: function(model, bindingDelegate, delegate_) { | |
6729 if (bindingDelegate) | |
6730 delegate_ = this.newDelegate_(bindingDelegate); | |
6731 else if (!delegate_) | |
6732 delegate_ = this.delegate_; | |
6733 | |
6734 if (!this.refContent_) | |
6735 this.refContent_ = this.ref_.content; | |
6736 var content = this.refContent_; | |
6737 if (content.firstChild === null) | |
6738 return emptyInstance; | |
6739 | |
6740 var map = getInstanceBindingMap(content, delegate_); | |
6741 var stagingDocument = getTemplateStagingDocument(this); | |
6742 var instance = stagingDocument.createDocumentFragment(); | |
6743 instance.templateCreator_ = this; | |
6744 instance.protoContent_ = content; | |
6745 instance.bindings_ = []; | |
6746 instance.terminator_ = null; | |
6747 var instanceRecord = instance.templateInstance_ = { | |
6748 firstNode: null, | |
6749 lastNode: null, | |
6750 model: model | |
6751 }; | |
6752 | |
6753 var i = 0; | |
6754 var collectTerminator = false; | |
6755 for (var child = content.firstChild; child; child = child.nextSibling) { | |
6756 // The terminator of the instance is the clone of the last child of the | |
6757 // content. If the last child is an active template, it may produce | |
6758 // instances as a result of production, so simply collecting the last | |
6759 // child of the instance after it has finished producing may be wrong. | |
6760 if (child.nextSibling === null) | |
6761 collectTerminator = true; | |
6762 | |
6763 var clone = cloneAndBindInstance(child, instance, stagingDocument, | |
6764 map.children[i++], | |
6765 model, | |
6766 delegate_, | |
6767 instance.bindings_); | |
6768 clone.templateInstance_ = instanceRecord; | |
6769 if (collectTerminator) | |
6770 instance.terminator_ = clone; | |
6771 } | |
6772 | |
6773 instanceRecord.firstNode = instance.firstChild; | |
6774 instanceRecord.lastNode = instance.lastChild; | |
6775 instance.templateCreator_ = undefined; | |
6776 instance.protoContent_ = undefined; | |
6777 return instance; | |
6778 }, | |
6779 | |
6780 get model() { | |
6781 return this.model_; | |
6782 }, | |
6783 | |
6784 set model(model) { | |
6785 this.model_ = model; | |
6786 ensureSetModelScheduled(this); | |
6787 }, | |
6788 | |
6789 get bindingDelegate() { | |
6790 return this.delegate_ && this.delegate_.raw; | |
6791 }, | |
6792 | |
6793 refChanged_: function() { | |
6794 if (!this.iterator_ || this.refContent_ === this.ref_.content) | |
6795 return; | |
6796 | |
6797 this.refContent_ = undefined; | |
6798 this.iterator_.valueChanged(); | |
6799 this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue()); | |
6800 }, | |
6801 | |
6802 clear: function() { | |
6803 this.model_ = undefined; | |
6804 this.delegate_ = undefined; | |
6805 if (this.bindings_ && this.bindings_.ref) | |
6806 this.bindings_.ref.close() | |
6807 this.refContent_ = undefined; | |
6808 if (!this.iterator_) | |
6809 return; | |
6810 this.iterator_.valueChanged(); | |
6811 this.iterator_.close() | |
6812 this.iterator_ = undefined; | |
6813 }, | |
6814 | |
6815 setDelegate_: function(delegate) { | |
6816 this.delegate_ = delegate; | |
6817 this.bindingMap_ = undefined; | |
6818 if (this.iterator_) { | |
6819 this.iterator_.instancePositionChangedFn_ = undefined; | |
6820 this.iterator_.instanceModelFn_ = undefined; | |
6821 } | |
6822 }, | |
6823 | |
6824 newDelegate_: function(bindingDelegate) { | |
6825 if (!bindingDelegate) | |
6826 return; | |
6827 | |
6828 function delegateFn(name) { | |
6829 var fn = bindingDelegate && bindingDelegate[name]; | |
6830 if (typeof fn != 'function') | |
6831 return; | |
6832 | |
6833 return function() { | |
6834 return fn.apply(bindingDelegate, arguments); | |
6835 }; | |
6836 } | |
6837 | |
6838 return { | |
6839 bindingMaps: {}, | |
6840 raw: bindingDelegate, | |
6841 prepareBinding: delegateFn('prepareBinding'), | |
6842 prepareInstanceModel: delegateFn('prepareInstanceModel'), | |
6843 prepareInstancePositionChanged: | |
6844 delegateFn('prepareInstancePositionChanged') | |
6845 }; | |
6846 }, | |
6847 | |
6848 set bindingDelegate(bindingDelegate) { | |
6849 if (this.delegate_) { | |
6850 throw Error('Template must be cleared before a new bindingDelegate ' + | |
6851 'can be assigned'); | |
6852 } | |
6853 | |
6854 this.setDelegate_(this.newDelegate_(bindingDelegate)); | |
6855 }, | |
6856 | |
6857 get ref_() { | |
6858 var ref = searchRefId(this, this.getAttribute('ref')); | |
6859 if (!ref) | |
6860 ref = this.instanceRef_; | |
6861 | |
6862 if (!ref) | |
6863 return this; | |
6864 | |
6865 var nextRef = ref.ref_; | |
6866 return nextRef ? nextRef : ref; | |
6867 } | |
6868 }); | |
6869 | |
6870 // Returns | |
6871 // a) undefined if there are no mustaches. | |
6872 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one
mustache. | |
6873 function parseMustaches(s, name, node, prepareBindingFn) { | |
6874 if (!s || !s.length) | |
6875 return; | |
6876 | |
6877 var tokens; | |
6878 var length = s.length; | |
6879 var startIndex = 0, lastIndex = 0, endIndex = 0; | |
6880 var onlyOneTime = true; | |
6881 while (lastIndex < length) { | |
6882 var startIndex = s.indexOf('{{', lastIndex); | |
6883 var oneTimeStart = s.indexOf('[[', lastIndex); | |
6884 var oneTime = false; | |
6885 var terminator = '}}'; | |
6886 | |
6887 if (oneTimeStart >= 0 && | |
6888 (startIndex < 0 || oneTimeStart < startIndex)) { | |
6889 startIndex = oneTimeStart; | |
6890 oneTime = true; | |
6891 terminator = ']]'; | |
6892 } | |
6893 | |
6894 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2); | |
6895 | |
6896 if (endIndex < 0) { | |
6897 if (!tokens) | |
6898 return; | |
6899 | |
6900 tokens.push(s.slice(lastIndex)); // TEXT | |
6901 break; | |
6902 } | |
6903 | |
6904 tokens = tokens || []; | |
6905 tokens.push(s.slice(lastIndex, startIndex)); // TEXT | |
6906 var pathString = s.slice(startIndex + 2, endIndex).trim(); | |
6907 tokens.push(oneTime); // ONE_TIME? | |
6908 onlyOneTime = onlyOneTime && oneTime; | |
6909 var delegateFn = prepareBindingFn && | |
6910 prepareBindingFn(pathString, name, node); | |
6911 // Don't try to parse the expression if there's a prepareBinding function | |
6912 if (delegateFn == null) { | |
6913 tokens.push(Path.get(pathString)); // PATH | |
6914 } else { | |
6915 tokens.push(null); | |
6916 } | |
6917 tokens.push(delegateFn); // DELEGATE_FN | |
6918 lastIndex = endIndex + 2; | |
6919 } | |
6920 | |
6921 if (lastIndex === length) | |
6922 tokens.push(''); // TEXT | |
6923 | |
6924 tokens.hasOnePath = tokens.length === 5; | |
6925 tokens.isSimplePath = tokens.hasOnePath && | |
6926 tokens[0] == '' && | |
6927 tokens[4] == ''; | |
6928 tokens.onlyOneTime = onlyOneTime; | |
6929 | |
6930 tokens.combinator = function(values) { | |
6931 var newValue = tokens[0]; | |
6932 | |
6933 for (var i = 1; i < tokens.length; i += 4) { | |
6934 var value = tokens.hasOnePath ? values : values[(i - 1) / 4]; | |
6935 if (value !== undefined) | |
6936 newValue += value; | |
6937 newValue += tokens[i + 3]; | |
6938 } | |
6939 | |
6940 return newValue; | |
6941 } | |
6942 | |
6943 return tokens; | |
6944 }; | |
6945 | |
6946 function processOneTimeBinding(name, tokens, node, model) { | |
6947 if (tokens.hasOnePath) { | |
6948 var delegateFn = tokens[3]; | |
6949 var value = delegateFn ? delegateFn(model, node, true) : | |
6950 tokens[2].getValueFrom(model); | |
6951 return tokens.isSimplePath ? value : tokens.combinator(value); | |
6952 } | |
6953 | |
6954 var values = []; | |
6955 for (var i = 1; i < tokens.length; i += 4) { | |
6956 var delegateFn = tokens[i + 2]; | |
6957 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) : | |
6958 tokens[i + 1].getValueFrom(model); | |
6959 } | |
6960 | |
6961 return tokens.combinator(values); | |
6962 } | |
6963 | |
6964 function processSinglePathBinding(name, tokens, node, model) { | |
6965 var delegateFn = tokens[3]; | |
6966 var observer = delegateFn ? delegateFn(model, node, false) : | |
6967 new PathObserver(model, tokens[2]); | |
6968 | |
6969 return tokens.isSimplePath ? observer : | |
6970 new ObserverTransform(observer, tokens.combinator); | |
6971 } | |
6972 | |
6973 function processBinding(name, tokens, node, model) { | |
6974 if (tokens.onlyOneTime) | |
6975 return processOneTimeBinding(name, tokens, node, model); | |
6976 | |
6977 if (tokens.hasOnePath) | |
6978 return processSinglePathBinding(name, tokens, node, model); | |
6979 | |
6980 var observer = new CompoundObserver(); | |
6981 | |
6982 for (var i = 1; i < tokens.length; i += 4) { | |
6983 var oneTime = tokens[i]; | |
6984 var delegateFn = tokens[i + 2]; | |
6985 | |
6986 if (delegateFn) { | |
6987 var value = delegateFn(model, node, oneTime); | |
6988 if (oneTime) | |
6989 observer.addPath(value) | |
6990 else | |
6991 observer.addObserver(value); | |
6992 continue; | |
6993 } | |
6994 | |
6995 var path = tokens[i + 1]; | |
6996 if (oneTime) | |
6997 observer.addPath(path.getValueFrom(model)) | |
6998 else | |
6999 observer.addPath(model, path); | |
7000 } | |
7001 | |
7002 return new ObserverTransform(observer, tokens.combinator); | |
7003 } | |
7004 | |
7005 function processBindings(node, bindings, model, instanceBindings) { | |
7006 for (var i = 0; i < bindings.length; i += 2) { | |
7007 var name = bindings[i] | |
7008 var tokens = bindings[i + 1]; | |
7009 var value = processBinding(name, tokens, node, model); | |
7010 var binding = node.bind(name, value, tokens.onlyOneTime); | |
7011 if (binding && instanceBindings) | |
7012 instanceBindings.push(binding); | |
7013 } | |
7014 | |
7015 node.bindFinished(); | |
7016 if (!bindings.isTemplate) | |
7017 return; | |
7018 | |
7019 node.model_ = model; | |
7020 var iter = node.processBindingDirectives_(bindings); | |
7021 if (instanceBindings && iter) | |
7022 instanceBindings.push(iter); | |
7023 } | |
7024 | |
7025 function parseWithDefault(el, name, prepareBindingFn) { | |
7026 var v = el.getAttribute(name); | |
7027 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn); | |
7028 } | |
7029 | |
7030 function parseAttributeBindings(element, prepareBindingFn) { | |
7031 assert(element); | |
7032 | |
7033 var bindings = []; | |
7034 var ifFound = false; | |
7035 var bindFound = false; | |
7036 | |
7037 for (var i = 0; i < element.attributes.length; i++) { | |
7038 var attr = element.attributes[i]; | |
7039 var name = attr.name; | |
7040 var value = attr.value; | |
7041 | |
7042 // Allow bindings expressed in attributes to be prefixed with underbars. | |
7043 // We do this to allow correct semantics for browsers that don't implement | |
7044 // <template> where certain attributes might trigger side-effects -- and | |
7045 // for IE which sanitizes certain attributes, disallowing mustache | |
7046 // replacements in their text. | |
7047 while (name[0] === '_') { | |
7048 name = name.substring(1); | |
7049 } | |
7050 | |
7051 if (isTemplate(element) && | |
7052 (name === IF || name === BIND || name === REPEAT)) { | |
7053 continue; | |
7054 } | |
7055 | |
7056 var tokens = parseMustaches(value, name, element, | |
7057 prepareBindingFn); | |
7058 if (!tokens) | |
7059 continue; | |
7060 | |
7061 bindings.push(name, tokens); | |
7062 } | |
7063 | |
7064 if (isTemplate(element)) { | |
7065 bindings.isTemplate = true; | |
7066 bindings.if = parseWithDefault(element, IF, prepareBindingFn); | |
7067 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn); | |
7068 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn); | |
7069 | |
7070 if (bindings.if && !bindings.bind && !bindings.repeat) | |
7071 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn); | |
7072 } | |
7073 | |
7074 return bindings; | |
7075 } | |
7076 | |
7077 function getBindings(node, prepareBindingFn) { | |
7078 if (node.nodeType === Node.ELEMENT_NODE) | |
7079 return parseAttributeBindings(node, prepareBindingFn); | |
7080 | |
7081 if (node.nodeType === Node.TEXT_NODE) { | |
7082 var tokens = parseMustaches(node.data, 'textContent', node, | |
7083 prepareBindingFn); | |
7084 if (tokens) | |
7085 return ['textContent', tokens]; | |
7086 } | |
7087 | |
7088 return []; | |
7089 } | |
7090 | |
7091 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model, | |
7092 delegate, | |
7093 instanceBindings, | |
7094 instanceRecord) { | |
7095 var clone = parent.appendChild(stagingDocument.importNode(node, false)); | |
7096 | |
7097 var i = 0; | |
7098 for (var child = node.firstChild; child; child = child.nextSibling) { | |
7099 cloneAndBindInstance(child, clone, stagingDocument, | |
7100 bindings.children[i++], | |
7101 model, | |
7102 delegate, | |
7103 instanceBindings); | |
7104 } | |
7105 | |
7106 if (bindings.isTemplate) { | |
7107 HTMLTemplateElement.decorate(clone, node); | |
7108 if (delegate) | |
7109 clone.setDelegate_(delegate); | |
7110 } | |
7111 | |
7112 processBindings(clone, bindings, model, instanceBindings); | |
7113 return clone; | |
7114 } | |
7115 | |
7116 function createInstanceBindingMap(node, prepareBindingFn) { | |
7117 var map = getBindings(node, prepareBindingFn); | |
7118 map.children = {}; | |
7119 var index = 0; | |
7120 for (var child = node.firstChild; child; child = child.nextSibling) { | |
7121 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn); | |
7122 } | |
7123 | |
7124 return map; | |
7125 } | |
7126 | |
7127 var contentUidCounter = 1; | |
7128 | |
7129 // TODO(rafaelw): Setup a MutationObserver on content which clears the id | |
7130 // so that bindingMaps regenerate when the template.content changes. | |
7131 function getContentUid(content) { | |
7132 var id = content.id_; | |
7133 if (!id) | |
7134 id = content.id_ = contentUidCounter++; | |
7135 return id; | |
7136 } | |
7137 | |
7138 // Each delegate is associated with a set of bindingMaps, one for each | |
7139 // content which may be used by a template. The intent is that each binding | |
7140 // delegate gets the opportunity to prepare the instance (via the prepare* | |
7141 // delegate calls) once across all uses. | |
7142 // TODO(rafaelw): Separate out the parse map from the binding map. In the | |
7143 // current implementation, if two delegates need a binding map for the same | |
7144 // content, the second will have to reparse. | |
7145 function getInstanceBindingMap(content, delegate_) { | |
7146 var contentId = getContentUid(content); | |
7147 if (delegate_) { | |
7148 var map = delegate_.bindingMaps[contentId]; | |
7149 if (!map) { | |
7150 map = delegate_.bindingMaps[contentId] = | |
7151 createInstanceBindingMap(content, delegate_.prepareBinding) || []; | |
7152 } | |
7153 return map; | |
7154 } | |
7155 | |
7156 var map = content.bindingMap_; | |
7157 if (!map) { | |
7158 map = content.bindingMap_ = | |
7159 createInstanceBindingMap(content, undefined) || []; | |
7160 } | |
7161 return map; | |
7162 } | |
7163 | |
7164 Object.defineProperty(Node.prototype, 'templateInstance', { | |
7165 get: function() { | |
7166 var instance = this.templateInstance_; | |
7167 return instance ? instance : | |
7168 (this.parentNode ? this.parentNode.templateInstance : undefined); | |
7169 } | |
7170 }); | |
7171 | |
7172 var emptyInstance = document.createDocumentFragment(); | |
7173 emptyInstance.bindings_ = []; | |
7174 emptyInstance.terminator_ = null; | |
7175 | |
7176 function TemplateIterator(templateElement) { | |
7177 this.closed = false; | |
7178 this.templateElement_ = templateElement; | |
7179 this.instances = []; | |
7180 this.deps = undefined; | |
7181 this.iteratedValue = []; | |
7182 this.presentValue = undefined; | |
7183 this.arrayObserver = undefined; | |
7184 } | |
7185 | |
7186 TemplateIterator.prototype = { | |
7187 closeDeps: function() { | |
7188 var deps = this.deps; | |
7189 if (deps) { | |
7190 if (deps.ifOneTime === false) | |
7191 deps.ifValue.close(); | |
7192 if (deps.oneTime === false) | |
7193 deps.value.close(); | |
7194 } | |
7195 }, | |
7196 | |
7197 updateDependencies: function(directives, model) { | |
7198 this.closeDeps(); | |
7199 | |
7200 var deps = this.deps = {}; | |
7201 var template = this.templateElement_; | |
7202 | |
7203 var ifValue = true; | |
7204 if (directives.if) { | |
7205 deps.hasIf = true; | |
7206 deps.ifOneTime = directives.if.onlyOneTime; | |
7207 deps.ifValue = processBinding(IF, directives.if, template, model); | |
7208 | |
7209 ifValue = deps.ifValue; | |
7210 | |
7211 // oneTime if & predicate is false. nothing else to do. | |
7212 if (deps.ifOneTime && !ifValue) { | |
7213 this.valueChanged(); | |
7214 return; | |
7215 } | |
7216 | |
7217 if (!deps.ifOneTime) | |
7218 ifValue = ifValue.open(this.updateIfValue, this); | |
7219 } | |
7220 | |
7221 if (directives.repeat) { | |
7222 deps.repeat = true; | |
7223 deps.oneTime = directives.repeat.onlyOneTime; | |
7224 deps.value = processBinding(REPEAT, directives.repeat, template, model); | |
7225 } else { | |
7226 deps.repeat = false; | |
7227 deps.oneTime = directives.bind.onlyOneTime; | |
7228 deps.value = processBinding(BIND, directives.bind, template, model); | |
7229 } | |
7230 | |
7231 var value = deps.value; | |
7232 if (!deps.oneTime) | |
7233 value = value.open(this.updateIteratedValue, this); | |
7234 | |
7235 if (!ifValue) { | |
7236 this.valueChanged(); | |
7237 return; | |
7238 } | |
7239 | |
7240 this.updateValue(value); | |
7241 }, | |
7242 | |
7243 /** | |
7244 * Gets the updated value of the bind/repeat. This can potentially call | |
7245 * user code (if a bindingDelegate is set up) so we try to avoid it if we | |
7246 * already have the value in hand (from Observer.open). | |
7247 */ | |
7248 getUpdatedValue: function() { | |
7249 var value = this.deps.value; | |
7250 if (!this.deps.oneTime) | |
7251 value = value.discardChanges(); | |
7252 return value; | |
7253 }, | |
7254 | |
7255 updateIfValue: function(ifValue) { | |
7256 if (!ifValue) { | |
7257 this.valueChanged(); | |
7258 return; | |
7259 } | |
7260 | |
7261 this.updateValue(this.getUpdatedValue()); | |
7262 }, | |
7263 | |
7264 updateIteratedValue: function(value) { | |
7265 if (this.deps.hasIf) { | |
7266 var ifValue = this.deps.ifValue; | |
7267 if (!this.deps.ifOneTime) | |
7268 ifValue = ifValue.discardChanges(); | |
7269 if (!ifValue) { | |
7270 this.valueChanged(); | |
7271 return; | |
7272 } | |
7273 } | |
7274 | |
7275 this.updateValue(value); | |
7276 }, | |
7277 | |
7278 updateValue: function(value) { | |
7279 if (!this.deps.repeat) | |
7280 value = [value]; | |
7281 var observe = this.deps.repeat && | |
7282 !this.deps.oneTime && | |
7283 Array.isArray(value); | |
7284 this.valueChanged(value, observe); | |
7285 }, | |
7286 | |
7287 valueChanged: function(value, observeValue) { | |
7288 if (!Array.isArray(value)) | |
7289 value = []; | |
7290 | |
7291 if (value === this.iteratedValue) | |
7292 return; | |
7293 | |
7294 this.unobserve(); | |
7295 this.presentValue = value; | |
7296 if (observeValue) { | |
7297 this.arrayObserver = new ArrayObserver(this.presentValue); | |
7298 this.arrayObserver.open(this.handleSplices, this); | |
7299 } | |
7300 | |
7301 this.handleSplices(ArrayObserver.calculateSplices(this.presentValue, | |
7302 this.iteratedValue)); | |
7303 }, | |
7304 | |
7305 getLastInstanceNode: function(index) { | |
7306 if (index == -1) | |
7307 return this.templateElement_; | |
7308 var instance = this.instances[index]; | |
7309 var terminator = instance.terminator_; | |
7310 if (!terminator) | |
7311 return this.getLastInstanceNode(index - 1); | |
7312 | |
7313 if (terminator.nodeType !== Node.ELEMENT_NODE || | |
7314 this.templateElement_ === terminator) { | |
7315 return terminator; | |
7316 } | |
7317 | |
7318 var subtemplateIterator = terminator.iterator_; | |
7319 if (!subtemplateIterator) | |
7320 return terminator; | |
7321 | |
7322 return subtemplateIterator.getLastTemplateNode(); | |
7323 }, | |
7324 | |
7325 getLastTemplateNode: function() { | |
7326 return this.getLastInstanceNode(this.instances.length - 1); | |
7327 }, | |
7328 | |
7329 insertInstanceAt: function(index, fragment) { | |
7330 var previousInstanceLast = this.getLastInstanceNode(index - 1); | |
7331 var parent = this.templateElement_.parentNode; | |
7332 this.instances.splice(index, 0, fragment); | |
7333 | |
7334 parent.insertBefore(fragment, previousInstanceLast.nextSibling); | |
7335 }, | |
7336 | |
7337 extractInstanceAt: function(index) { | |
7338 var previousInstanceLast = this.getLastInstanceNode(index - 1); | |
7339 var lastNode = this.getLastInstanceNode(index); | |
7340 var parent = this.templateElement_.parentNode; | |
7341 var instance = this.instances.splice(index, 1)[0]; | |
7342 | |
7343 while (lastNode !== previousInstanceLast) { | |
7344 var node = previousInstanceLast.nextSibling; | |
7345 if (node == lastNode) | |
7346 lastNode = previousInstanceLast; | |
7347 | |
7348 instance.appendChild(parent.removeChild(node)); | |
7349 } | |
7350 | |
7351 return instance; | |
7352 }, | |
7353 | |
7354 getDelegateFn: function(fn) { | |
7355 fn = fn && fn(this.templateElement_); | |
7356 return typeof fn === 'function' ? fn : null; | |
7357 }, | |
7358 | |
7359 handleSplices: function(splices) { | |
7360 if (this.closed || !splices.length) | |
7361 return; | |
7362 | |
7363 var template = this.templateElement_; | |
7364 | |
7365 if (!template.parentNode) { | |
7366 this.close(); | |
7367 return; | |
7368 } | |
7369 | |
7370 ArrayObserver.applySplices(this.iteratedValue, this.presentValue, | |
7371 splices); | |
7372 | |
7373 var delegate = template.delegate_; | |
7374 if (this.instanceModelFn_ === undefined) { | |
7375 this.instanceModelFn_ = | |
7376 this.getDelegateFn(delegate && delegate.prepareInstanceModel); | |
7377 } | |
7378 | |
7379 if (this.instancePositionChangedFn_ === undefined) { | |
7380 this.instancePositionChangedFn_ = | |
7381 this.getDelegateFn(delegate && | |
7382 delegate.prepareInstancePositionChanged); | |
7383 } | |
7384 | |
7385 // Instance Removals | |
7386 var instanceCache = new Map; | |
7387 var removeDelta = 0; | |
7388 for (var i = 0; i < splices.length; i++) { | |
7389 var splice = splices[i]; | |
7390 var removed = splice.removed; | |
7391 for (var j = 0; j < removed.length; j++) { | |
7392 var model = removed[j]; | |
7393 var instance = this.extractInstanceAt(splice.index + removeDelta); | |
7394 if (instance !== emptyInstance) { | |
7395 instanceCache.set(model, instance); | |
7396 } | |
7397 } | |
7398 | |
7399 removeDelta -= splice.addedCount; | |
7400 } | |
7401 | |
7402 // Instance Insertions | |
7403 for (var i = 0; i < splices.length; i++) { | |
7404 var splice = splices[i]; | |
7405 var addIndex = splice.index; | |
7406 for (; addIndex < splice.index + splice.addedCount; addIndex++) { | |
7407 var model = this.iteratedValue[addIndex]; | |
7408 var instance = instanceCache.get(model); | |
7409 if (instance) { | |
7410 instanceCache.delete(model); | |
7411 } else { | |
7412 if (this.instanceModelFn_) { | |
7413 model = this.instanceModelFn_(model); | |
7414 } | |
7415 | |
7416 if (model === undefined) { | |
7417 instance = emptyInstance; | |
7418 } else { | |
7419 instance = template.createInstance(model, undefined, delegate); | |
7420 } | |
7421 } | |
7422 | |
7423 this.insertInstanceAt(addIndex, instance); | |
7424 } | |
7425 } | |
7426 | |
7427 instanceCache.forEach(function(instance) { | |
7428 this.closeInstanceBindings(instance); | |
7429 }, this); | |
7430 | |
7431 if (this.instancePositionChangedFn_) | |
7432 this.reportInstancesMoved(splices); | |
7433 }, | |
7434 | |
7435 reportInstanceMoved: function(index) { | |
7436 var instance = this.instances[index]; | |
7437 if (instance === emptyInstance) | |
7438 return; | |
7439 | |
7440 this.instancePositionChangedFn_(instance.templateInstance_, index); | |
7441 }, | |
7442 | |
7443 reportInstancesMoved: function(splices) { | |
7444 var index = 0; | |
7445 var offset = 0; | |
7446 for (var i = 0; i < splices.length; i++) { | |
7447 var splice = splices[i]; | |
7448 if (offset != 0) { | |
7449 while (index < splice.index) { | |
7450 this.reportInstanceMoved(index); | |
7451 index++; | |
7452 } | |
7453 } else { | |
7454 index = splice.index; | |
7455 } | |
7456 | |
7457 while (index < splice.index + splice.addedCount) { | |
7458 this.reportInstanceMoved(index); | |
7459 index++; | |
7460 } | |
7461 | |
7462 offset += splice.addedCount - splice.removed.length; | |
7463 } | |
7464 | |
7465 if (offset == 0) | |
7466 return; | |
7467 | |
7468 var length = this.instances.length; | |
7469 while (index < length) { | |
7470 this.reportInstanceMoved(index); | |
7471 index++; | |
7472 } | |
7473 }, | |
7474 | |
7475 closeInstanceBindings: function(instance) { | |
7476 var bindings = instance.bindings_; | |
7477 for (var i = 0; i < bindings.length; i++) { | |
7478 bindings[i].close(); | |
7479 } | |
7480 }, | |
7481 | |
7482 unobserve: function() { | |
7483 if (!this.arrayObserver) | |
7484 return; | |
7485 | |
7486 this.arrayObserver.close(); | |
7487 this.arrayObserver = undefined; | |
7488 }, | |
7489 | |
7490 close: function() { | |
7491 if (this.closed) | |
7492 return; | |
7493 this.unobserve(); | |
7494 for (var i = 0; i < this.instances.length; i++) { | |
7495 this.closeInstanceBindings(this.instances[i]); | |
7496 } | |
7497 | |
7498 this.instances.length = 0; | |
7499 this.closeDeps(); | |
7500 this.templateElement_.iterator_ = undefined; | |
7501 this.closed = true; | |
7502 } | |
7503 }; | |
7504 | |
7505 // Polyfill-specific API. | |
7506 HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom; | |
7507 })(this); | |
7508 | |
7509 (function(scope) { | |
7510 'use strict'; | |
7511 | |
7512 // feature detect for URL constructor | |
7513 var hasWorkingUrl = false; | |
7514 if (!scope.forceJURL) { | |
7515 try { | |
7516 var u = new URL('b', 'http://a'); | |
7517 u.pathname = 'c%20d'; | |
7518 hasWorkingUrl = u.href === 'http://a/c%20d'; | |
7519 } catch(e) {} | |
7520 } | |
7521 | |
7522 if (hasWorkingUrl) | |
7523 return; | |
7524 | |
7525 var relative = Object.create(null); | |
7526 relative['ftp'] = 21; | |
7527 relative['file'] = 0; | |
7528 relative['gopher'] = 70; | |
7529 relative['http'] = 80; | |
7530 relative['https'] = 443; | |
7531 relative['ws'] = 80; | |
7532 relative['wss'] = 443; | |
7533 | |
7534 var relativePathDotMapping = Object.create(null); | |
7535 relativePathDotMapping['%2e'] = '.'; | |
7536 relativePathDotMapping['.%2e'] = '..'; | |
7537 relativePathDotMapping['%2e.'] = '..'; | |
7538 relativePathDotMapping['%2e%2e'] = '..'; | |
7539 | |
7540 function isRelativeScheme(scheme) { | |
7541 return relative[scheme] !== undefined; | |
7542 } | |
7543 | |
7544 function invalid() { | |
7545 clear.call(this); | |
7546 this._isInvalid = true; | |
7547 } | |
7548 | |
7549 function IDNAToASCII(h) { | |
7550 if ('' == h) { | |
7551 invalid.call(this) | |
7552 } | |
7553 // XXX | |
7554 return h.toLowerCase() | |
7555 } | |
7556 | |
7557 function percentEscape(c) { | |
7558 var unicode = c.charCodeAt(0); | |
7559 if (unicode > 0x20 && | |
7560 unicode < 0x7F && | |
7561 // " # < > ? ` | |
7562 [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1 | |
7563 ) { | |
7564 return c; | |
7565 } | |
7566 return encodeURIComponent(c); | |
7567 } | |
7568 | |
7569 function percentEscapeQuery(c) { | |
7570 // XXX This actually needs to encode c using encoding and then | |
7571 // convert the bytes one-by-one. | |
7572 | |
7573 var unicode = c.charCodeAt(0); | |
7574 if (unicode > 0x20 && | |
7575 unicode < 0x7F && | |
7576 // " # < > ` (do not escape '?') | |
7577 [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1 | |
7578 ) { | |
7579 return c; | |
7580 } | |
7581 return encodeURIComponent(c); | |
7582 } | |
7583 | |
7584 var EOF = undefined, | |
7585 ALPHA = /[a-zA-Z]/, | |
7586 ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/; | |
7587 | |
7588 function parse(input, stateOverride, base) { | |
7589 function err(message) { | |
7590 errors.push(message) | |
7591 } | |
7592 | |
7593 var state = stateOverride || 'scheme start', | |
7594 cursor = 0, | |
7595 buffer = '', | |
7596 seenAt = false, | |
7597 seenBracket = false, | |
7598 errors = []; | |
7599 | |
7600 loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid)
{ | |
7601 var c = input[cursor]; | |
7602 switch (state) { | |
7603 case 'scheme start': | |
7604 if (c && ALPHA.test(c)) { | |
7605 buffer += c.toLowerCase(); // ASCII-safe | |
7606 state = 'scheme'; | |
7607 } else if (!stateOverride) { | |
7608 buffer = ''; | |
7609 state = 'no scheme'; | |
7610 continue; | |
7611 } else { | |
7612 err('Invalid scheme.'); | |
7613 break loop; | |
7614 } | |
7615 break; | |
7616 | |
7617 case 'scheme': | |
7618 if (c && ALPHANUMERIC.test(c)) { | |
7619 buffer += c.toLowerCase(); // ASCII-safe | |
7620 } else if (':' == c) { | |
7621 this._scheme = buffer; | |
7622 buffer = ''; | |
7623 if (stateOverride) { | |
7624 break loop; | |
7625 } | |
7626 if (isRelativeScheme(this._scheme)) { | |
7627 this._isRelative = true; | |
7628 } | |
7629 if ('file' == this._scheme) { | |
7630 state = 'relative'; | |
7631 } else if (this._isRelative && base && base._scheme == this._scheme)
{ | |
7632 state = 'relative or authority'; | |
7633 } else if (this._isRelative) { | |
7634 state = 'authority first slash'; | |
7635 } else { | |
7636 state = 'scheme data'; | |
7637 } | |
7638 } else if (!stateOverride) { | |
7639 buffer = ''; | |
7640 cursor = 0; | |
7641 state = 'no scheme'; | |
7642 continue; | |
7643 } else if (EOF == c) { | |
7644 break loop; | |
7645 } else { | |
7646 err('Code point not allowed in scheme: ' + c) | |
7647 break loop; | |
7648 } | |
7649 break; | |
7650 | |
7651 case 'scheme data': | |
7652 if ('?' == c) { | |
7653 query = '?'; | |
7654 state = 'query'; | |
7655 } else if ('#' == c) { | |
7656 this._fragment = '#'; | |
7657 state = 'fragment'; | |
7658 } else { | |
7659 // XXX error handling | |
7660 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
7661 this._schemeData += percentEscape(c); | |
7662 } | |
7663 } | |
7664 break; | |
7665 | |
7666 case 'no scheme': | |
7667 if (!base || !(isRelativeScheme(base._scheme))) { | |
7668 err('Missing scheme.'); | |
7669 invalid.call(this); | |
7670 } else { | |
7671 state = 'relative'; | |
7672 continue; | |
7673 } | |
7674 break; | |
7675 | |
7676 case 'relative or authority': | |
7677 if ('/' == c && '/' == input[cursor+1]) { | |
7678 state = 'authority ignore slashes'; | |
7679 } else { | |
7680 err('Expected /, got: ' + c); | |
7681 state = 'relative'; | |
7682 continue | |
7683 } | |
7684 break; | |
7685 | |
7686 case 'relative': | |
7687 this._isRelative = true; | |
7688 if ('file' != this._scheme) | |
7689 this._scheme = base._scheme; | |
7690 if (EOF == c) { | |
7691 this._host = base._host; | |
7692 this._port = base._port; | |
7693 this._path = base._path.slice(); | |
7694 this._query = base._query; | |
7695 break loop; | |
7696 } else if ('/' == c || '\\' == c) { | |
7697 if ('\\' == c) | |
7698 err('\\ is an invalid code point.'); | |
7699 state = 'relative slash'; | |
7700 } else if ('?' == c) { | |
7701 this._host = base._host; | |
7702 this._port = base._port; | |
7703 this._path = base._path.slice(); | |
7704 this._query = '?'; | |
7705 state = 'query'; | |
7706 } else if ('#' == c) { | |
7707 this._host = base._host; | |
7708 this._port = base._port; | |
7709 this._path = base._path.slice(); | |
7710 this._query = base._query; | |
7711 this._fragment = '#'; | |
7712 state = 'fragment'; | |
7713 } else { | |
7714 var nextC = input[cursor+1] | |
7715 var nextNextC = input[cursor+2] | |
7716 if ( | |
7717 'file' != this._scheme || !ALPHA.test(c) || | |
7718 (nextC != ':' && nextC != '|') || | |
7719 (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?'
!= nextNextC && '#' != nextNextC)) { | |
7720 this._host = base._host; | |
7721 this._port = base._port; | |
7722 this._path = base._path.slice(); | |
7723 this._path.pop(); | |
7724 } | |
7725 state = 'relative path'; | |
7726 continue; | |
7727 } | |
7728 break; | |
7729 | |
7730 case 'relative slash': | |
7731 if ('/' == c || '\\' == c) { | |
7732 if ('\\' == c) { | |
7733 err('\\ is an invalid code point.'); | |
7734 } | |
7735 if ('file' == this._scheme) { | |
7736 state = 'file host'; | |
7737 } else { | |
7738 state = 'authority ignore slashes'; | |
7739 } | |
7740 } else { | |
7741 if ('file' != this._scheme) { | |
7742 this._host = base._host; | |
7743 this._port = base._port; | |
7744 } | |
7745 state = 'relative path'; | |
7746 continue; | |
7747 } | |
7748 break; | |
7749 | |
7750 case 'authority first slash': | |
7751 if ('/' == c) { | |
7752 state = 'authority second slash'; | |
7753 } else { | |
7754 err("Expected '/', got: " + c); | |
7755 state = 'authority ignore slashes'; | |
7756 continue; | |
7757 } | |
7758 break; | |
7759 | |
7760 case 'authority second slash': | |
7761 state = 'authority ignore slashes'; | |
7762 if ('/' != c) { | |
7763 err("Expected '/', got: " + c); | |
7764 continue; | |
7765 } | |
7766 break; | |
7767 | |
7768 case 'authority ignore slashes': | |
7769 if ('/' != c && '\\' != c) { | |
7770 state = 'authority'; | |
7771 continue; | |
7772 } else { | |
7773 err('Expected authority, got: ' + c); | |
7774 } | |
7775 break; | |
7776 | |
7777 case 'authority': | |
7778 if ('@' == c) { | |
7779 if (seenAt) { | |
7780 err('@ already seen.'); | |
7781 buffer += '%40'; | |
7782 } | |
7783 seenAt = true; | |
7784 for (var i = 0; i < buffer.length; i++) { | |
7785 var cp = buffer[i]; | |
7786 if ('\t' == cp || '\n' == cp || '\r' == cp) { | |
7787 err('Invalid whitespace in authority.'); | |
7788 continue; | |
7789 } | |
7790 // XXX check URL code points | |
7791 if (':' == cp && null === this._password) { | |
7792 this._password = ''; | |
7793 continue; | |
7794 } | |
7795 var tempC = percentEscape(cp); | |
7796 (null !== this._password) ? this._password += tempC : this._userna
me += tempC; | |
7797 } | |
7798 buffer = ''; | |
7799 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ | |
7800 cursor -= buffer.length; | |
7801 buffer = ''; | |
7802 state = 'host'; | |
7803 continue; | |
7804 } else { | |
7805 buffer += c; | |
7806 } | |
7807 break; | |
7808 | |
7809 case 'file host': | |
7810 if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { | |
7811 if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':'
|| buffer[1] == '|')) { | |
7812 state = 'relative path'; | |
7813 } else if (buffer.length == 0) { | |
7814 state = 'relative path start'; | |
7815 } else { | |
7816 this._host = IDNAToASCII.call(this, buffer); | |
7817 buffer = ''; | |
7818 state = 'relative path start'; | |
7819 } | |
7820 continue; | |
7821 } else if ('\t' == c || '\n' == c || '\r' == c) { | |
7822 err('Invalid whitespace in file host.'); | |
7823 } else { | |
7824 buffer += c; | |
7825 } | |
7826 break; | |
7827 | |
7828 case 'host': | |
7829 case 'hostname': | |
7830 if (':' == c && !seenBracket) { | |
7831 // XXX host parsing | |
7832 this._host = IDNAToASCII.call(this, buffer); | |
7833 buffer = ''; | |
7834 state = 'port'; | |
7835 if ('hostname' == stateOverride) { | |
7836 break loop; | |
7837 } | |
7838 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ | |
7839 this._host = IDNAToASCII.call(this, buffer); | |
7840 buffer = ''; | |
7841 state = 'relative path start'; | |
7842 if (stateOverride) { | |
7843 break loop; | |
7844 } | |
7845 continue; | |
7846 } else if ('\t' != c && '\n' != c && '\r' != c) { | |
7847 if ('[' == c) { | |
7848 seenBracket = true; | |
7849 } else if (']' == c) { | |
7850 seenBracket = false; | |
7851 } | |
7852 buffer += c; | |
7853 } else { | |
7854 err('Invalid code point in host/hostname: ' + c); | |
7855 } | |
7856 break; | |
7857 | |
7858 case 'port': | |
7859 if (/[0-9]/.test(c)) { | |
7860 buffer += c; | |
7861 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c |
| stateOverride) { | |
7862 if ('' != buffer) { | |
7863 var temp = parseInt(buffer, 10); | |
7864 if (temp != relative[this._scheme]) { | |
7865 this._port = temp + ''; | |
7866 } | |
7867 buffer = ''; | |
7868 } | |
7869 if (stateOverride) { | |
7870 break loop; | |
7871 } | |
7872 state = 'relative path start'; | |
7873 continue; | |
7874 } else if ('\t' == c || '\n' == c || '\r' == c) { | |
7875 err('Invalid code point in port: ' + c); | |
7876 } else { | |
7877 invalid.call(this); | |
7878 } | |
7879 break; | |
7880 | |
7881 case 'relative path start': | |
7882 if ('\\' == c) | |
7883 err("'\\' not allowed in path."); | |
7884 state = 'relative path'; | |
7885 if ('/' != c && '\\' != c) { | |
7886 continue; | |
7887 } | |
7888 break; | |
7889 | |
7890 case 'relative path': | |
7891 if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c
|| '#' == c))) { | |
7892 if ('\\' == c) { | |
7893 err('\\ not allowed in relative path.'); | |
7894 } | |
7895 var tmp; | |
7896 if (tmp = relativePathDotMapping[buffer.toLowerCase()]) { | |
7897 buffer = tmp; | |
7898 } | |
7899 if ('..' == buffer) { | |
7900 this._path.pop(); | |
7901 if ('/' != c && '\\' != c) { | |
7902 this._path.push(''); | |
7903 } | |
7904 } else if ('.' == buffer && '/' != c && '\\' != c) { | |
7905 this._path.push(''); | |
7906 } else if ('.' != buffer) { | |
7907 if ('file' == this._scheme && this._path.length == 0 && buffer.len
gth == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') { | |
7908 buffer = buffer[0] + ':'; | |
7909 } | |
7910 this._path.push(buffer); | |
7911 } | |
7912 buffer = ''; | |
7913 if ('?' == c) { | |
7914 this._query = '?'; | |
7915 state = 'query'; | |
7916 } else if ('#' == c) { | |
7917 this._fragment = '#'; | |
7918 state = 'fragment'; | |
7919 } | |
7920 } else if ('\t' != c && '\n' != c && '\r' != c) { | |
7921 buffer += percentEscape(c); | |
7922 } | |
7923 break; | |
7924 | |
7925 case 'query': | |
7926 if (!stateOverride && '#' == c) { | |
7927 this._fragment = '#'; | |
7928 state = 'fragment'; | |
7929 } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
7930 this._query += percentEscapeQuery(c); | |
7931 } | |
7932 break; | |
7933 | |
7934 case 'fragment': | |
7935 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
7936 this._fragment += c; | |
7937 } | |
7938 break; | |
7939 } | |
7940 | |
7941 cursor++; | |
7942 } | |
7943 } | |
7944 | |
7945 function clear() { | |
7946 this._scheme = ''; | |
7947 this._schemeData = ''; | |
7948 this._username = ''; | |
7949 this._password = null; | |
7950 this._host = ''; | |
7951 this._port = ''; | |
7952 this._path = []; | |
7953 this._query = ''; | |
7954 this._fragment = ''; | |
7955 this._isInvalid = false; | |
7956 this._isRelative = false; | |
7957 } | |
7958 | |
7959 // Does not process domain names or IP addresses. | |
7960 // Does not handle encoding for the query parameter. | |
7961 function jURL(url, base /* , encoding */) { | |
7962 if (base !== undefined && !(base instanceof jURL)) | |
7963 base = new jURL(String(base)); | |
7964 | |
7965 this._url = url; | |
7966 clear.call(this); | |
7967 | |
7968 var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ''); | |
7969 // encoding = encoding || 'utf-8' | |
7970 | |
7971 parse.call(this, input, null, base); | |
7972 } | |
7973 | |
7974 jURL.prototype = { | |
7975 get href() { | |
7976 if (this._isInvalid) | |
7977 return this._url; | |
7978 | |
7979 var authority = ''; | |
7980 if ('' != this._username || null != this._password) { | |
7981 authority = this._username + | |
7982 (null != this._password ? ':' + this._password : '') + '@'; | |
7983 } | |
7984 | |
7985 return this.protocol + | |
7986 (this._isRelative ? '//' + authority + this.host : '') + | |
7987 this.pathname + this._query + this._fragment; | |
7988 }, | |
7989 set href(href) { | |
7990 clear.call(this); | |
7991 parse.call(this, href); | |
7992 }, | |
7993 | |
7994 get protocol() { | |
7995 return this._scheme + ':'; | |
7996 }, | |
7997 set protocol(protocol) { | |
7998 if (this._isInvalid) | |
7999 return; | |
8000 parse.call(this, protocol + ':', 'scheme start'); | |
8001 }, | |
8002 | |
8003 get host() { | |
8004 return this._isInvalid ? '' : this._port ? | |
8005 this._host + ':' + this._port : this._host; | |
8006 }, | |
8007 set host(host) { | |
8008 if (this._isInvalid || !this._isRelative) | |
8009 return; | |
8010 parse.call(this, host, 'host'); | |
8011 }, | |
8012 | |
8013 get hostname() { | |
8014 return this._host; | |
8015 }, | |
8016 set hostname(hostname) { | |
8017 if (this._isInvalid || !this._isRelative) | |
8018 return; | |
8019 parse.call(this, hostname, 'hostname'); | |
8020 }, | |
8021 | |
8022 get port() { | |
8023 return this._port; | |
8024 }, | |
8025 set port(port) { | |
8026 if (this._isInvalid || !this._isRelative) | |
8027 return; | |
8028 parse.call(this, port, 'port'); | |
8029 }, | |
8030 | |
8031 get pathname() { | |
8032 return this._isInvalid ? '' : this._isRelative ? | |
8033 '/' + this._path.join('/') : this._schemeData; | |
8034 }, | |
8035 set pathname(pathname) { | |
8036 if (this._isInvalid || !this._isRelative) | |
8037 return; | |
8038 this._path = []; | |
8039 parse.call(this, pathname, 'relative path start'); | |
8040 }, | |
8041 | |
8042 get search() { | |
8043 return this._isInvalid || !this._query || '?' == this._query ? | |
8044 '' : this._query; | |
8045 }, | |
8046 set search(search) { | |
8047 if (this._isInvalid || !this._isRelative) | |
8048 return; | |
8049 this._query = '?'; | |
8050 if ('?' == search[0]) | |
8051 search = search.slice(1); | |
8052 parse.call(this, search, 'query'); | |
8053 }, | |
8054 | |
8055 get hash() { | |
8056 return this._isInvalid || !this._fragment || '#' == this._fragment ? | |
8057 '' : this._fragment; | |
8058 }, | |
8059 set hash(hash) { | |
8060 if (this._isInvalid) | |
8061 return; | |
8062 this._fragment = '#'; | |
8063 if ('#' == hash[0]) | |
8064 hash = hash.slice(1); | |
8065 parse.call(this, hash, 'fragment'); | |
8066 }, | |
8067 | |
8068 get origin() { | |
8069 var host; | |
8070 if (this._isInvalid || !this._scheme) { | |
8071 return ''; | |
8072 } | |
8073 // javascript: Gecko returns String(""), WebKit/Blink String("null") | |
8074 // Gecko throws error for "data://" | |
8075 // data: Gecko returns "", Blink returns "data://", WebKit returns "null" | |
8076 // Gecko returns String("") for file: mailto: | |
8077 // WebKit/Blink returns String("SCHEME://") for file: mailto: | |
8078 switch (this._scheme) { | |
8079 case 'data': | |
8080 case 'file': | |
8081 case 'javascript': | |
8082 case 'mailto': | |
8083 return 'null'; | |
8084 } | |
8085 host = this.host; | |
8086 if (!host) { | |
8087 return ''; | |
8088 } | |
8089 return this._scheme + '://' + host; | |
8090 } | |
8091 }; | |
8092 | |
8093 // Copy over the static methods | |
8094 var OriginalURL = scope.URL; | |
8095 if (OriginalURL) { | |
8096 jURL.createObjectURL = function(blob) { | |
8097 // IE extension allows a second optional options argument. | |
8098 // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx | |
8099 return OriginalURL.createObjectURL.apply(OriginalURL, arguments); | |
8100 }; | |
8101 jURL.revokeObjectURL = function(url) { | |
8102 OriginalURL.revokeObjectURL(url); | |
8103 }; | |
8104 } | |
8105 | |
8106 scope.URL = jURL; | |
8107 | |
8108 })(this); | |
8109 | |
8110 (function(scope) { | |
8111 | |
8112 var iterations = 0; | |
8113 var callbacks = []; | |
8114 var twiddle = document.createTextNode(''); | |
8115 | |
8116 function endOfMicrotask(callback) { | |
8117 twiddle.textContent = iterations++; | |
8118 callbacks.push(callback); | |
8119 } | |
8120 | |
8121 function atEndOfMicrotask() { | |
8122 while (callbacks.length) { | |
8123 callbacks.shift()(); | |
8124 } | |
8125 } | |
8126 | |
8127 new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask) | |
8128 .observe(twiddle, {characterData: true}) | |
8129 ; | |
8130 | |
8131 // exports | |
8132 scope.endOfMicrotask = endOfMicrotask; | |
8133 // bc | |
8134 Platform.endOfMicrotask = endOfMicrotask; | |
8135 | |
8136 })(Polymer); | |
8137 | |
8138 | |
8139 (function(scope) { | |
8140 | |
8141 /** | |
8142 * @class Polymer | |
8143 */ | |
8144 | |
8145 // imports | |
8146 var endOfMicrotask = scope.endOfMicrotask; | |
8147 | |
8148 // logging | |
8149 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
8150 | |
8151 // inject style sheet | |
8152 var style = document.createElement('style'); | |
8153 style.textContent = 'template {display: none !important;} /* injected by platfor
m.js */'; | |
8154 var head = document.querySelector('head'); | |
8155 head.insertBefore(style, head.firstChild); | |
8156 | |
8157 | |
8158 /** | |
8159 * Force any pending data changes to be observed before | |
8160 * the next task. Data changes are processed asynchronously but are guaranteed | |
8161 * to be processed, for example, before painting. This method should rarely be | |
8162 * needed. It does nothing when Object.observe is available; | |
8163 * when Object.observe is not available, Polymer automatically flushes data | |
8164 * changes approximately every 1/10 second. | |
8165 * Therefore, `flush` should only be used when a data mutation should be | |
8166 * observed sooner than this. | |
8167 * | |
8168 * @method flush | |
8169 */ | |
8170 // flush (with logging) | |
8171 var flushing; | |
8172 function flush() { | |
8173 if (!flushing) { | |
8174 flushing = true; | |
8175 endOfMicrotask(function() { | |
8176 flushing = false; | |
8177 log.data && console.group('flush'); | |
8178 Platform.performMicrotaskCheckpoint(); | |
8179 log.data && console.groupEnd(); | |
8180 }); | |
8181 } | |
8182 }; | |
8183 | |
8184 // polling dirty checker | |
8185 // flush periodically if platform does not have object observe. | |
8186 if (!Observer.hasObjectObserve) { | |
8187 var FLUSH_POLL_INTERVAL = 125; | |
8188 window.addEventListener('WebComponentsReady', function() { | |
8189 flush(); | |
8190 // watch document visiblity to toggle dirty-checking | |
8191 var visibilityHandler = function() { | |
8192 // only flush if the page is visibile | |
8193 if (document.visibilityState === 'hidden') { | |
8194 if (scope.flushPoll) { | |
8195 clearInterval(scope.flushPoll); | |
8196 } | |
8197 } else { | |
8198 scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL); | |
8199 } | |
8200 }; | |
8201 if (typeof document.visibilityState === 'string') { | |
8202 document.addEventListener('visibilitychange', visibilityHandler); | |
8203 } | |
8204 visibilityHandler(); | |
8205 }); | |
8206 } else { | |
8207 // make flush a no-op when we have Object.observe | |
8208 flush = function() {}; | |
8209 } | |
8210 | |
8211 if (window.CustomElements && !CustomElements.useNative) { | |
8212 var originalImportNode = Document.prototype.importNode; | |
8213 Document.prototype.importNode = function(node, deep) { | |
8214 var imported = originalImportNode.call(this, node, deep); | |
8215 CustomElements.upgradeAll(imported); | |
8216 return imported; | |
8217 }; | |
8218 } | |
8219 | |
8220 // exports | |
8221 scope.flush = flush; | |
8222 // bc | |
8223 Platform.flush = flush; | |
8224 | |
8225 })(window.Polymer); | |
8226 | |
8227 | |
8228 (function(scope) { | |
8229 | |
8230 var urlResolver = { | |
8231 resolveDom: function(root, url) { | |
8232 url = url || baseUrl(root); | |
8233 this.resolveAttributes(root, url); | |
8234 this.resolveStyles(root, url); | |
8235 // handle template.content | |
8236 var templates = root.querySelectorAll('template'); | |
8237 if (templates) { | |
8238 for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i+
+) { | |
8239 if (t.content) { | |
8240 this.resolveDom(t.content, url); | |
8241 } | |
8242 } | |
8243 } | |
8244 }, | |
8245 resolveTemplate: function(template) { | |
8246 this.resolveDom(template.content, baseUrl(template)); | |
8247 }, | |
8248 resolveStyles: function(root, url) { | |
8249 var styles = root.querySelectorAll('style'); | |
8250 if (styles) { | |
8251 for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) { | |
8252 this.resolveStyle(s, url); | |
8253 } | |
8254 } | |
8255 }, | |
8256 resolveStyle: function(style, url) { | |
8257 url = url || baseUrl(style); | |
8258 style.textContent = this.resolveCssText(style.textContent, url); | |
8259 }, | |
8260 resolveCssText: function(cssText, baseUrl, keepAbsolute) { | |
8261 cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEX
P); | |
8262 return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEX
P); | |
8263 }, | |
8264 resolveAttributes: function(root, url) { | |
8265 if (root.hasAttributes && root.hasAttributes()) { | |
8266 this.resolveElementAttributes(root, url); | |
8267 } | |
8268 // search for attributes that host urls | |
8269 var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR); | |
8270 if (nodes) { | |
8271 for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) { | |
8272 this.resolveElementAttributes(n, url); | |
8273 } | |
8274 } | |
8275 }, | |
8276 resolveElementAttributes: function(node, url) { | |
8277 url = url || baseUrl(node); | |
8278 URL_ATTRS.forEach(function(v) { | |
8279 var attr = node.attributes[v]; | |
8280 var value = attr && attr.value; | |
8281 var replacement; | |
8282 if (value && value.search(URL_TEMPLATE_SEARCH) < 0) { | |
8283 if (v === 'style') { | |
8284 replacement = replaceUrlsInCssText(value, url, false, CSS_URL_REGEXP); | |
8285 } else { | |
8286 replacement = resolveRelativeUrl(url, value); | |
8287 } | |
8288 attr.value = replacement; | |
8289 } | |
8290 }); | |
8291 } | |
8292 }; | |
8293 | |
8294 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; | |
8295 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; | |
8296 var URL_ATTRS = ['href', 'src', 'action', 'style', 'url']; | |
8297 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; | |
8298 var URL_TEMPLATE_SEARCH = '{{.*}}'; | |
8299 var URL_HASH = '#'; | |
8300 | |
8301 function baseUrl(node) { | |
8302 var u = new URL(node.ownerDocument.baseURI); | |
8303 u.search = ''; | |
8304 u.hash = ''; | |
8305 return u; | |
8306 } | |
8307 | |
8308 function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) { | |
8309 return cssText.replace(regexp, function(m, pre, url, post) { | |
8310 var urlPath = url.replace(/["']/g, ''); | |
8311 urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute); | |
8312 return pre + '\'' + urlPath + '\'' + post; | |
8313 }); | |
8314 } | |
8315 | |
8316 function resolveRelativeUrl(baseUrl, url, keepAbsolute) { | |
8317 // do not resolve '/' absolute urls | |
8318 if (url && url[0] === '/') { | |
8319 return url; | |
8320 } | |
8321 // do not resolve '#' links, they are used for routing | |
8322 if (url && url[0] === '#') { | |
8323 return url; | |
8324 } | |
8325 var u = new URL(url, baseUrl); | |
8326 return keepAbsolute ? u.href : makeDocumentRelPath(u.href); | |
8327 } | |
8328 | |
8329 function makeDocumentRelPath(url) { | |
8330 var root = baseUrl(document.documentElement); | |
8331 var u = new URL(url, root); | |
8332 if (u.host === root.host && u.port === root.port && | |
8333 u.protocol === root.protocol) { | |
8334 return makeRelPath(root, u); | |
8335 } else { | |
8336 return url; | |
8337 } | |
8338 } | |
8339 | |
8340 // make a relative path from source to target | |
8341 function makeRelPath(sourceUrl, targetUrl) { | |
8342 var source = sourceUrl.pathname; | |
8343 var target = targetUrl.pathname; | |
8344 var s = source.split('/'); | |
8345 var t = target.split('/'); | |
8346 while (s.length && s[0] === t[0]){ | |
8347 s.shift(); | |
8348 t.shift(); | |
8349 } | |
8350 for (var i = 0, l = s.length - 1; i < l; i++) { | |
8351 t.unshift('..'); | |
8352 } | |
8353 // empty '#' is discarded but we need to preserve it. | |
8354 var hash = (targetUrl.href.slice(-1) === URL_HASH) ? URL_HASH : targetUrl.hash
; | |
8355 return t.join('/') + targetUrl.search + hash; | |
8356 } | |
8357 | |
8358 // exports | |
8359 scope.urlResolver = urlResolver; | |
8360 | |
8361 })(Polymer); | |
8362 | |
8363 (function(scope) { | |
8364 var endOfMicrotask = Polymer.endOfMicrotask; | |
8365 | |
8366 // Generic url loader | |
8367 function Loader(regex) { | |
8368 this.cache = Object.create(null); | |
8369 this.map = Object.create(null); | |
8370 this.requests = 0; | |
8371 this.regex = regex; | |
8372 } | |
8373 Loader.prototype = { | |
8374 | |
8375 // TODO(dfreedm): there may be a better factoring here | |
8376 // extract absolute urls from the text (full of relative urls) | |
8377 extractUrls: function(text, base) { | |
8378 var matches = []; | |
8379 var matched, u; | |
8380 while ((matched = this.regex.exec(text))) { | |
8381 u = new URL(matched[1], base); | |
8382 matches.push({matched: matched[0], url: u.href}); | |
8383 } | |
8384 return matches; | |
8385 }, | |
8386 // take a text blob, a root url, and a callback and load all the urls found
within the text | |
8387 // returns a map of absolute url to text | |
8388 process: function(text, root, callback) { | |
8389 var matches = this.extractUrls(text, root); | |
8390 | |
8391 // every call to process returns all the text this loader has ever receive
d | |
8392 var done = callback.bind(null, this.map); | |
8393 this.fetch(matches, done); | |
8394 }, | |
8395 // build a mapping of url -> text from matches | |
8396 fetch: function(matches, callback) { | |
8397 var inflight = matches.length; | |
8398 | |
8399 // return early if there is no fetching to be done | |
8400 if (!inflight) { | |
8401 return callback(); | |
8402 } | |
8403 | |
8404 // wait for all subrequests to return | |
8405 var done = function() { | |
8406 if (--inflight === 0) { | |
8407 callback(); | |
8408 } | |
8409 }; | |
8410 | |
8411 // start fetching all subrequests | |
8412 var m, req, url; | |
8413 for (var i = 0; i < inflight; i++) { | |
8414 m = matches[i]; | |
8415 url = m.url; | |
8416 req = this.cache[url]; | |
8417 // if this url has already been requested, skip requesting it again | |
8418 if (!req) { | |
8419 req = this.xhr(url); | |
8420 req.match = m; | |
8421 this.cache[url] = req; | |
8422 } | |
8423 // wait for the request to process its subrequests | |
8424 req.wait(done); | |
8425 } | |
8426 }, | |
8427 handleXhr: function(request) { | |
8428 var match = request.match; | |
8429 var url = match.url; | |
8430 | |
8431 // handle errors with an empty string | |
8432 var response = request.response || request.responseText || ''; | |
8433 this.map[url] = response; | |
8434 this.fetch(this.extractUrls(response, url), request.resolve); | |
8435 }, | |
8436 xhr: function(url) { | |
8437 this.requests++; | |
8438 var request = new XMLHttpRequest(); | |
8439 request.open('GET', url, true); | |
8440 request.send(); | |
8441 request.onerror = request.onload = this.handleXhr.bind(this, request); | |
8442 | |
8443 // queue of tasks to run after XHR returns | |
8444 request.pending = []; | |
8445 request.resolve = function() { | |
8446 var pending = request.pending; | |
8447 for(var i = 0; i < pending.length; i++) { | |
8448 pending[i](); | |
8449 } | |
8450 request.pending = null; | |
8451 }; | |
8452 | |
8453 // if we have already resolved, pending is null, async call the callback | |
8454 request.wait = function(fn) { | |
8455 if (request.pending) { | |
8456 request.pending.push(fn); | |
8457 } else { | |
8458 endOfMicrotask(fn); | |
8459 } | |
8460 }; | |
8461 | |
8462 return request; | |
8463 } | |
8464 }; | |
8465 | |
8466 scope.Loader = Loader; | |
8467 })(Polymer); | |
8468 | |
8469 (function(scope) { | |
8470 | |
8471 var urlResolver = scope.urlResolver; | |
8472 var Loader = scope.Loader; | |
8473 | |
8474 function StyleResolver() { | |
8475 this.loader = new Loader(this.regex); | |
8476 } | |
8477 StyleResolver.prototype = { | |
8478 regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g, | |
8479 // Recursively replace @imports with the text at that url | |
8480 resolve: function(text, url, callback) { | |
8481 var done = function(map) { | |
8482 callback(this.flatten(text, url, map)); | |
8483 }.bind(this); | |
8484 this.loader.process(text, url, done); | |
8485 }, | |
8486 // resolve the textContent of a style node | |
8487 resolveNode: function(style, url, callback) { | |
8488 var text = style.textContent; | |
8489 var done = function(text) { | |
8490 style.textContent = text; | |
8491 callback(style); | |
8492 }; | |
8493 this.resolve(text, url, done); | |
8494 }, | |
8495 // flatten all the @imports to text | |
8496 flatten: function(text, base, map) { | |
8497 var matches = this.loader.extractUrls(text, base); | |
8498 var match, url, intermediate; | |
8499 for (var i = 0; i < matches.length; i++) { | |
8500 match = matches[i]; | |
8501 url = match.url; | |
8502 // resolve any css text to be relative to the importer, keep absolute url | |
8503 intermediate = urlResolver.resolveCssText(map[url], url, true); | |
8504 // flatten intermediate @imports | |
8505 intermediate = this.flatten(intermediate, base, map); | |
8506 text = text.replace(match.matched, intermediate); | |
8507 } | |
8508 return text; | |
8509 }, | |
8510 loadStyles: function(styles, base, callback) { | |
8511 var loaded=0, l = styles.length; | |
8512 // called in the context of the style | |
8513 function loadedStyle(style) { | |
8514 loaded++; | |
8515 if (loaded === l && callback) { | |
8516 callback(); | |
8517 } | |
8518 } | |
8519 for (var i=0, s; (i<l) && (s=styles[i]); i++) { | |
8520 this.resolveNode(s, base, loadedStyle); | |
8521 } | |
8522 } | |
8523 }; | |
8524 | |
8525 var styleResolver = new StyleResolver(); | |
8526 | |
8527 // exports | |
8528 scope.styleResolver = styleResolver; | |
8529 | |
8530 })(Polymer); | |
8531 | |
8532 (function(scope) { | |
8533 | |
8534 // copy own properties from 'api' to 'prototype, with name hinting for 'super' | |
8535 function extend(prototype, api) { | |
8536 if (prototype && api) { | |
8537 // use only own properties of 'api' | |
8538 Object.getOwnPropertyNames(api).forEach(function(n) { | |
8539 // acquire property descriptor | |
8540 var pd = Object.getOwnPropertyDescriptor(api, n); | |
8541 if (pd) { | |
8542 // clone property via descriptor | |
8543 Object.defineProperty(prototype, n, pd); | |
8544 // cache name-of-method for 'super' engine | |
8545 if (typeof pd.value == 'function') { | |
8546 // hint the 'super' engine | |
8547 pd.value.nom = n; | |
8548 } | |
8549 } | |
8550 }); | |
8551 } | |
8552 return prototype; | |
8553 } | |
8554 | |
8555 | |
8556 // mixin | |
8557 | |
8558 // copy all properties from inProps (et al) to inObj | |
8559 function mixin(inObj/*, inProps, inMoreProps, ...*/) { | |
8560 var obj = inObj || {}; | |
8561 for (var i = 1; i < arguments.length; i++) { | |
8562 var p = arguments[i]; | |
8563 try { | |
8564 for (var n in p) { | |
8565 copyProperty(n, p, obj); | |
8566 } | |
8567 } catch(x) { | |
8568 } | |
8569 } | |
8570 return obj; | |
8571 } | |
8572 | |
8573 // copy property inName from inSource object to inTarget object | |
8574 function copyProperty(inName, inSource, inTarget) { | |
8575 var pd = getPropertyDescriptor(inSource, inName); | |
8576 Object.defineProperty(inTarget, inName, pd); | |
8577 } | |
8578 | |
8579 // get property descriptor for inName on inObject, even if | |
8580 // inName exists on some link in inObject's prototype chain | |
8581 function getPropertyDescriptor(inObject, inName) { | |
8582 if (inObject) { | |
8583 var pd = Object.getOwnPropertyDescriptor(inObject, inName); | |
8584 return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName
); | |
8585 } | |
8586 } | |
8587 | |
8588 // exports | |
8589 | |
8590 scope.extend = extend; | |
8591 scope.mixin = mixin; | |
8592 | |
8593 // for bc | |
8594 Platform.mixin = mixin; | |
8595 | |
8596 })(Polymer); | |
8597 | |
8598 (function(scope) { | |
8599 | |
8600 // usage | |
8601 | |
8602 // invoke cb.call(this) in 100ms, unless the job is re-registered, | |
8603 // which resets the timer | |
8604 // | |
8605 // this.myJob = this.job(this.myJob, cb, 100) | |
8606 // | |
8607 // returns a job handle which can be used to re-register a job | |
8608 | |
8609 var Job = function(inContext) { | |
8610 this.context = inContext; | |
8611 this.boundComplete = this.complete.bind(this) | |
8612 }; | |
8613 Job.prototype = { | |
8614 go: function(callback, wait) { | |
8615 this.callback = callback; | |
8616 var h; | |
8617 if (!wait) { | |
8618 h = requestAnimationFrame(this.boundComplete); | |
8619 this.handle = function() { | |
8620 cancelAnimationFrame(h); | |
8621 } | |
8622 } else { | |
8623 h = setTimeout(this.boundComplete, wait); | |
8624 this.handle = function() { | |
8625 clearTimeout(h); | |
8626 } | |
8627 } | |
8628 }, | |
8629 stop: function() { | |
8630 if (this.handle) { | |
8631 this.handle(); | |
8632 this.handle = null; | |
8633 } | |
8634 }, | |
8635 complete: function() { | |
8636 if (this.handle) { | |
8637 this.stop(); | |
8638 this.callback.call(this.context); | |
8639 } | |
8640 } | |
8641 }; | |
8642 | |
8643 function job(job, callback, wait) { | |
8644 if (job) { | |
8645 job.stop(); | |
8646 } else { | |
8647 job = new Job(this); | |
8648 } | |
8649 job.go(callback, wait); | |
8650 return job; | |
8651 } | |
8652 | |
8653 // exports | |
8654 | |
8655 scope.job = job; | |
8656 | |
8657 })(Polymer); | |
8658 | |
8659 (function(scope) { | |
8660 | |
8661 // dom polyfill, additions, and utility methods | |
8662 | |
8663 var registry = {}; | |
8664 | |
8665 HTMLElement.register = function(tag, prototype) { | |
8666 registry[tag] = prototype; | |
8667 }; | |
8668 | |
8669 // get prototype mapped to node <tag> | |
8670 HTMLElement.getPrototypeForTag = function(tag) { | |
8671 var prototype = !tag ? HTMLElement.prototype : registry[tag]; | |
8672 // TODO(sjmiles): creating <tag> is likely to have wasteful side-effects | |
8673 return prototype || Object.getPrototypeOf(document.createElement(tag)); | |
8674 }; | |
8675 | |
8676 // we have to flag propagation stoppage for the event dispatcher | |
8677 var originalStopPropagation = Event.prototype.stopPropagation; | |
8678 Event.prototype.stopPropagation = function() { | |
8679 this.cancelBubble = true; | |
8680 originalStopPropagation.apply(this, arguments); | |
8681 }; | |
8682 | |
8683 | |
8684 // polyfill DOMTokenList | |
8685 // * add/remove: allow these methods to take multiple classNames | |
8686 // * toggle: add a 2nd argument which forces the given state rather | |
8687 // than toggling. | |
8688 | |
8689 var add = DOMTokenList.prototype.add; | |
8690 var remove = DOMTokenList.prototype.remove; | |
8691 DOMTokenList.prototype.add = function() { | |
8692 for (var i = 0; i < arguments.length; i++) { | |
8693 add.call(this, arguments[i]); | |
8694 } | |
8695 }; | |
8696 DOMTokenList.prototype.remove = function() { | |
8697 for (var i = 0; i < arguments.length; i++) { | |
8698 remove.call(this, arguments[i]); | |
8699 } | |
8700 }; | |
8701 DOMTokenList.prototype.toggle = function(name, bool) { | |
8702 if (arguments.length == 1) { | |
8703 bool = !this.contains(name); | |
8704 } | |
8705 bool ? this.add(name) : this.remove(name); | |
8706 }; | |
8707 DOMTokenList.prototype.switch = function(oldName, newName) { | |
8708 oldName && this.remove(oldName); | |
8709 newName && this.add(newName); | |
8710 }; | |
8711 | |
8712 // add array() to NodeList, NamedNodeMap, HTMLCollection | |
8713 | |
8714 var ArraySlice = function() { | |
8715 return Array.prototype.slice.call(this); | |
8716 }; | |
8717 | |
8718 var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {}); | |
8719 | |
8720 NodeList.prototype.array = ArraySlice; | |
8721 namedNodeMap.prototype.array = ArraySlice; | |
8722 HTMLCollection.prototype.array = ArraySlice; | |
8723 | |
8724 // utility | |
8725 | |
8726 function createDOM(inTagOrNode, inHTML, inAttrs) { | |
8727 var dom = typeof inTagOrNode == 'string' ? | |
8728 document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true); | |
8729 dom.innerHTML = inHTML; | |
8730 if (inAttrs) { | |
8731 for (var n in inAttrs) { | |
8732 dom.setAttribute(n, inAttrs[n]); | |
8733 } | |
8734 } | |
8735 return dom; | |
8736 } | |
8737 | |
8738 // exports | |
8739 | |
8740 scope.createDOM = createDOM; | |
8741 | |
8742 })(Polymer); | |
8743 | |
8744 (function(scope) { | |
8745 // super | |
8746 | |
8747 // `arrayOfArgs` is an optional array of args like one might pass | |
8748 // to `Function.apply` | |
8749 | |
8750 // TODO(sjmiles): | |
8751 // $super must be installed on an instance or prototype chain | |
8752 // as `super`, and invoked via `this`, e.g. | |
8753 // `this.super();` | |
8754 | |
8755 // will not work if function objects are not unique, for example, | |
8756 // when using mixins. | |
8757 // The memoization strategy assumes each function exists on only one | |
8758 // prototype chain i.e. we use the function object for memoizing) | |
8759 // perhaps we can bookkeep on the prototype itself instead | |
8760 function $super(arrayOfArgs) { | |
8761 // since we are thunking a method call, performance is important here: | |
8762 // memoize all lookups, once memoized the fast path calls no other | |
8763 // functions | |
8764 // | |
8765 // find the caller (cannot be `strict` because of 'caller') | |
8766 var caller = $super.caller; | |
8767 // memoized 'name of method' | |
8768 var nom = caller.nom; | |
8769 // memoized next implementation prototype | |
8770 var _super = caller._super; | |
8771 if (!_super) { | |
8772 if (!nom) { | |
8773 nom = caller.nom = nameInThis.call(this, caller); | |
8774 } | |
8775 if (!nom) { | |
8776 console.warn('called super() on a method not installed declaratively (
has no .nom property)'); | |
8777 } | |
8778 // super prototype is either cached or we have to find it | |
8779 // by searching __proto__ (at the 'top') | |
8780 // invariant: because we cache _super on fn below, we never reach | |
8781 // here from inside a series of calls to super(), so it's ok to | |
8782 // start searching from the prototype of 'this' (at the 'top') | |
8783 // we must never memoize a null super for this reason | |
8784 _super = memoizeSuper(caller, nom, getPrototypeOf(this)); | |
8785 } | |
8786 // our super function | |
8787 var fn = _super[nom]; | |
8788 if (fn) { | |
8789 // memoize information so 'fn' can call 'super' | |
8790 if (!fn._super) { | |
8791 // must not memoize null, or we lose our invariant above | |
8792 memoizeSuper(fn, nom, _super); | |
8793 } | |
8794 // invoke the inherited method | |
8795 // if 'fn' is not function valued, this will throw | |
8796 return fn.apply(this, arrayOfArgs || []); | |
8797 } | |
8798 } | |
8799 | |
8800 function nameInThis(value) { | |
8801 var p = this.__proto__; | |
8802 while (p && p !== HTMLElement.prototype) { | |
8803 // TODO(sjmiles): getOwnPropertyNames is absurdly expensive | |
8804 var n$ = Object.getOwnPropertyNames(p); | |
8805 for (var i=0, l=n$.length, n; i<l && (n=n$[i]); i++) { | |
8806 var d = Object.getOwnPropertyDescriptor(p, n); | |
8807 if (typeof d.value === 'function' && d.value === value) { | |
8808 return n; | |
8809 } | |
8810 } | |
8811 p = p.__proto__; | |
8812 } | |
8813 } | |
8814 | |
8815 function memoizeSuper(method, name, proto) { | |
8816 // find and cache next prototype containing `name` | |
8817 // we need the prototype so we can do another lookup | |
8818 // from here | |
8819 var s = nextSuper(proto, name, method); | |
8820 if (s[name]) { | |
8821 // `s` is a prototype, the actual method is `s[name]` | |
8822 // tag super method with it's name for quicker lookups | |
8823 s[name].nom = name; | |
8824 } | |
8825 return method._super = s; | |
8826 } | |
8827 | |
8828 function nextSuper(proto, name, caller) { | |
8829 // look for an inherited prototype that implements name | |
8830 while (proto) { | |
8831 if ((proto[name] !== caller) && proto[name]) { | |
8832 return proto; | |
8833 } | |
8834 proto = getPrototypeOf(proto); | |
8835 } | |
8836 // must not return null, or we lose our invariant above | |
8837 // in this case, a super() call was invoked where no superclass | |
8838 // method exists | |
8839 // TODO(sjmiles): thow an exception? | |
8840 return Object; | |
8841 } | |
8842 | |
8843 // NOTE: In some platforms (IE10) the prototype chain is faked via | |
8844 // __proto__. Therefore, always get prototype via __proto__ instead of | |
8845 // the more standard Object.getPrototypeOf. | |
8846 function getPrototypeOf(prototype) { | |
8847 return prototype.__proto__; | |
8848 } | |
8849 | |
8850 // utility function to precompute name tags for functions | |
8851 // in a (unchained) prototype | |
8852 function hintSuper(prototype) { | |
8853 // tag functions with their prototype name to optimize | |
8854 // super call invocations | |
8855 for (var n in prototype) { | |
8856 var pd = Object.getOwnPropertyDescriptor(prototype, n); | |
8857 if (pd && typeof pd.value === 'function') { | |
8858 pd.value.nom = n; | |
8859 } | |
8860 } | |
8861 } | |
8862 | |
8863 // exports | |
8864 | |
8865 scope.super = $super; | |
8866 | |
8867 })(Polymer); | |
8868 | |
8869 (function(scope) { | |
8870 | |
8871 function noopHandler(value) { | |
8872 return value; | |
8873 } | |
8874 | |
8875 // helper for deserializing properties of various types to strings | |
8876 var typeHandlers = { | |
8877 string: noopHandler, | |
8878 'undefined': noopHandler, | |
8879 date: function(value) { | |
8880 return new Date(Date.parse(value) || Date.now()); | |
8881 }, | |
8882 boolean: function(value) { | |
8883 if (value === '') { | |
8884 return true; | |
8885 } | |
8886 return value === 'false' ? false : !!value; | |
8887 }, | |
8888 number: function(value) { | |
8889 var n = parseFloat(value); | |
8890 // hex values like "0xFFFF" parseFloat as 0 | |
8891 if (n === 0) { | |
8892 n = parseInt(value); | |
8893 } | |
8894 return isNaN(n) ? value : n; | |
8895 // this code disabled because encoded values (like "0xFFFF") | |
8896 // do not round trip to their original format | |
8897 //return (String(floatVal) === value) ? floatVal : value; | |
8898 }, | |
8899 object: function(value, currentValue) { | |
8900 if (currentValue === null) { | |
8901 return value; | |
8902 } | |
8903 try { | |
8904 // If the string is an object, we can parse is with the JSON library. | |
8905 // include convenience replace for single-quotes. If the author omits | |
8906 // quotes altogether, parse will fail. | |
8907 return JSON.parse(value.replace(/'/g, '"')); | |
8908 } catch(e) { | |
8909 // The object isn't valid JSON, return the raw value | |
8910 return value; | |
8911 } | |
8912 }, | |
8913 // avoid deserialization of functions | |
8914 'function': function(value, currentValue) { | |
8915 return currentValue; | |
8916 } | |
8917 }; | |
8918 | |
8919 function deserializeValue(value, currentValue) { | |
8920 // attempt to infer type from default value | |
8921 var inferredType = typeof currentValue; | |
8922 // invent 'date' type value for Date | |
8923 if (currentValue instanceof Date) { | |
8924 inferredType = 'date'; | |
8925 } | |
8926 // delegate deserialization via type string | |
8927 return typeHandlers[inferredType](value, currentValue); | |
8928 } | |
8929 | |
8930 // exports | |
8931 | |
8932 scope.deserializeValue = deserializeValue; | |
8933 | |
8934 })(Polymer); | |
8935 | |
8936 (function(scope) { | |
8937 | |
8938 // imports | |
8939 | |
8940 var extend = scope.extend; | |
8941 | |
8942 // module | |
8943 | |
8944 var api = {}; | |
8945 | |
8946 api.declaration = {}; | |
8947 api.instance = {}; | |
8948 | |
8949 api.publish = function(apis, prototype) { | |
8950 for (var n in apis) { | |
8951 extend(prototype, apis[n]); | |
8952 } | |
8953 }; | |
8954 | |
8955 // exports | |
8956 | |
8957 scope.api = api; | |
8958 | |
8959 })(Polymer); | |
8960 | |
8961 (function(scope) { | |
8962 | |
8963 /** | |
8964 * @class polymer-base | |
8965 */ | |
8966 | |
8967 var utils = { | |
8968 | |
8969 /** | |
8970 * Invokes a function asynchronously. The context of the callback | |
8971 * function is bound to 'this' automatically. Returns a handle which may | |
8972 * be passed to <a href="#cancelAsync">cancelAsync</a> to cancel the | |
8973 * asynchronous call. | |
8974 * | |
8975 * @method async | |
8976 * @param {Function|String} method | |
8977 * @param {any|Array} args | |
8978 * @param {number} timeout | |
8979 */ | |
8980 async: function(method, args, timeout) { | |
8981 // when polyfilling Object.observe, ensure changes | |
8982 // propagate before executing the async method | |
8983 Polymer.flush(); | |
8984 // second argument to `apply` must be an array | |
8985 args = (args && args.length) ? args : [args]; | |
8986 // function to invoke | |
8987 var fn = function() { | |
8988 (this[method] || method).apply(this, args); | |
8989 }.bind(this); | |
8990 // execute `fn` sooner or later | |
8991 var handle = timeout ? setTimeout(fn, timeout) : | |
8992 requestAnimationFrame(fn); | |
8993 // NOTE: switch on inverting handle to determine which time is used. | |
8994 return timeout ? handle : ~handle; | |
8995 }, | |
8996 | |
8997 /** | |
8998 * Cancels a pending callback that was scheduled via | |
8999 * <a href="#async">async</a>. | |
9000 * | |
9001 * @method cancelAsync | |
9002 * @param {handle} handle Handle of the `async` to cancel. | |
9003 */ | |
9004 cancelAsync: function(handle) { | |
9005 if (handle < 0) { | |
9006 cancelAnimationFrame(~handle); | |
9007 } else { | |
9008 clearTimeout(handle); | |
9009 } | |
9010 }, | |
9011 | |
9012 /** | |
9013 * Fire an event. | |
9014 * | |
9015 * @method fire | |
9016 * @returns {Object} event | |
9017 * @param {string} type An event name. | |
9018 * @param {any} detail | |
9019 * @param {Node} onNode Target node. | |
9020 * @param {Boolean} bubbles Set false to prevent bubbling, defaults to true | |
9021 * @param {Boolean} cancelable Set false to prevent cancellation, defaults
to true | |
9022 */ | |
9023 fire: function(type, detail, onNode, bubbles, cancelable) { | |
9024 var node = onNode || this; | |
9025 var detail = detail === null || detail === undefined ? {} : detail; | |
9026 var event = new CustomEvent(type, { | |
9027 bubbles: bubbles !== undefined ? bubbles : true, | |
9028 cancelable: cancelable !== undefined ? cancelable : true, | |
9029 detail: detail | |
9030 }); | |
9031 node.dispatchEvent(event); | |
9032 return event; | |
9033 }, | |
9034 | |
9035 /** | |
9036 * Fire an event asynchronously. | |
9037 * | |
9038 * @method asyncFire | |
9039 * @param {string} type An event name. | |
9040 * @param detail | |
9041 * @param {Node} toNode Target node. | |
9042 */ | |
9043 asyncFire: function(/*inType, inDetail*/) { | |
9044 this.async("fire", arguments); | |
9045 }, | |
9046 | |
9047 /** | |
9048 * Remove class from old, add class to anew, if they exist. | |
9049 * | |
9050 * @param classFollows | |
9051 * @param anew A node. | |
9052 * @param old A node | |
9053 * @param className | |
9054 */ | |
9055 classFollows: function(anew, old, className) { | |
9056 if (old) { | |
9057 old.classList.remove(className); | |
9058 } | |
9059 if (anew) { | |
9060 anew.classList.add(className); | |
9061 } | |
9062 }, | |
9063 | |
9064 /** | |
9065 * Inject HTML which contains markup bound to this element into | |
9066 * a target element (replacing target element content). | |
9067 * | |
9068 * @param String html to inject | |
9069 * @param Element target element | |
9070 */ | |
9071 injectBoundHTML: function(html, element) { | |
9072 var template = document.createElement('template'); | |
9073 template.innerHTML = html; | |
9074 var fragment = this.instanceTemplate(template); | |
9075 if (element) { | |
9076 element.textContent = ''; | |
9077 element.appendChild(fragment); | |
9078 } | |
9079 return fragment; | |
9080 } | |
9081 }; | |
9082 | |
9083 // no-operation function for handy stubs | |
9084 var nop = function() {}; | |
9085 | |
9086 // null-object for handy stubs | |
9087 var nob = {}; | |
9088 | |
9089 // deprecated | |
9090 | |
9091 utils.asyncMethod = utils.async; | |
9092 | |
9093 // exports | |
9094 | |
9095 scope.api.instance.utils = utils; | |
9096 scope.nop = nop; | |
9097 scope.nob = nob; | |
9098 | |
9099 })(Polymer); | |
9100 | |
9101 (function(scope) { | |
9102 | |
9103 // imports | |
9104 | |
9105 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
9106 var EVENT_PREFIX = 'on-'; | |
9107 | |
9108 // instance events api | |
9109 var events = { | |
9110 // read-only | |
9111 EVENT_PREFIX: EVENT_PREFIX, | |
9112 // event listeners on host | |
9113 addHostListeners: function() { | |
9114 var events = this.eventDelegates; | |
9115 log.events && (Object.keys(events).length > 0) && console.log('[%s] addHos
tListeners:', this.localName, events); | |
9116 // NOTE: host events look like bindings but really are not; | |
9117 // (1) we don't want the attribute to be set and (2) we want to support | |
9118 // multiple event listeners ('host' and 'instance') and Node.bind | |
9119 // by default supports 1 thing being bound. | |
9120 for (var type in events) { | |
9121 var methodName = events[type]; | |
9122 PolymerGestures.addEventListener(this, type, this.element.getEventHandle
r(this, this, methodName)); | |
9123 } | |
9124 }, | |
9125 // call 'method' or function method on 'obj' with 'args', if the method exis
ts | |
9126 dispatchMethod: function(obj, method, args) { | |
9127 if (obj) { | |
9128 log.events && console.group('[%s] dispatch [%s]', obj.localName, method)
; | |
9129 var fn = typeof method === 'function' ? method : obj[method]; | |
9130 if (fn) { | |
9131 fn[args ? 'apply' : 'call'](obj, args); | |
9132 } | |
9133 log.events && console.groupEnd(); | |
9134 // NOTE: dirty check right after calling method to ensure | |
9135 // changes apply quickly; in a very complicated app using high | |
9136 // frequency events, this can be a perf concern; in this case, | |
9137 // imperative handlers can be used to avoid flushing. | |
9138 Polymer.flush(); | |
9139 } | |
9140 } | |
9141 }; | |
9142 | |
9143 // exports | |
9144 | |
9145 scope.api.instance.events = events; | |
9146 | |
9147 /** | |
9148 * @class Polymer | |
9149 */ | |
9150 | |
9151 /** | |
9152 * Add a gesture aware event handler to the given `node`. Can be used | |
9153 * in place of `element.addEventListener` and ensures gestures will function | |
9154 * as expected on mobile platforms. Please note that Polymer's declarative | |
9155 * event handlers include this functionality by default. | |
9156 * | |
9157 * @method addEventListener | |
9158 * @param {Node} node node on which to listen | |
9159 * @param {String} eventType name of the event | |
9160 * @param {Function} handlerFn event handler function | |
9161 * @param {Boolean} capture set to true to invoke event capturing | |
9162 * @type Function | |
9163 */ | |
9164 // alias PolymerGestures event listener logic | |
9165 scope.addEventListener = function(node, eventType, handlerFn, capture) { | |
9166 PolymerGestures.addEventListener(wrap(node), eventType, handlerFn, capture); | |
9167 }; | |
9168 | |
9169 /** | |
9170 * Remove a gesture aware event handler on the given `node`. To remove an | |
9171 * event listener, the exact same arguments are required that were passed | |
9172 * to `Polymer.addEventListener`. | |
9173 * | |
9174 * @method removeEventListener | |
9175 * @param {Node} node node on which to listen | |
9176 * @param {String} eventType name of the event | |
9177 * @param {Function} handlerFn event handler function | |
9178 * @param {Boolean} capture set to true to invoke event capturing | |
9179 * @type Function | |
9180 */ | |
9181 scope.removeEventListener = function(node, eventType, handlerFn, capture) { | |
9182 PolymerGestures.removeEventListener(wrap(node), eventType, handlerFn, captur
e); | |
9183 }; | |
9184 | |
9185 })(Polymer); | |
9186 | |
9187 (function(scope) { | |
9188 | |
9189 // instance api for attributes | |
9190 | |
9191 var attributes = { | |
9192 // copy attributes defined in the element declaration to the instance | |
9193 // e.g. <polymer-element name="x-foo" tabIndex="0"> tabIndex is copied | |
9194 // to the element instance here. | |
9195 copyInstanceAttributes: function () { | |
9196 var a$ = this._instanceAttributes; | |
9197 for (var k in a$) { | |
9198 if (!this.hasAttribute(k)) { | |
9199 this.setAttribute(k, a$[k]); | |
9200 } | |
9201 } | |
9202 }, | |
9203 // for each attribute on this, deserialize value to property as needed | |
9204 takeAttributes: function() { | |
9205 // if we have no publish lookup table, we have no attributes to take | |
9206 // TODO(sjmiles): ad hoc | |
9207 if (this._publishLC) { | |
9208 for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++)
{ | |
9209 this.attributeToProperty(a.name, a.value); | |
9210 } | |
9211 } | |
9212 }, | |
9213 // if attribute 'name' is mapped to a property, deserialize | |
9214 // 'value' into that property | |
9215 attributeToProperty: function(name, value) { | |
9216 // try to match this attribute to a property (attributes are | |
9217 // all lower-case, so this is case-insensitive search) | |
9218 var name = this.propertyForAttribute(name); | |
9219 if (name) { | |
9220 // filter out 'mustached' values, these are to be | |
9221 // replaced with bound-data and are not yet values | |
9222 // themselves | |
9223 if (value && value.search(scope.bindPattern) >= 0) { | |
9224 return; | |
9225 } | |
9226 // get original value | |
9227 var currentValue = this[name]; | |
9228 // deserialize Boolean or Number values from attribute | |
9229 var value = this.deserializeValue(value, currentValue); | |
9230 // only act if the value has changed | |
9231 if (value !== currentValue) { | |
9232 // install new value (has side-effects) | |
9233 this[name] = value; | |
9234 } | |
9235 } | |
9236 }, | |
9237 // return the published property matching name, or undefined | |
9238 propertyForAttribute: function(name) { | |
9239 var match = this._publishLC && this._publishLC[name]; | |
9240 return match; | |
9241 }, | |
9242 // convert representation of `stringValue` based on type of `currentValue` | |
9243 deserializeValue: function(stringValue, currentValue) { | |
9244 return scope.deserializeValue(stringValue, currentValue); | |
9245 }, | |
9246 // convert to a string value based on the type of `inferredType` | |
9247 serializeValue: function(value, inferredType) { | |
9248 if (inferredType === 'boolean') { | |
9249 return value ? '' : undefined; | |
9250 } else if (inferredType !== 'object' && inferredType !== 'function' | |
9251 && value !== undefined) { | |
9252 return value; | |
9253 } | |
9254 }, | |
9255 // serializes `name` property value and updates the corresponding attribute | |
9256 // note that reflection is opt-in. | |
9257 reflectPropertyToAttribute: function(name) { | |
9258 var inferredType = typeof this[name]; | |
9259 // try to intelligently serialize property value | |
9260 var serializedValue = this.serializeValue(this[name], inferredType); | |
9261 // boolean properties must reflect as boolean attributes | |
9262 if (serializedValue !== undefined) { | |
9263 this.setAttribute(name, serializedValue); | |
9264 // TODO(sorvell): we should remove attr for all properties | |
9265 // that have undefined serialization; however, we will need to | |
9266 // refine the attr reflection system to achieve this; pica, for example, | |
9267 // relies on having inferredType object properties not removed as | |
9268 // attrs. | |
9269 } else if (inferredType === 'boolean') { | |
9270 this.removeAttribute(name); | |
9271 } | |
9272 } | |
9273 }; | |
9274 | |
9275 // exports | |
9276 | |
9277 scope.api.instance.attributes = attributes; | |
9278 | |
9279 })(Polymer); | |
9280 | |
9281 (function(scope) { | |
9282 | |
9283 /** | |
9284 * @class polymer-base | |
9285 */ | |
9286 | |
9287 // imports | |
9288 | |
9289 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
9290 | |
9291 // magic words | |
9292 | |
9293 var OBSERVE_SUFFIX = 'Changed'; | |
9294 | |
9295 // element api | |
9296 | |
9297 var empty = []; | |
9298 | |
9299 var updateRecord = { | |
9300 object: undefined, | |
9301 type: 'update', | |
9302 name: undefined, | |
9303 oldValue: undefined | |
9304 }; | |
9305 | |
9306 var numberIsNaN = Number.isNaN || function(value) { | |
9307 return typeof value === 'number' && isNaN(value); | |
9308 }; | |
9309 | |
9310 function areSameValue(left, right) { | |
9311 if (left === right) | |
9312 return left !== 0 || 1 / left === 1 / right; | |
9313 if (numberIsNaN(left) && numberIsNaN(right)) | |
9314 return true; | |
9315 return left !== left && right !== right; | |
9316 } | |
9317 | |
9318 // capture A's value if B's value is null or undefined, | |
9319 // otherwise use B's value | |
9320 function resolveBindingValue(oldValue, value) { | |
9321 if (value === undefined && oldValue === null) { | |
9322 return value; | |
9323 } | |
9324 return (value === null || value === undefined) ? oldValue : value; | |
9325 } | |
9326 | |
9327 var properties = { | |
9328 | |
9329 // creates a CompoundObserver to observe property changes | |
9330 // NOTE, this is only done there are any properties in the `observe` object | |
9331 createPropertyObserver: function() { | |
9332 var n$ = this._observeNames; | |
9333 if (n$ && n$.length) { | |
9334 var o = this._propertyObserver = new CompoundObserver(true); | |
9335 this.registerObserver(o); | |
9336 // TODO(sorvell): may not be kosher to access the value here (this[n]); | |
9337 // previously we looked at the descriptor on the prototype | |
9338 // this doesn't work for inheritance and not for accessors without | |
9339 // a value property | |
9340 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { | |
9341 o.addPath(this, n); | |
9342 this.observeArrayValue(n, this[n], null); | |
9343 } | |
9344 } | |
9345 }, | |
9346 | |
9347 // start observing property changes | |
9348 openPropertyObserver: function() { | |
9349 if (this._propertyObserver) { | |
9350 this._propertyObserver.open(this.notifyPropertyChanges, this); | |
9351 } | |
9352 }, | |
9353 | |
9354 // handler for property changes; routes changes to observing methods | |
9355 // note: array valued properties are observed for array splices | |
9356 notifyPropertyChanges: function(newValues, oldValues, paths) { | |
9357 var name, method, called = {}; | |
9358 for (var i in oldValues) { | |
9359 // note: paths is of form [object, path, object, path] | |
9360 name = paths[2 * i + 1]; | |
9361 method = this.observe[name]; | |
9362 if (method) { | |
9363 var ov = oldValues[i], nv = newValues[i]; | |
9364 // observes the value if it is an array | |
9365 this.observeArrayValue(name, nv, ov); | |
9366 if (!called[method]) { | |
9367 // only invoke change method if one of ov or nv is not (undefined |
null) | |
9368 if ((ov !== undefined && ov !== null) || (nv !== undefined && nv !==
null)) { | |
9369 called[method] = true; | |
9370 // TODO(sorvell): call method with the set of values it's expectin
g; | |
9371 // e.g. 'foo bar': 'invalidate' expects the new and old values for | |
9372 // foo and bar. Currently we give only one of these and then | |
9373 // deliver all the arguments. | |
9374 this.invokeMethod(method, [ov, nv, arguments]); | |
9375 } | |
9376 } | |
9377 } | |
9378 } | |
9379 }, | |
9380 | |
9381 // call method iff it exists. | |
9382 invokeMethod: function(method, args) { | |
9383 var fn = this[method] || method; | |
9384 if (typeof fn === 'function') { | |
9385 fn.apply(this, args); | |
9386 } | |
9387 }, | |
9388 | |
9389 /** | |
9390 * Force any pending property changes to synchronously deliver to | |
9391 * handlers specified in the `observe` object. | |
9392 * Note, normally changes are processed at microtask time. | |
9393 * | |
9394 * @method deliverChanges | |
9395 */ | |
9396 deliverChanges: function() { | |
9397 if (this._propertyObserver) { | |
9398 this._propertyObserver.deliver(); | |
9399 } | |
9400 }, | |
9401 | |
9402 observeArrayValue: function(name, value, old) { | |
9403 // we only care if there are registered side-effects | |
9404 var callbackName = this.observe[name]; | |
9405 if (callbackName) { | |
9406 // if we are observing the previous value, stop | |
9407 if (Array.isArray(old)) { | |
9408 log.observe && console.log('[%s] observeArrayValue: unregister observe
r [%s]', this.localName, name); | |
9409 this.closeNamedObserver(name + '__array'); | |
9410 } | |
9411 // if the new value is an array, being observing it | |
9412 if (Array.isArray(value)) { | |
9413 log.observe && console.log('[%s] observeArrayValue: register observer
[%s]', this.localName, name, value); | |
9414 var observer = new ArrayObserver(value); | |
9415 observer.open(function(splices) { | |
9416 this.invokeMethod(callbackName, [splices]); | |
9417 }, this); | |
9418 this.registerNamedObserver(name + '__array', observer); | |
9419 } | |
9420 } | |
9421 }, | |
9422 | |
9423 emitPropertyChangeRecord: function(name, value, oldValue) { | |
9424 var object = this; | |
9425 if (areSameValue(value, oldValue)) { | |
9426 return; | |
9427 } | |
9428 // invoke property change side effects | |
9429 this._propertyChanged(name, value, oldValue); | |
9430 // emit change record | |
9431 if (!Observer.hasObjectObserve) { | |
9432 return; | |
9433 } | |
9434 var notifier = this._objectNotifier; | |
9435 if (!notifier) { | |
9436 notifier = this._objectNotifier = Object.getNotifier(this); | |
9437 } | |
9438 updateRecord.object = this; | |
9439 updateRecord.name = name; | |
9440 updateRecord.oldValue = oldValue; | |
9441 notifier.notify(updateRecord); | |
9442 }, | |
9443 | |
9444 _propertyChanged: function(name, value, oldValue) { | |
9445 if (this.reflect[name]) { | |
9446 this.reflectPropertyToAttribute(name); | |
9447 } | |
9448 }, | |
9449 | |
9450 // creates a property binding (called via bind) to a published property. | |
9451 bindProperty: function(property, observable, oneTime) { | |
9452 if (oneTime) { | |
9453 this[property] = observable; | |
9454 return; | |
9455 } | |
9456 var computed = this.element.prototype.computed; | |
9457 // Binding an "out-only" value to a computed property. Note that | |
9458 // since this observer isn't opened, it doesn't need to be closed on | |
9459 // cleanup. | |
9460 if (computed && computed[property]) { | |
9461 var privateComputedBoundValue = property + 'ComputedBoundObservable_'; | |
9462 this[privateComputedBoundValue] = observable; | |
9463 return; | |
9464 } | |
9465 return this.bindToAccessor(property, observable, resolveBindingValue); | |
9466 }, | |
9467 | |
9468 // NOTE property `name` must be published. This makes it an accessor. | |
9469 bindToAccessor: function(name, observable, resolveFn) { | |
9470 var privateName = name + '_'; | |
9471 var privateObservable = name + 'Observable_'; | |
9472 // Present for properties which are computed and published and have a | |
9473 // bound value. | |
9474 var privateComputedBoundValue = name + 'ComputedBoundObservable_'; | |
9475 this[privateObservable] = observable; | |
9476 var oldValue = this[privateName]; | |
9477 // observable callback | |
9478 var self = this; | |
9479 function updateValue(value, oldValue) { | |
9480 self[privateName] = value; | |
9481 var setObserveable = self[privateComputedBoundValue]; | |
9482 if (setObserveable && typeof setObserveable.setValue == 'function') { | |
9483 setObserveable.setValue(value); | |
9484 } | |
9485 self.emitPropertyChangeRecord(name, value, oldValue); | |
9486 } | |
9487 // resolve initial value | |
9488 var value = observable.open(updateValue); | |
9489 if (resolveFn && !areSameValue(oldValue, value)) { | |
9490 var resolvedValue = resolveFn(oldValue, value); | |
9491 if (!areSameValue(value, resolvedValue)) { | |
9492 value = resolvedValue; | |
9493 if (observable.setValue) { | |
9494 observable.setValue(value); | |
9495 } | |
9496 } | |
9497 } | |
9498 updateValue(value, oldValue); | |
9499 // register and return observable | |
9500 var observer = { | |
9501 close: function() { | |
9502 observable.close(); | |
9503 self[privateObservable] = undefined; | |
9504 self[privateComputedBoundValue] = undefined; | |
9505 } | |
9506 }; | |
9507 this.registerObserver(observer); | |
9508 return observer; | |
9509 }, | |
9510 | |
9511 createComputedProperties: function() { | |
9512 if (!this._computedNames) { | |
9513 return; | |
9514 } | |
9515 for (var i = 0; i < this._computedNames.length; i++) { | |
9516 var name = this._computedNames[i]; | |
9517 var expressionText = this.computed[name]; | |
9518 try { | |
9519 var expression = PolymerExpressions.getExpression(expressionText); | |
9520 var observable = expression.getBinding(this, this.element.syntax); | |
9521 this.bindToAccessor(name, observable); | |
9522 } catch (ex) { | |
9523 console.error('Failed to create computed property', ex); | |
9524 } | |
9525 } | |
9526 }, | |
9527 | |
9528 // property bookkeeping | |
9529 registerObserver: function(observer) { | |
9530 if (!this._observers) { | |
9531 this._observers = [observer]; | |
9532 return; | |
9533 } | |
9534 this._observers.push(observer); | |
9535 }, | |
9536 | |
9537 closeObservers: function() { | |
9538 if (!this._observers) { | |
9539 return; | |
9540 } | |
9541 // observer array items are arrays of observers. | |
9542 var observers = this._observers; | |
9543 for (var i = 0; i < observers.length; i++) { | |
9544 var observer = observers[i]; | |
9545 if (observer && typeof observer.close == 'function') { | |
9546 observer.close(); | |
9547 } | |
9548 } | |
9549 this._observers = []; | |
9550 }, | |
9551 | |
9552 // bookkeeping observers for memory management | |
9553 registerNamedObserver: function(name, observer) { | |
9554 var o$ = this._namedObservers || (this._namedObservers = {}); | |
9555 o$[name] = observer; | |
9556 }, | |
9557 | |
9558 closeNamedObserver: function(name) { | |
9559 var o$ = this._namedObservers; | |
9560 if (o$ && o$[name]) { | |
9561 o$[name].close(); | |
9562 o$[name] = null; | |
9563 return true; | |
9564 } | |
9565 }, | |
9566 | |
9567 closeNamedObservers: function() { | |
9568 if (this._namedObservers) { | |
9569 for (var i in this._namedObservers) { | |
9570 this.closeNamedObserver(i); | |
9571 } | |
9572 this._namedObservers = {}; | |
9573 } | |
9574 } | |
9575 | |
9576 }; | |
9577 | |
9578 // logging | |
9579 var LOG_OBSERVE = '[%s] watching [%s]'; | |
9580 var LOG_OBSERVED = '[%s#%s] watch: [%s] now [%s] was [%s]'; | |
9581 var LOG_CHANGED = '[%s#%s] propertyChanged: [%s] now [%s] was [%s]'; | |
9582 | |
9583 // exports | |
9584 | |
9585 scope.api.instance.properties = properties; | |
9586 | |
9587 })(Polymer); | |
9588 | |
9589 (function(scope) { | |
9590 | |
9591 /** | |
9592 * @class polymer-base | |
9593 */ | |
9594 | |
9595 // imports | |
9596 | |
9597 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
9598 | |
9599 // element api supporting mdv | |
9600 var mdv = { | |
9601 | |
9602 /** | |
9603 * Creates dom cloned from the given template, instantiating bindings | |
9604 * with this element as the template model and `PolymerExpressions` as the | |
9605 * binding delegate. | |
9606 * | |
9607 * @method instanceTemplate | |
9608 * @param {Template} template source template from which to create dom. | |
9609 */ | |
9610 instanceTemplate: function(template) { | |
9611 // ensure template is decorated (lets' things like <tr template ...> work) | |
9612 HTMLTemplateElement.decorate(template); | |
9613 // ensure a default bindingDelegate | |
9614 var syntax = this.syntax || (!template.bindingDelegate && | |
9615 this.element.syntax); | |
9616 var dom = template.createInstance(this, syntax); | |
9617 var observers = dom.bindings_; | |
9618 for (var i = 0; i < observers.length; i++) { | |
9619 this.registerObserver(observers[i]); | |
9620 } | |
9621 return dom; | |
9622 }, | |
9623 | |
9624 // Called by TemplateBinding/NodeBind to setup a binding to the given | |
9625 // property. It's overridden here to support property bindings | |
9626 // in addition to attribute bindings that are supported by default. | |
9627 bind: function(name, observable, oneTime) { | |
9628 var property = this.propertyForAttribute(name); | |
9629 if (!property) { | |
9630 // TODO(sjmiles): this mixin method must use the special form | |
9631 // of `super` installed by `mixinMethod` in declaration/prototype.js | |
9632 return this.mixinSuper(arguments); | |
9633 } else { | |
9634 // use n-way Polymer binding | |
9635 var observer = this.bindProperty(property, observable, oneTime); | |
9636 // NOTE: reflecting binding information is typically required only for | |
9637 // tooling. It has a performance cost so it's opt-in in Node.bind. | |
9638 if (Platform.enableBindingsReflection && observer) { | |
9639 observer.path = observable.path_; | |
9640 this._recordBinding(property, observer); | |
9641 } | |
9642 if (this.reflect[property]) { | |
9643 this.reflectPropertyToAttribute(property); | |
9644 } | |
9645 return observer; | |
9646 } | |
9647 }, | |
9648 | |
9649 _recordBinding: function(name, observer) { | |
9650 this.bindings_ = this.bindings_ || {}; | |
9651 this.bindings_[name] = observer; | |
9652 }, | |
9653 | |
9654 // Called by TemplateBinding when all bindings on an element have been | |
9655 // executed. This signals that all element inputs have been gathered | |
9656 // and it's safe to ready the element, create shadow-root and start | |
9657 // data-observation. | |
9658 bindFinished: function() { | |
9659 this.makeElementReady(); | |
9660 }, | |
9661 | |
9662 // called at detached time to signal that an element's bindings should be | |
9663 // cleaned up. This is done asynchronously so that users have the chance | |
9664 // to call `cancelUnbindAll` to prevent unbinding. | |
9665 asyncUnbindAll: function() { | |
9666 if (!this._unbound) { | |
9667 log.unbind && console.log('[%s] asyncUnbindAll', this.localName); | |
9668 this._unbindAllJob = this.job(this._unbindAllJob, this.unbindAll, 0); | |
9669 } | |
9670 }, | |
9671 | |
9672 /** | |
9673 * This method should rarely be used and only if | |
9674 * <a href="#cancelUnbindAll">`cancelUnbindAll`</a> has been called to | |
9675 * prevent element unbinding. In this case, the element's bindings will | |
9676 * not be automatically cleaned up and it cannot be garbage collected | |
9677 * by the system. If memory pressure is a concern or a | |
9678 * large amount of elements need to be managed in this way, `unbindAll` | |
9679 * can be called to deactivate the element's bindings and allow its | |
9680 * memory to be reclaimed. | |
9681 * | |
9682 * @method unbindAll | |
9683 */ | |
9684 unbindAll: function() { | |
9685 if (!this._unbound) { | |
9686 this.closeObservers(); | |
9687 this.closeNamedObservers(); | |
9688 this._unbound = true; | |
9689 } | |
9690 }, | |
9691 | |
9692 /** | |
9693 * Call in `detached` to prevent the element from unbinding when it is | |
9694 * detached from the dom. The element is unbound as a cleanup step that | |
9695 * allows its memory to be reclaimed. | |
9696 * If `cancelUnbindAll` is used, consider calling | |
9697 * <a href="#unbindAll">`unbindAll`</a> when the element is no longer | |
9698 * needed. This will allow its memory to be reclaimed. | |
9699 * | |
9700 * @method cancelUnbindAll | |
9701 */ | |
9702 cancelUnbindAll: function() { | |
9703 if (this._unbound) { | |
9704 log.unbind && console.warn('[%s] already unbound, cannot cancel unbindAl
l', this.localName); | |
9705 return; | |
9706 } | |
9707 log.unbind && console.log('[%s] cancelUnbindAll', this.localName); | |
9708 if (this._unbindAllJob) { | |
9709 this._unbindAllJob = this._unbindAllJob.stop(); | |
9710 } | |
9711 } | |
9712 | |
9713 }; | |
9714 | |
9715 function unbindNodeTree(node) { | |
9716 forNodeTree(node, _nodeUnbindAll); | |
9717 } | |
9718 | |
9719 function _nodeUnbindAll(node) { | |
9720 node.unbindAll(); | |
9721 } | |
9722 | |
9723 function forNodeTree(node, callback) { | |
9724 if (node) { | |
9725 callback(node); | |
9726 for (var child = node.firstChild; child; child = child.nextSibling) { | |
9727 forNodeTree(child, callback); | |
9728 } | |
9729 } | |
9730 } | |
9731 | |
9732 var mustachePattern = /\{\{([^{}]*)}}/; | |
9733 | |
9734 // exports | |
9735 | |
9736 scope.bindPattern = mustachePattern; | |
9737 scope.api.instance.mdv = mdv; | |
9738 | |
9739 })(Polymer); | |
9740 | |
9741 (function(scope) { | |
9742 | |
9743 /** | |
9744 * Common prototype for all Polymer Elements. | |
9745 * | |
9746 * @class polymer-base | |
9747 * @homepage polymer.github.io | |
9748 */ | |
9749 var base = { | |
9750 /** | |
9751 * Tags this object as the canonical Base prototype. | |
9752 * | |
9753 * @property PolymerBase | |
9754 * @type boolean | |
9755 * @default true | |
9756 */ | |
9757 PolymerBase: true, | |
9758 | |
9759 /** | |
9760 * Debounce signals. | |
9761 * | |
9762 * Call `job` to defer a named signal, and all subsequent matching signals, | |
9763 * until a wait time has elapsed with no new signal. | |
9764 * | |
9765 * debouncedClickAction: function(e) { | |
9766 * // processClick only when it's been 100ms since the last click | |
9767 * this.job('click', function() { | |
9768 * this.processClick; | |
9769 * }, 100); | |
9770 * } | |
9771 * | |
9772 * @method job | |
9773 * @param String {String} job A string identifier for the job to debounce. | |
9774 * @param Function {Function} callback A function that is called (with `this
` context) when the wait time elapses. | |
9775 * @param Number {Number} wait Time in milliseconds (ms) after the last sign
al that must elapse before invoking `callback` | |
9776 * @type Handle | |
9777 */ | |
9778 job: function(job, callback, wait) { | |
9779 if (typeof job === 'string') { | |
9780 var n = '___' + job; | |
9781 this[n] = Polymer.job.call(this, this[n], callback, wait); | |
9782 } else { | |
9783 // TODO(sjmiles): suggest we deprecate this call signature | |
9784 return Polymer.job.call(this, job, callback, wait); | |
9785 } | |
9786 }, | |
9787 | |
9788 /** | |
9789 * Invoke a superclass method. | |
9790 * | |
9791 * Use `super()` to invoke the most recently overridden call to the | |
9792 * currently executing function. | |
9793 * | |
9794 * To pass arguments through, use the literal `arguments` as the parameter | |
9795 * to `super()`. | |
9796 * | |
9797 * nextPageAction: function(e) { | |
9798 * // invoke the superclass version of `nextPageAction` | |
9799 * this.super(arguments); | |
9800 * } | |
9801 * | |
9802 * To pass custom arguments, arrange them in an array. | |
9803 * | |
9804 * appendSerialNo: function(value, serial) { | |
9805 * // prefix the superclass serial number with our lot # before | |
9806 * // invoking the superlcass | |
9807 * return this.super([value, this.lotNo + serial]) | |
9808 * } | |
9809 * | |
9810 * @method super | |
9811 * @type Any | |
9812 * @param {args) An array of arguments to use when calling the superclass me
thod, or null. | |
9813 */ | |
9814 super: Polymer.super, | |
9815 | |
9816 /** | |
9817 * Lifecycle method called when the element is instantiated. | |
9818 * | |
9819 * Override `created` to perform custom create-time tasks. No need to call | |
9820 * super-class `created` unless you are extending another Polymer element. | |
9821 * Created is called before the element creates `shadowRoot` or prepares | |
9822 * data-observation. | |
9823 * | |
9824 * @method created | |
9825 * @type void | |
9826 */ | |
9827 created: function() { | |
9828 }, | |
9829 | |
9830 /** | |
9831 * Lifecycle method called when the element has populated it's `shadowRoot`, | |
9832 * prepared data-observation, and made itself ready for API interaction. | |
9833 * | |
9834 * @method ready | |
9835 * @type void | |
9836 */ | |
9837 ready: function() { | |
9838 }, | |
9839 | |
9840 /** | |
9841 * Low-level lifecycle method called as part of standard Custom Elements | |
9842 * operation. Polymer implements this method to provide basic default | |
9843 * functionality. For custom create-time tasks, implement `created` | |
9844 * instead, which is called immediately after `createdCallback`. | |
9845 * | |
9846 * @method createdCallback | |
9847 */ | |
9848 createdCallback: function() { | |
9849 if (this.templateInstance && this.templateInstance.model) { | |
9850 console.warn('Attributes on ' + this.localName + ' were data bound ' + | |
9851 'prior to Polymer upgrading the element. This may result in ' + | |
9852 'incorrect binding types.'); | |
9853 } | |
9854 this.created(); | |
9855 this.prepareElement(); | |
9856 if (!this.ownerDocument.isStagingDocument) { | |
9857 this.makeElementReady(); | |
9858 } | |
9859 }, | |
9860 | |
9861 // system entry point, do not override | |
9862 prepareElement: function() { | |
9863 if (this._elementPrepared) { | |
9864 console.warn('Element already prepared', this.localName); | |
9865 return; | |
9866 } | |
9867 this._elementPrepared = true; | |
9868 // storage for shadowRoots info | |
9869 this.shadowRoots = {}; | |
9870 // install property observers | |
9871 this.createPropertyObserver(); | |
9872 this.openPropertyObserver(); | |
9873 // install boilerplate attributes | |
9874 this.copyInstanceAttributes(); | |
9875 // process input attributes | |
9876 this.takeAttributes(); | |
9877 // add event listeners | |
9878 this.addHostListeners(); | |
9879 }, | |
9880 | |
9881 // system entry point, do not override | |
9882 makeElementReady: function() { | |
9883 if (this._readied) { | |
9884 return; | |
9885 } | |
9886 this._readied = true; | |
9887 this.createComputedProperties(); | |
9888 this.parseDeclarations(this.__proto__); | |
9889 // NOTE: Support use of the `unresolved` attribute to help polyfill | |
9890 // custom elements' `:unresolved` feature. | |
9891 this.removeAttribute('unresolved'); | |
9892 // user entry point | |
9893 this.ready(); | |
9894 }, | |
9895 | |
9896 /** | |
9897 * Low-level lifecycle method called as part of standard Custom Elements | |
9898 * operation. Polymer implements this method to provide basic default | |
9899 * functionality. For custom tasks in your element, implement `attributeChan
ged` | |
9900 * instead, which is called immediately after `attributeChangedCallback`. | |
9901 * | |
9902 * @method attributeChangedCallback | |
9903 */ | |
9904 attributeChangedCallback: function(name, oldValue) { | |
9905 // TODO(sjmiles): adhoc filter | |
9906 if (name !== 'class' && name !== 'style') { | |
9907 this.attributeToProperty(name, this.getAttribute(name)); | |
9908 } | |
9909 if (this.attributeChanged) { | |
9910 this.attributeChanged.apply(this, arguments); | |
9911 } | |
9912 }, | |
9913 | |
9914 /** | |
9915 * Low-level lifecycle method called as part of standard Custom Elements | |
9916 * operation. Polymer implements this method to provide basic default | |
9917 * functionality. For custom create-time tasks, implement `attached` | |
9918 * instead, which is called immediately after `attachedCallback`. | |
9919 * | |
9920 * @method attachedCallback | |
9921 */ | |
9922 attachedCallback: function() { | |
9923 // when the element is attached, prevent it from unbinding. | |
9924 this.cancelUnbindAll(); | |
9925 // invoke user action | |
9926 if (this.attached) { | |
9927 this.attached(); | |
9928 } | |
9929 if (!this.hasBeenAttached) { | |
9930 this.hasBeenAttached = true; | |
9931 if (this.domReady) { | |
9932 this.async('domReady'); | |
9933 } | |
9934 } | |
9935 }, | |
9936 | |
9937 /** | |
9938 * Implement to access custom elements in dom descendants, ancestors, | |
9939 * or siblings. Because custom elements upgrade in document order, | |
9940 * elements accessed in `ready` or `attached` may not be upgraded. When | |
9941 * `domReady` is called, all registered custom elements are guaranteed | |
9942 * to have been upgraded. | |
9943 * | |
9944 * @method domReady | |
9945 */ | |
9946 | |
9947 /** | |
9948 * Low-level lifecycle method called as part of standard Custom Elements | |
9949 * operation. Polymer implements this method to provide basic default | |
9950 * functionality. For custom create-time tasks, implement `detached` | |
9951 * instead, which is called immediately after `detachedCallback`. | |
9952 * | |
9953 * @method detachedCallback | |
9954 */ | |
9955 detachedCallback: function() { | |
9956 if (!this.preventDispose) { | |
9957 this.asyncUnbindAll(); | |
9958 } | |
9959 // invoke user action | |
9960 if (this.detached) { | |
9961 this.detached(); | |
9962 } | |
9963 // TODO(sorvell): bc | |
9964 if (this.leftView) { | |
9965 this.leftView(); | |
9966 } | |
9967 }, | |
9968 | |
9969 /** | |
9970 * Walks the prototype-chain of this element and allows specific | |
9971 * classes a chance to process static declarations. | |
9972 * | |
9973 * In particular, each polymer-element has it's own `template`. | |
9974 * `parseDeclarations` is used to accumulate all element `template`s | |
9975 * from an inheritance chain. | |
9976 * | |
9977 * `parseDeclaration` static methods implemented in the chain are called | |
9978 * recursively, oldest first, with the `<polymer-element>` associated | |
9979 * with the current prototype passed as an argument. | |
9980 * | |
9981 * An element may override this method to customize shadow-root generation. | |
9982 * | |
9983 * @method parseDeclarations | |
9984 */ | |
9985 parseDeclarations: function(p) { | |
9986 if (p && p.element) { | |
9987 this.parseDeclarations(p.__proto__); | |
9988 p.parseDeclaration.call(this, p.element); | |
9989 } | |
9990 }, | |
9991 | |
9992 /** | |
9993 * Perform init-time actions based on static information in the | |
9994 * `<polymer-element>` instance argument. | |
9995 * | |
9996 * For example, the standard implementation locates the template associated | |
9997 * with the given `<polymer-element>` and stamps it into a shadow-root to | |
9998 * implement shadow inheritance. | |
9999 * | |
10000 * An element may override this method for custom behavior. | |
10001 * | |
10002 * @method parseDeclaration | |
10003 */ | |
10004 parseDeclaration: function(elementElement) { | |
10005 var template = this.fetchTemplate(elementElement); | |
10006 if (template) { | |
10007 var root = this.shadowFromTemplate(template); | |
10008 this.shadowRoots[elementElement.name] = root; | |
10009 } | |
10010 }, | |
10011 | |
10012 /** | |
10013 * Given a `<polymer-element>`, find an associated template (if any) to be | |
10014 * used for shadow-root generation. | |
10015 * | |
10016 * An element may override this method for custom behavior. | |
10017 * | |
10018 * @method fetchTemplate | |
10019 */ | |
10020 fetchTemplate: function(elementElement) { | |
10021 return elementElement.querySelector('template'); | |
10022 }, | |
10023 | |
10024 /** | |
10025 * Create a shadow-root in this host and stamp `template` as it's | |
10026 * content. | |
10027 * | |
10028 * An element may override this method for custom behavior. | |
10029 * | |
10030 * @method shadowFromTemplate | |
10031 */ | |
10032 shadowFromTemplate: function(template) { | |
10033 if (template) { | |
10034 // make a shadow root | |
10035 var root = this.createShadowRoot(); | |
10036 // stamp template | |
10037 // which includes parsing and applying MDV bindings before being | |
10038 // inserted (to avoid {{}} in attribute values) | |
10039 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. | |
10040 var dom = this.instanceTemplate(template); | |
10041 // append to shadow dom | |
10042 root.appendChild(dom); | |
10043 // perform post-construction initialization tasks on shadow root | |
10044 this.shadowRootReady(root, template); | |
10045 // return the created shadow root | |
10046 return root; | |
10047 } | |
10048 }, | |
10049 | |
10050 // utility function that stamps a <template> into light-dom | |
10051 lightFromTemplate: function(template, refNode) { | |
10052 if (template) { | |
10053 // TODO(sorvell): mark this element as an eventController so that | |
10054 // event listeners on bound nodes inside it will be called on it. | |
10055 // Note, the expectation here is that events on all descendants | |
10056 // should be handled by this element. | |
10057 this.eventController = this; | |
10058 // stamp template | |
10059 // which includes parsing and applying MDV bindings before being | |
10060 // inserted (to avoid {{}} in attribute values) | |
10061 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. | |
10062 var dom = this.instanceTemplate(template); | |
10063 // append to shadow dom | |
10064 if (refNode) { | |
10065 this.insertBefore(dom, refNode); | |
10066 } else { | |
10067 this.appendChild(dom); | |
10068 } | |
10069 // perform post-construction initialization tasks on ahem, light root | |
10070 this.shadowRootReady(this); | |
10071 // return the created shadow root | |
10072 return dom; | |
10073 } | |
10074 }, | |
10075 | |
10076 shadowRootReady: function(root) { | |
10077 // locate nodes with id and store references to them in this.$ hash | |
10078 this.marshalNodeReferences(root); | |
10079 }, | |
10080 | |
10081 // locate nodes with id and store references to them in this.$ hash | |
10082 marshalNodeReferences: function(root) { | |
10083 // establish $ instance variable | |
10084 var $ = this.$ = this.$ || {}; | |
10085 // populate $ from nodes with ID from the LOCAL tree | |
10086 if (root) { | |
10087 var n$ = root.querySelectorAll("[id]"); | |
10088 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { | |
10089 $[n.id] = n; | |
10090 }; | |
10091 } | |
10092 }, | |
10093 | |
10094 /** | |
10095 * Register a one-time callback when a child-list or sub-tree mutation | |
10096 * occurs on node. | |
10097 * | |
10098 * For persistent callbacks, call onMutation from your listener. | |
10099 * | |
10100 * @method onMutation | |
10101 * @param Node {Node} node Node to watch for mutations. | |
10102 * @param Function {Function} listener Function to call on mutation. The fun
ction is invoked as `listener.call(this, observer, mutations);` where `observer`
is the MutationObserver that triggered the notification, and `mutations` is the
native mutation list. | |
10103 */ | |
10104 onMutation: function(node, listener) { | |
10105 var observer = new MutationObserver(function(mutations) { | |
10106 listener.call(this, observer, mutations); | |
10107 observer.disconnect(); | |
10108 }.bind(this)); | |
10109 observer.observe(node, {childList: true, subtree: true}); | |
10110 } | |
10111 }; | |
10112 | |
10113 /** | |
10114 * @class Polymer | |
10115 */ | |
10116 | |
10117 /** | |
10118 * Returns true if the object includes <a href="#polymer-base">polymer-base</a
> in it's prototype chain. | |
10119 * | |
10120 * @method isBase | |
10121 * @param Object {Object} object Object to test. | |
10122 * @type Boolean | |
10123 */ | |
10124 function isBase(object) { | |
10125 return object.hasOwnProperty('PolymerBase') | |
10126 } | |
10127 | |
10128 // name a base constructor for dev tools | |
10129 | |
10130 /** | |
10131 * The Polymer base-class constructor. | |
10132 * | |
10133 * @property Base | |
10134 * @type Function | |
10135 */ | |
10136 function PolymerBase() {}; | |
10137 PolymerBase.prototype = base; | |
10138 base.constructor = PolymerBase; | |
10139 | |
10140 // exports | |
10141 | |
10142 scope.Base = PolymerBase; | |
10143 scope.isBase = isBase; | |
10144 scope.api.instance.base = base; | |
10145 | |
10146 })(Polymer); | |
10147 | |
10148 (function(scope) { | |
10149 | |
10150 // imports | |
10151 | |
10152 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
10153 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; | |
10154 | |
10155 // magic words | |
10156 | |
10157 var STYLE_SCOPE_ATTRIBUTE = 'element'; | |
10158 var STYLE_CONTROLLER_SCOPE = 'controller'; | |
10159 | |
10160 var styles = { | |
10161 STYLE_SCOPE_ATTRIBUTE: STYLE_SCOPE_ATTRIBUTE, | |
10162 /** | |
10163 * Installs external stylesheets and <style> elements with the attribute | |
10164 * polymer-scope='controller' into the scope of element. This is intended | |
10165 * to be a called during custom element construction. | |
10166 */ | |
10167 installControllerStyles: function() { | |
10168 // apply controller styles, but only if they are not yet applied | |
10169 var scope = this.findStyleScope(); | |
10170 if (scope && !this.scopeHasNamedStyle(scope, this.localName)) { | |
10171 // allow inherited controller styles | |
10172 var proto = getPrototypeOf(this), cssText = ''; | |
10173 while (proto && proto.element) { | |
10174 cssText += proto.element.cssTextForScope(STYLE_CONTROLLER_SCOPE); | |
10175 proto = getPrototypeOf(proto); | |
10176 } | |
10177 if (cssText) { | |
10178 this.installScopeCssText(cssText, scope); | |
10179 } | |
10180 } | |
10181 }, | |
10182 installScopeStyle: function(style, name, scope) { | |
10183 var scope = scope || this.findStyleScope(), name = name || ''; | |
10184 if (scope && !this.scopeHasNamedStyle(scope, this.localName + name)) { | |
10185 var cssText = ''; | |
10186 if (style instanceof Array) { | |
10187 for (var i=0, l=style.length, s; (i<l) && (s=style[i]); i++) { | |
10188 cssText += s.textContent + '\n\n'; | |
10189 } | |
10190 } else { | |
10191 cssText = style.textContent; | |
10192 } | |
10193 this.installScopeCssText(cssText, scope, name); | |
10194 } | |
10195 }, | |
10196 installScopeCssText: function(cssText, scope, name) { | |
10197 scope = scope || this.findStyleScope(); | |
10198 name = name || ''; | |
10199 if (!scope) { | |
10200 return; | |
10201 } | |
10202 if (hasShadowDOMPolyfill) { | |
10203 cssText = shimCssText(cssText, scope.host); | |
10204 } | |
10205 var style = this.element.cssTextToScopeStyle(cssText, | |
10206 STYLE_CONTROLLER_SCOPE); | |
10207 Polymer.applyStyleToScope(style, scope); | |
10208 // cache that this style has been applied | |
10209 this.styleCacheForScope(scope)[this.localName + name] = true; | |
10210 }, | |
10211 findStyleScope: function(node) { | |
10212 // find the shadow root that contains this element | |
10213 var n = node || this; | |
10214 while (n.parentNode) { | |
10215 n = n.parentNode; | |
10216 } | |
10217 return n; | |
10218 }, | |
10219 scopeHasNamedStyle: function(scope, name) { | |
10220 var cache = this.styleCacheForScope(scope); | |
10221 return cache[name]; | |
10222 }, | |
10223 styleCacheForScope: function(scope) { | |
10224 if (hasShadowDOMPolyfill) { | |
10225 var scopeName = scope.host ? scope.host.localName : scope.localName; | |
10226 return polyfillScopeStyleCache[scopeName] || (polyfillScopeStyleCache[sc
opeName] = {}); | |
10227 } else { | |
10228 return scope._scopeStyles = (scope._scopeStyles || {}); | |
10229 } | |
10230 } | |
10231 }; | |
10232 | |
10233 var polyfillScopeStyleCache = {}; | |
10234 | |
10235 // NOTE: use raw prototype traversal so that we ensure correct traversal | |
10236 // on platforms where the protoype chain is simulated via __proto__ (IE10) | |
10237 function getPrototypeOf(prototype) { | |
10238 return prototype.__proto__; | |
10239 } | |
10240 | |
10241 function shimCssText(cssText, host) { | |
10242 var name = '', is = false; | |
10243 if (host) { | |
10244 name = host.localName; | |
10245 is = host.hasAttribute('is'); | |
10246 } | |
10247 var selector = WebComponents.ShadowCSS.makeScopeSelector(name, is); | |
10248 return WebComponents.ShadowCSS.shimCssText(cssText, selector); | |
10249 } | |
10250 | |
10251 // exports | |
10252 | |
10253 scope.api.instance.styles = styles; | |
10254 | |
10255 })(Polymer); | |
10256 | |
10257 (function(scope) { | |
10258 | |
10259 // imports | |
10260 | |
10261 var extend = scope.extend; | |
10262 var api = scope.api; | |
10263 | |
10264 // imperative implementation: Polymer() | |
10265 | |
10266 // specify an 'own' prototype for tag `name` | |
10267 function element(name, prototype) { | |
10268 if (typeof name !== 'string') { | |
10269 var script = prototype || document._currentScript; | |
10270 prototype = name; | |
10271 name = script && script.parentNode && script.parentNode.getAttribute ? | |
10272 script.parentNode.getAttribute('name') : ''; | |
10273 if (!name) { | |
10274 throw 'Element name could not be inferred.'; | |
10275 } | |
10276 } | |
10277 if (getRegisteredPrototype(name)) { | |
10278 throw 'Already registered (Polymer) prototype for element ' + name; | |
10279 } | |
10280 // cache the prototype | |
10281 registerPrototype(name, prototype); | |
10282 // notify the registrar waiting for 'name', if any | |
10283 notifyPrototype(name); | |
10284 } | |
10285 | |
10286 // async prototype source | |
10287 | |
10288 function waitingForPrototype(name, client) { | |
10289 waitPrototype[name] = client; | |
10290 } | |
10291 | |
10292 var waitPrototype = {}; | |
10293 | |
10294 function notifyPrototype(name) { | |
10295 if (waitPrototype[name]) { | |
10296 waitPrototype[name].registerWhenReady(); | |
10297 delete waitPrototype[name]; | |
10298 } | |
10299 } | |
10300 | |
10301 // utility and bookkeeping | |
10302 | |
10303 // maps tag names to prototypes, as registered with | |
10304 // Polymer. Prototypes associated with a tag name | |
10305 // using document.registerElement are available from | |
10306 // HTMLElement.getPrototypeForTag(). | |
10307 // If an element was fully registered by Polymer, then | |
10308 // Polymer.getRegisteredPrototype(name) === | |
10309 // HTMLElement.getPrototypeForTag(name) | |
10310 | |
10311 var prototypesByName = {}; | |
10312 | |
10313 function registerPrototype(name, prototype) { | |
10314 return prototypesByName[name] = prototype || {}; | |
10315 } | |
10316 | |
10317 function getRegisteredPrototype(name) { | |
10318 return prototypesByName[name]; | |
10319 } | |
10320 | |
10321 function instanceOfType(element, type) { | |
10322 if (typeof type !== 'string') { | |
10323 return false; | |
10324 } | |
10325 var proto = HTMLElement.getPrototypeForTag(type); | |
10326 var ctor = proto && proto.constructor; | |
10327 if (!ctor) { | |
10328 return false; | |
10329 } | |
10330 if (CustomElements.instanceof) { | |
10331 return CustomElements.instanceof(element, ctor); | |
10332 } | |
10333 return element instanceof ctor; | |
10334 } | |
10335 | |
10336 // exports | |
10337 | |
10338 scope.getRegisteredPrototype = getRegisteredPrototype; | |
10339 scope.waitingForPrototype = waitingForPrototype; | |
10340 scope.instanceOfType = instanceOfType; | |
10341 | |
10342 // namespace shenanigans so we can expose our scope on the registration | |
10343 // function | |
10344 | |
10345 // make window.Polymer reference `element()` | |
10346 | |
10347 window.Polymer = element; | |
10348 | |
10349 // TODO(sjmiles): find a way to do this that is less terrible | |
10350 // copy window.Polymer properties onto `element()` | |
10351 | |
10352 extend(Polymer, scope); | |
10353 | |
10354 // Under the HTMLImports polyfill, scripts in the main document | |
10355 // do not block on imports; we want to allow calls to Polymer in the main | |
10356 // document. WebComponents collects those calls until we can process them, whi
ch | |
10357 // we do here. | |
10358 | |
10359 if (WebComponents.consumeDeclarations) { | |
10360 WebComponents.consumeDeclarations(function(declarations) { | |
10361 if (declarations) { | |
10362 for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i+
+) { | |
10363 element.apply(null, d); | |
10364 } | |
10365 } | |
10366 }); | |
10367 } | |
10368 | |
10369 })(Polymer); | |
10370 | |
10371 (function(scope) { | |
10372 | |
10373 /** | |
10374 * @class polymer-base | |
10375 */ | |
10376 | |
10377 /** | |
10378 * Resolve a url path to be relative to a `base` url. If unspecified, `base` | |
10379 * defaults to the element's ownerDocument url. Can be used to resolve | |
10380 * paths from element's in templates loaded in HTMLImports to be relative | |
10381 * to the document containing the element. Polymer automatically does this for | |
10382 * url attributes in element templates; however, if a url, for | |
10383 * example, contains a binding, then `resolvePath` can be used to ensure it is | |
10384 * relative to the element document. For example, in an element's template, | |
10385 * | |
10386 * <a href="{{resolvePath(path)}}">Resolved</a> | |
10387 * | |
10388 * @method resolvePath | |
10389 * @param {String} url Url path to resolve. | |
10390 * @param {String} base Optional base url against which to resolve, defaults | |
10391 * to the element's ownerDocument url. | |
10392 * returns {String} resolved url. | |
10393 */ | |
10394 | |
10395 var path = { | |
10396 resolveElementPaths: function(node) { | |
10397 Polymer.urlResolver.resolveDom(node); | |
10398 }, | |
10399 addResolvePathApi: function() { | |
10400 // let assetpath attribute modify the resolve path | |
10401 var assetPath = this.getAttribute('assetpath') || ''; | |
10402 var root = new URL(assetPath, this.ownerDocument.baseURI); | |
10403 this.prototype.resolvePath = function(urlPath, base) { | |
10404 var u = new URL(urlPath, base || root); | |
10405 return u.href; | |
10406 }; | |
10407 } | |
10408 }; | |
10409 | |
10410 // exports | |
10411 scope.api.declaration.path = path; | |
10412 | |
10413 })(Polymer); | |
10414 | |
10415 (function(scope) { | |
10416 | |
10417 // imports | |
10418 | |
10419 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
10420 var api = scope.api.instance.styles; | |
10421 var STYLE_SCOPE_ATTRIBUTE = api.STYLE_SCOPE_ATTRIBUTE; | |
10422 | |
10423 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; | |
10424 | |
10425 // magic words | |
10426 | |
10427 var STYLE_SELECTOR = 'style'; | |
10428 var STYLE_LOADABLE_MATCH = '@import'; | |
10429 var SHEET_SELECTOR = 'link[rel=stylesheet]'; | |
10430 var STYLE_GLOBAL_SCOPE = 'global'; | |
10431 var SCOPE_ATTR = 'polymer-scope'; | |
10432 | |
10433 var styles = { | |
10434 // returns true if resources are loading | |
10435 loadStyles: function(callback) { | |
10436 var template = this.fetchTemplate(); | |
10437 var content = template && this.templateContent(); | |
10438 if (content) { | |
10439 this.convertSheetsToStyles(content); | |
10440 var styles = this.findLoadableStyles(content); | |
10441 if (styles.length) { | |
10442 var templateUrl = template.ownerDocument.baseURI; | |
10443 return Polymer.styleResolver.loadStyles(styles, templateUrl, callback)
; | |
10444 } | |
10445 } | |
10446 if (callback) { | |
10447 callback(); | |
10448 } | |
10449 }, | |
10450 convertSheetsToStyles: function(root) { | |
10451 var s$ = root.querySelectorAll(SHEET_SELECTOR); | |
10452 for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) { | |
10453 c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI)
, | |
10454 this.ownerDocument); | |
10455 this.copySheetAttributes(c, s); | |
10456 s.parentNode.replaceChild(c, s); | |
10457 } | |
10458 }, | |
10459 copySheetAttributes: function(style, link) { | |
10460 for (var i=0, a$=link.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) { | |
10461 if (a.name !== 'rel' && a.name !== 'href') { | |
10462 style.setAttribute(a.name, a.value); | |
10463 } | |
10464 } | |
10465 }, | |
10466 findLoadableStyles: function(root) { | |
10467 var loadables = []; | |
10468 if (root) { | |
10469 var s$ = root.querySelectorAll(STYLE_SELECTOR); | |
10470 for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) { | |
10471 if (s.textContent.match(STYLE_LOADABLE_MATCH)) { | |
10472 loadables.push(s); | |
10473 } | |
10474 } | |
10475 } | |
10476 return loadables; | |
10477 }, | |
10478 /** | |
10479 * Install external stylesheets loaded in <polymer-element> elements into th
e | |
10480 * element's template. | |
10481 * @param elementElement The <element> element to style. | |
10482 */ | |
10483 installSheets: function() { | |
10484 this.cacheSheets(); | |
10485 this.cacheStyles(); | |
10486 this.installLocalSheets(); | |
10487 this.installGlobalStyles(); | |
10488 }, | |
10489 /** | |
10490 * Remove all sheets from element and store for later use. | |
10491 */ | |
10492 cacheSheets: function() { | |
10493 this.sheets = this.findNodes(SHEET_SELECTOR); | |
10494 this.sheets.forEach(function(s) { | |
10495 if (s.parentNode) { | |
10496 s.parentNode.removeChild(s); | |
10497 } | |
10498 }); | |
10499 }, | |
10500 cacheStyles: function() { | |
10501 this.styles = this.findNodes(STYLE_SELECTOR + '[' + SCOPE_ATTR + ']'); | |
10502 this.styles.forEach(function(s) { | |
10503 if (s.parentNode) { | |
10504 s.parentNode.removeChild(s); | |
10505 } | |
10506 }); | |
10507 }, | |
10508 /** | |
10509 * Takes external stylesheets loaded in an <element> element and moves | |
10510 * their content into a <style> element inside the <element>'s template. | |
10511 * The sheet is then removed from the <element>. This is done only so | |
10512 * that if the element is loaded in the main document, the sheet does | |
10513 * not become active. | |
10514 * Note, ignores sheets with the attribute 'polymer-scope'. | |
10515 * @param elementElement The <element> element to style. | |
10516 */ | |
10517 installLocalSheets: function () { | |
10518 var sheets = this.sheets.filter(function(s) { | |
10519 return !s.hasAttribute(SCOPE_ATTR); | |
10520 }); | |
10521 var content = this.templateContent(); | |
10522 if (content) { | |
10523 var cssText = ''; | |
10524 sheets.forEach(function(sheet) { | |
10525 cssText += cssTextFromSheet(sheet) + '\n'; | |
10526 }); | |
10527 if (cssText) { | |
10528 var style = createStyleElement(cssText, this.ownerDocument); | |
10529 content.insertBefore(style, content.firstChild); | |
10530 } | |
10531 } | |
10532 }, | |
10533 findNodes: function(selector, matcher) { | |
10534 var nodes = this.querySelectorAll(selector).array(); | |
10535 var content = this.templateContent(); | |
10536 if (content) { | |
10537 var templateNodes = content.querySelectorAll(selector).array(); | |
10538 nodes = nodes.concat(templateNodes); | |
10539 } | |
10540 return matcher ? nodes.filter(matcher) : nodes; | |
10541 }, | |
10542 /** | |
10543 * Promotes external stylesheets and <style> elements with the attribute | |
10544 * polymer-scope='global' into global scope. | |
10545 * This is particularly useful for defining @keyframe rules which | |
10546 * currently do not function in scoped or shadow style elements. | |
10547 * (See wkb.ug/72462) | |
10548 * @param elementElement The <element> element to style. | |
10549 */ | |
10550 // TODO(sorvell): remove when wkb.ug/72462 is addressed. | |
10551 installGlobalStyles: function() { | |
10552 var style = this.styleForScope(STYLE_GLOBAL_SCOPE); | |
10553 applyStyleToScope(style, document.head); | |
10554 }, | |
10555 cssTextForScope: function(scopeDescriptor) { | |
10556 var cssText = ''; | |
10557 // handle stylesheets | |
10558 var selector = '[' + SCOPE_ATTR + '=' + scopeDescriptor + ']'; | |
10559 var matcher = function(s) { | |
10560 return matchesSelector(s, selector); | |
10561 }; | |
10562 var sheets = this.sheets.filter(matcher); | |
10563 sheets.forEach(function(sheet) { | |
10564 cssText += cssTextFromSheet(sheet) + '\n\n'; | |
10565 }); | |
10566 // handle cached style elements | |
10567 var styles = this.styles.filter(matcher); | |
10568 styles.forEach(function(style) { | |
10569 cssText += style.textContent + '\n\n'; | |
10570 }); | |
10571 return cssText; | |
10572 }, | |
10573 styleForScope: function(scopeDescriptor) { | |
10574 var cssText = this.cssTextForScope(scopeDescriptor); | |
10575 return this.cssTextToScopeStyle(cssText, scopeDescriptor); | |
10576 }, | |
10577 cssTextToScopeStyle: function(cssText, scopeDescriptor) { | |
10578 if (cssText) { | |
10579 var style = createStyleElement(cssText); | |
10580 style.setAttribute(STYLE_SCOPE_ATTRIBUTE, this.getAttribute('name') + | |
10581 '-' + scopeDescriptor); | |
10582 return style; | |
10583 } | |
10584 } | |
10585 }; | |
10586 | |
10587 function importRuleForSheet(sheet, baseUrl) { | |
10588 var href = new URL(sheet.getAttribute('href'), baseUrl).href; | |
10589 return '@import \'' + href + '\';'; | |
10590 } | |
10591 | |
10592 function applyStyleToScope(style, scope) { | |
10593 if (style) { | |
10594 if (scope === document) { | |
10595 scope = document.head; | |
10596 } | |
10597 if (hasShadowDOMPolyfill) { | |
10598 scope = document.head; | |
10599 } | |
10600 // TODO(sorvell): necessary for IE | |
10601 // see https://connect.microsoft.com/IE/feedback/details/790212/ | |
10602 // cloning-a-style-element-and-adding-to-document-produces | |
10603 // -unexpected-result#details | |
10604 // var clone = style.cloneNode(true); | |
10605 var clone = createStyleElement(style.textContent); | |
10606 var attr = style.getAttribute(STYLE_SCOPE_ATTRIBUTE); | |
10607 if (attr) { | |
10608 clone.setAttribute(STYLE_SCOPE_ATTRIBUTE, attr); | |
10609 } | |
10610 // TODO(sorvell): probably too brittle; try to figure out | |
10611 // where to put the element. | |
10612 var refNode = scope.firstElementChild; | |
10613 if (scope === document.head) { | |
10614 var selector = 'style[' + STYLE_SCOPE_ATTRIBUTE + ']'; | |
10615 var s$ = document.head.querySelectorAll(selector); | |
10616 if (s$.length) { | |
10617 refNode = s$[s$.length-1].nextElementSibling; | |
10618 } | |
10619 } | |
10620 scope.insertBefore(clone, refNode); | |
10621 } | |
10622 } | |
10623 | |
10624 function createStyleElement(cssText, scope) { | |
10625 scope = scope || document; | |
10626 scope = scope.createElement ? scope : scope.ownerDocument; | |
10627 var style = scope.createElement('style'); | |
10628 style.textContent = cssText; | |
10629 return style; | |
10630 } | |
10631 | |
10632 function cssTextFromSheet(sheet) { | |
10633 return (sheet && sheet.__resource) || ''; | |
10634 } | |
10635 | |
10636 function matchesSelector(node, inSelector) { | |
10637 if (matches) { | |
10638 return matches.call(node, inSelector); | |
10639 } | |
10640 } | |
10641 var p = HTMLElement.prototype; | |
10642 var matches = p.matches || p.matchesSelector || p.webkitMatchesSelector | |
10643 || p.mozMatchesSelector; | |
10644 | |
10645 // exports | |
10646 | |
10647 scope.api.declaration.styles = styles; | |
10648 scope.applyStyleToScope = applyStyleToScope; | |
10649 | |
10650 })(Polymer); | |
10651 | |
10652 (function(scope) { | |
10653 | |
10654 // imports | |
10655 | |
10656 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
10657 var api = scope.api.instance.events; | |
10658 var EVENT_PREFIX = api.EVENT_PREFIX; | |
10659 | |
10660 var mixedCaseEventTypes = {}; | |
10661 [ | |
10662 'webkitAnimationStart', | |
10663 'webkitAnimationEnd', | |
10664 'webkitTransitionEnd', | |
10665 'DOMFocusOut', | |
10666 'DOMFocusIn', | |
10667 'DOMMouseScroll' | |
10668 ].forEach(function(e) { | |
10669 mixedCaseEventTypes[e.toLowerCase()] = e; | |
10670 }); | |
10671 | |
10672 // polymer-element declarative api: events feature | |
10673 var events = { | |
10674 parseHostEvents: function() { | |
10675 // our delegates map | |
10676 var delegates = this.prototype.eventDelegates; | |
10677 // extract data from attributes into delegates | |
10678 this.addAttributeDelegates(delegates); | |
10679 }, | |
10680 addAttributeDelegates: function(delegates) { | |
10681 // for each attribute | |
10682 for (var i=0, a; a=this.attributes[i]; i++) { | |
10683 // does it have magic marker identifying it as an event delegate? | |
10684 if (this.hasEventPrefix(a.name)) { | |
10685 // if so, add the info to delegates | |
10686 delegates[this.removeEventPrefix(a.name)] = a.value.replace('{{', '') | |
10687 .replace('}}', '').trim(); | |
10688 } | |
10689 } | |
10690 }, | |
10691 // starts with 'on-' | |
10692 hasEventPrefix: function (n) { | |
10693 return n && (n[0] === 'o') && (n[1] === 'n') && (n[2] === '-'); | |
10694 }, | |
10695 removeEventPrefix: function(n) { | |
10696 return n.slice(prefixLength); | |
10697 }, | |
10698 findController: function(node) { | |
10699 while (node.parentNode) { | |
10700 if (node.eventController) { | |
10701 return node.eventController; | |
10702 } | |
10703 node = node.parentNode; | |
10704 } | |
10705 return node.host; | |
10706 }, | |
10707 getEventHandler: function(controller, target, method) { | |
10708 var events = this; | |
10709 return function(e) { | |
10710 if (!controller || !controller.PolymerBase) { | |
10711 controller = events.findController(target); | |
10712 } | |
10713 | |
10714 var args = [e, e.detail, e.currentTarget]; | |
10715 controller.dispatchMethod(controller, method, args); | |
10716 }; | |
10717 }, | |
10718 prepareEventBinding: function(pathString, name, node) { | |
10719 if (!this.hasEventPrefix(name)) | |
10720 return; | |
10721 | |
10722 var eventType = this.removeEventPrefix(name); | |
10723 eventType = mixedCaseEventTypes[eventType] || eventType; | |
10724 | |
10725 var events = this; | |
10726 | |
10727 return function(model, node, oneTime) { | |
10728 var handler = events.getEventHandler(undefined, node, pathString); | |
10729 PolymerGestures.addEventListener(node, eventType, handler); | |
10730 | |
10731 if (oneTime) | |
10732 return; | |
10733 | |
10734 // TODO(rafaelw): This is really pointless work. Aside from the cost | |
10735 // of these allocations, NodeBind is going to setAttribute back to its | |
10736 // current value. Fixing this would mean changing the TemplateBinding | |
10737 // binding delegate API. | |
10738 function bindingValue() { | |
10739 return '{{ ' + pathString + ' }}'; | |
10740 } | |
10741 | |
10742 return { | |
10743 open: bindingValue, | |
10744 discardChanges: bindingValue, | |
10745 close: function() { | |
10746 PolymerGestures.removeEventListener(node, eventType, handler); | |
10747 } | |
10748 }; | |
10749 }; | |
10750 } | |
10751 }; | |
10752 | |
10753 var prefixLength = EVENT_PREFIX.length; | |
10754 | |
10755 // exports | |
10756 scope.api.declaration.events = events; | |
10757 | |
10758 })(Polymer); | |
10759 | |
10760 (function(scope) { | |
10761 | |
10762 // element api | |
10763 | |
10764 var observationBlacklist = ['attribute']; | |
10765 | |
10766 var properties = { | |
10767 inferObservers: function(prototype) { | |
10768 // called before prototype.observe is chained to inherited object | |
10769 var observe = prototype.observe, property; | |
10770 for (var n in prototype) { | |
10771 if (n.slice(-7) === 'Changed') { | |
10772 property = n.slice(0, -7); | |
10773 if (this.canObserveProperty(property)) { | |
10774 if (!observe) { | |
10775 observe = (prototype.observe = {}); | |
10776 } | |
10777 observe[property] = observe[property] || n; | |
10778 } | |
10779 } | |
10780 } | |
10781 }, | |
10782 canObserveProperty: function(property) { | |
10783 return (observationBlacklist.indexOf(property) < 0); | |
10784 }, | |
10785 explodeObservers: function(prototype) { | |
10786 // called before prototype.observe is chained to inherited object | |
10787 var o = prototype.observe; | |
10788 if (o) { | |
10789 var exploded = {}; | |
10790 for (var n in o) { | |
10791 var names = n.split(' '); | |
10792 for (var i=0, ni; ni=names[i]; i++) { | |
10793 exploded[ni] = o[n]; | |
10794 } | |
10795 } | |
10796 prototype.observe = exploded; | |
10797 } | |
10798 }, | |
10799 optimizePropertyMaps: function(prototype) { | |
10800 if (prototype.observe) { | |
10801 // construct name list | |
10802 var a = prototype._observeNames = []; | |
10803 for (var n in prototype.observe) { | |
10804 var names = n.split(' '); | |
10805 for (var i=0, ni; ni=names[i]; i++) { | |
10806 a.push(ni); | |
10807 } | |
10808 } | |
10809 } | |
10810 if (prototype.publish) { | |
10811 // construct name list | |
10812 var a = prototype._publishNames = []; | |
10813 for (var n in prototype.publish) { | |
10814 a.push(n); | |
10815 } | |
10816 } | |
10817 if (prototype.computed) { | |
10818 // construct name list | |
10819 var a = prototype._computedNames = []; | |
10820 for (var n in prototype.computed) { | |
10821 a.push(n); | |
10822 } | |
10823 } | |
10824 }, | |
10825 publishProperties: function(prototype, base) { | |
10826 // if we have any properties to publish | |
10827 var publish = prototype.publish; | |
10828 if (publish) { | |
10829 // transcribe `publish` entries onto own prototype | |
10830 this.requireProperties(publish, prototype, base); | |
10831 // warn and remove accessor names that are broken on some browsers | |
10832 this.filterInvalidAccessorNames(publish); | |
10833 // construct map of lower-cased property names | |
10834 prototype._publishLC = this.lowerCaseMap(publish); | |
10835 } | |
10836 var computed = prototype.computed; | |
10837 if (computed) { | |
10838 // warn and remove accessor names that are broken on some browsers | |
10839 this.filterInvalidAccessorNames(computed); | |
10840 } | |
10841 }, | |
10842 // Publishing/computing a property where the name might conflict with a | |
10843 // browser property is not currently supported to help users of Polymer | |
10844 // avoid browser bugs: | |
10845 // | |
10846 // https://code.google.com/p/chromium/issues/detail?id=43394 | |
10847 // https://bugs.webkit.org/show_bug.cgi?id=49739 | |
10848 // | |
10849 // We can lift this restriction when those bugs are fixed. | |
10850 filterInvalidAccessorNames: function(propertyNames) { | |
10851 for (var name in propertyNames) { | |
10852 // Check if the name is in our blacklist. | |
10853 if (this.propertyNameBlacklist[name]) { | |
10854 console.warn('Cannot define property "' + name + '" for element "' + | |
10855 this.name + '" because it has the same name as an HTMLElement ' + | |
10856 'property, and not all browsers support overriding that. ' + | |
10857 'Consider giving it a different name.'); | |
10858 // Remove the invalid accessor from the list. | |
10859 delete propertyNames[name]; | |
10860 } | |
10861 } | |
10862 }, | |
10863 // | |
10864 // `name: value` entries in the `publish` object may need to generate | |
10865 // matching properties on the prototype. | |
10866 // | |
10867 // Values that are objects may have a `reflect` property, which | |
10868 // signals that the value describes property control metadata. | |
10869 // In metadata objects, the prototype default value (if any) | |
10870 // is encoded in the `value` property. | |
10871 // | |
10872 // publish: { | |
10873 // foo: 5, | |
10874 // bar: {value: true, reflect: true}, | |
10875 // zot: {} | |
10876 // } | |
10877 // | |
10878 // `reflect` metadata property controls whether changes to the property | |
10879 // are reflected back to the attribute (default false). | |
10880 // | |
10881 // A value is stored on the prototype unless it's === `undefined`, | |
10882 // in which case the base chain is checked for a value. | |
10883 // If the basal value is also undefined, `null` is stored on the prototype. | |
10884 // | |
10885 // The reflection data is stored on another prototype object, `reflect` | |
10886 // which also can be specified directly. | |
10887 // | |
10888 // reflect: { | |
10889 // foo: true | |
10890 // } | |
10891 // | |
10892 requireProperties: function(propertyInfos, prototype, base) { | |
10893 // per-prototype storage for reflected properties | |
10894 prototype.reflect = prototype.reflect || {}; | |
10895 // ensure a prototype value for each property | |
10896 // and update the property's reflect to attribute status | |
10897 for (var n in propertyInfos) { | |
10898 var value = propertyInfos[n]; | |
10899 // value has metadata if it has a `reflect` property | |
10900 if (value && value.reflect !== undefined) { | |
10901 prototype.reflect[n] = Boolean(value.reflect); | |
10902 value = value.value; | |
10903 } | |
10904 // only set a value if one is specified | |
10905 if (value !== undefined) { | |
10906 prototype[n] = value; | |
10907 } | |
10908 } | |
10909 }, | |
10910 lowerCaseMap: function(properties) { | |
10911 var map = {}; | |
10912 for (var n in properties) { | |
10913 map[n.toLowerCase()] = n; | |
10914 } | |
10915 return map; | |
10916 }, | |
10917 createPropertyAccessor: function(name, ignoreWrites) { | |
10918 var proto = this.prototype; | |
10919 | |
10920 var privateName = name + '_'; | |
10921 var privateObservable = name + 'Observable_'; | |
10922 proto[privateName] = proto[name]; | |
10923 | |
10924 Object.defineProperty(proto, name, { | |
10925 get: function() { | |
10926 var observable = this[privateObservable]; | |
10927 if (observable) | |
10928 observable.deliver(); | |
10929 | |
10930 return this[privateName]; | |
10931 }, | |
10932 set: function(value) { | |
10933 if (ignoreWrites) { | |
10934 return this[privateName]; | |
10935 } | |
10936 | |
10937 var observable = this[privateObservable]; | |
10938 if (observable) { | |
10939 observable.setValue(value); | |
10940 return; | |
10941 } | |
10942 | |
10943 var oldValue = this[privateName]; | |
10944 this[privateName] = value; | |
10945 this.emitPropertyChangeRecord(name, value, oldValue); | |
10946 | |
10947 return value; | |
10948 }, | |
10949 configurable: true | |
10950 }); | |
10951 }, | |
10952 createPropertyAccessors: function(prototype) { | |
10953 var n$ = prototype._computedNames; | |
10954 if (n$ && n$.length) { | |
10955 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { | |
10956 this.createPropertyAccessor(n, true); | |
10957 } | |
10958 } | |
10959 var n$ = prototype._publishNames; | |
10960 if (n$ && n$.length) { | |
10961 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { | |
10962 // If the property is computed and published, the accessor is created | |
10963 // above. | |
10964 if (!prototype.computed || !prototype.computed[n]) { | |
10965 this.createPropertyAccessor(n); | |
10966 } | |
10967 } | |
10968 } | |
10969 }, | |
10970 // This list contains some property names that people commonly want to use, | |
10971 // but won't work because of Chrome/Safari bugs. It isn't an exhaustive | |
10972 // list. In particular it doesn't contain any property names found on | |
10973 // subtypes of HTMLElement (e.g. name, value). Rather it attempts to catch | |
10974 // some common cases. | |
10975 propertyNameBlacklist: { | |
10976 children: 1, | |
10977 'class': 1, | |
10978 id: 1, | |
10979 hidden: 1, | |
10980 style: 1, | |
10981 title: 1, | |
10982 } | |
10983 }; | |
10984 | |
10985 // exports | |
10986 | |
10987 scope.api.declaration.properties = properties; | |
10988 | |
10989 })(Polymer); | |
10990 | |
10991 (function(scope) { | |
10992 | |
10993 // magic words | |
10994 | |
10995 var ATTRIBUTES_ATTRIBUTE = 'attributes'; | |
10996 var ATTRIBUTES_REGEX = /\s|,/; | |
10997 | |
10998 // attributes api | |
10999 | |
11000 var attributes = { | |
11001 | |
11002 inheritAttributesObjects: function(prototype) { | |
11003 // chain our lower-cased publish map to the inherited version | |
11004 this.inheritObject(prototype, 'publishLC'); | |
11005 // chain our instance attributes map to the inherited version | |
11006 this.inheritObject(prototype, '_instanceAttributes'); | |
11007 }, | |
11008 | |
11009 publishAttributes: function(prototype, base) { | |
11010 // merge names from 'attributes' attribute into the 'publish' object | |
11011 var attributes = this.getAttribute(ATTRIBUTES_ATTRIBUTE); | |
11012 if (attributes) { | |
11013 // create a `publish` object if needed. | |
11014 // the `publish` object is only relevant to this prototype, the | |
11015 // publishing logic in `declaration/properties.js` is responsible for | |
11016 // managing property values on the prototype chain. | |
11017 // TODO(sjmiles): the `publish` object is later chained to it's | |
11018 // ancestor object, presumably this is only for | |
11019 // reflection or other non-library uses. | |
11020 var publish = prototype.publish || (prototype.publish = {}); | |
11021 // names='a b c' or names='a,b,c' | |
11022 var names = attributes.split(ATTRIBUTES_REGEX); | |
11023 // record each name for publishing | |
11024 for (var i=0, l=names.length, n; i<l; i++) { | |
11025 // remove excess ws | |
11026 n = names[i].trim(); | |
11027 // looks weird, but causes n to exist on `publish` if it does not; | |
11028 // a more careful test would need expensive `in` operator | |
11029 if (n && publish[n] === undefined) { | |
11030 publish[n] = undefined; | |
11031 } | |
11032 } | |
11033 } | |
11034 }, | |
11035 | |
11036 // record clonable attributes from <element> | |
11037 accumulateInstanceAttributes: function() { | |
11038 // inherit instance attributes | |
11039 var clonable = this.prototype._instanceAttributes; | |
11040 // merge attributes from element | |
11041 var a$ = this.attributes; | |
11042 for (var i=0, l=a$.length, a; (i<l) && (a=a$[i]); i++) { | |
11043 if (this.isInstanceAttribute(a.name)) { | |
11044 clonable[a.name] = a.value; | |
11045 } | |
11046 } | |
11047 }, | |
11048 | |
11049 isInstanceAttribute: function(name) { | |
11050 return !this.blackList[name] && name.slice(0,3) !== 'on-'; | |
11051 }, | |
11052 | |
11053 // do not clone these attributes onto instances | |
11054 blackList: { | |
11055 name: 1, | |
11056 'extends': 1, | |
11057 constructor: 1, | |
11058 noscript: 1, | |
11059 assetpath: 1, | |
11060 'cache-csstext': 1 | |
11061 } | |
11062 | |
11063 }; | |
11064 | |
11065 // add ATTRIBUTES_ATTRIBUTE to the blacklist | |
11066 attributes.blackList[ATTRIBUTES_ATTRIBUTE] = 1; | |
11067 | |
11068 // exports | |
11069 | |
11070 scope.api.declaration.attributes = attributes; | |
11071 | |
11072 })(Polymer); | |
11073 | |
11074 (function(scope) { | |
11075 | |
11076 // imports | |
11077 var events = scope.api.declaration.events; | |
11078 | |
11079 var syntax = new PolymerExpressions(); | |
11080 var prepareBinding = syntax.prepareBinding; | |
11081 | |
11082 // Polymer takes a first crack at the binding to see if it's a declarative | |
11083 // event handler. | |
11084 syntax.prepareBinding = function(pathString, name, node) { | |
11085 return events.prepareEventBinding(pathString, name, node) || | |
11086 prepareBinding.call(syntax, pathString, name, node); | |
11087 }; | |
11088 | |
11089 // declaration api supporting mdv | |
11090 var mdv = { | |
11091 syntax: syntax, | |
11092 fetchTemplate: function() { | |
11093 return this.querySelector('template'); | |
11094 }, | |
11095 templateContent: function() { | |
11096 var template = this.fetchTemplate(); | |
11097 return template && template.content; | |
11098 }, | |
11099 installBindingDelegate: function(template) { | |
11100 if (template) { | |
11101 template.bindingDelegate = this.syntax; | |
11102 } | |
11103 } | |
11104 }; | |
11105 | |
11106 // exports | |
11107 scope.api.declaration.mdv = mdv; | |
11108 | |
11109 })(Polymer); | |
11110 | |
11111 (function(scope) { | |
11112 | |
11113 // imports | |
11114 | |
11115 var api = scope.api; | |
11116 var isBase = scope.isBase; | |
11117 var extend = scope.extend; | |
11118 | |
11119 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; | |
11120 | |
11121 // prototype api | |
11122 | |
11123 var prototype = { | |
11124 | |
11125 register: function(name, extendeeName) { | |
11126 // build prototype combining extendee, Polymer base, and named api | |
11127 this.buildPrototype(name, extendeeName); | |
11128 // register our custom element with the platform | |
11129 this.registerPrototype(name, extendeeName); | |
11130 // reference constructor in a global named by 'constructor' attribute | |
11131 this.publishConstructor(); | |
11132 }, | |
11133 | |
11134 buildPrototype: function(name, extendeeName) { | |
11135 // get our custom prototype (before chaining) | |
11136 var extension = scope.getRegisteredPrototype(name); | |
11137 // get basal prototype | |
11138 var base = this.generateBasePrototype(extendeeName); | |
11139 // implement declarative features | |
11140 this.desugarBeforeChaining(extension, base); | |
11141 // join prototypes | |
11142 this.prototype = this.chainPrototypes(extension, base); | |
11143 // more declarative features | |
11144 this.desugarAfterChaining(name, extendeeName); | |
11145 }, | |
11146 | |
11147 desugarBeforeChaining: function(prototype, base) { | |
11148 // back reference declaration element | |
11149 // TODO(sjmiles): replace `element` with `elementElement` or `declaration` | |
11150 prototype.element = this; | |
11151 // transcribe `attributes` declarations onto own prototype's `publish` | |
11152 this.publishAttributes(prototype, base); | |
11153 // `publish` properties to the prototype and to attribute watch | |
11154 this.publishProperties(prototype, base); | |
11155 // infer observers for `observe` list based on method names | |
11156 this.inferObservers(prototype); | |
11157 // desugar compound observer syntax, e.g. 'a b c' | |
11158 this.explodeObservers(prototype); | |
11159 }, | |
11160 | |
11161 chainPrototypes: function(prototype, base) { | |
11162 // chain various meta-data objects to inherited versions | |
11163 this.inheritMetaData(prototype, base); | |
11164 // chain custom api to inherited | |
11165 var chained = this.chainObject(prototype, base); | |
11166 // x-platform fixup | |
11167 ensurePrototypeTraversal(chained); | |
11168 return chained; | |
11169 }, | |
11170 | |
11171 inheritMetaData: function(prototype, base) { | |
11172 // chain observe object to inherited | |
11173 this.inheritObject('observe', prototype, base); | |
11174 // chain publish object to inherited | |
11175 this.inheritObject('publish', prototype, base); | |
11176 // chain reflect object to inherited | |
11177 this.inheritObject('reflect', prototype, base); | |
11178 // chain our lower-cased publish map to the inherited version | |
11179 this.inheritObject('_publishLC', prototype, base); | |
11180 // chain our instance attributes map to the inherited version | |
11181 this.inheritObject('_instanceAttributes', prototype, base); | |
11182 // chain our event delegates map to the inherited version | |
11183 this.inheritObject('eventDelegates', prototype, base); | |
11184 }, | |
11185 | |
11186 // implement various declarative features | |
11187 desugarAfterChaining: function(name, extendee) { | |
11188 // build side-chained lists to optimize iterations | |
11189 this.optimizePropertyMaps(this.prototype); | |
11190 this.createPropertyAccessors(this.prototype); | |
11191 // install mdv delegate on template | |
11192 this.installBindingDelegate(this.fetchTemplate()); | |
11193 // install external stylesheets as if they are inline | |
11194 this.installSheets(); | |
11195 // adjust any paths in dom from imports | |
11196 this.resolveElementPaths(this); | |
11197 // compile list of attributes to copy to instances | |
11198 this.accumulateInstanceAttributes(); | |
11199 // parse on-* delegates declared on `this` element | |
11200 this.parseHostEvents(); | |
11201 // | |
11202 // install a helper method this.resolvePath to aid in | |
11203 // setting resource urls. e.g. | |
11204 // this.$.image.src = this.resolvePath('images/foo.png') | |
11205 this.addResolvePathApi(); | |
11206 // under ShadowDOMPolyfill, transforms to approximate missing CSS features | |
11207 if (hasShadowDOMPolyfill) { | |
11208 WebComponents.ShadowCSS.shimStyling(this.templateContent(), name, | |
11209 extendee); | |
11210 } | |
11211 // allow custom element access to the declarative context | |
11212 if (this.prototype.registerCallback) { | |
11213 this.prototype.registerCallback(this); | |
11214 } | |
11215 }, | |
11216 | |
11217 // if a named constructor is requested in element, map a reference | |
11218 // to the constructor to the given symbol | |
11219 publishConstructor: function() { | |
11220 var symbol = this.getAttribute('constructor'); | |
11221 if (symbol) { | |
11222 window[symbol] = this.ctor; | |
11223 } | |
11224 }, | |
11225 | |
11226 // build prototype combining extendee, Polymer base, and named api | |
11227 generateBasePrototype: function(extnds) { | |
11228 var prototype = this.findBasePrototype(extnds); | |
11229 if (!prototype) { | |
11230 // create a prototype based on tag-name extension | |
11231 var prototype = HTMLElement.getPrototypeForTag(extnds); | |
11232 // insert base api in inheritance chain (if needed) | |
11233 prototype = this.ensureBaseApi(prototype); | |
11234 // memoize this base | |
11235 memoizedBases[extnds] = prototype; | |
11236 } | |
11237 return prototype; | |
11238 }, | |
11239 | |
11240 findBasePrototype: function(name) { | |
11241 return memoizedBases[name]; | |
11242 }, | |
11243 | |
11244 // install Polymer instance api into prototype chain, as needed | |
11245 ensureBaseApi: function(prototype) { | |
11246 if (prototype.PolymerBase) { | |
11247 return prototype; | |
11248 } | |
11249 var extended = Object.create(prototype); | |
11250 // we need a unique copy of base api for each base prototype | |
11251 // therefore we 'extend' here instead of simply chaining | |
11252 api.publish(api.instance, extended); | |
11253 // TODO(sjmiles): sharing methods across prototype chains is | |
11254 // not supported by 'super' implementation which optimizes | |
11255 // by memoizing prototype relationships. | |
11256 // Probably we should have a version of 'extend' that is | |
11257 // share-aware: it could study the text of each function, | |
11258 // look for usage of 'super', and wrap those functions in | |
11259 // closures. | |
11260 // As of now, there is only one problematic method, so | |
11261 // we just patch it manually. | |
11262 // To avoid re-entrancy problems, the special super method | |
11263 // installed is called `mixinSuper` and the mixin method | |
11264 // must use this method instead of the default `super`. | |
11265 this.mixinMethod(extended, prototype, api.instance.mdv, 'bind'); | |
11266 // return buffed-up prototype | |
11267 return extended; | |
11268 }, | |
11269 | |
11270 mixinMethod: function(extended, prototype, api, name) { | |
11271 var $super = function(args) { | |
11272 return prototype[name].apply(this, args); | |
11273 }; | |
11274 extended[name] = function() { | |
11275 this.mixinSuper = $super; | |
11276 return api[name].apply(this, arguments); | |
11277 } | |
11278 }, | |
11279 | |
11280 // ensure prototype[name] inherits from a prototype.prototype[name] | |
11281 inheritObject: function(name, prototype, base) { | |
11282 // require an object | |
11283 var source = prototype[name] || {}; | |
11284 // chain inherited properties onto a new object | |
11285 prototype[name] = this.chainObject(source, base[name]); | |
11286 }, | |
11287 | |
11288 // register 'prototype' to custom element 'name', store constructor | |
11289 registerPrototype: function(name, extendee) { | |
11290 var info = { | |
11291 prototype: this.prototype | |
11292 } | |
11293 // native element must be specified in extends | |
11294 var typeExtension = this.findTypeExtension(extendee); | |
11295 if (typeExtension) { | |
11296 info.extends = typeExtension; | |
11297 } | |
11298 // register the prototype with HTMLElement for name lookup | |
11299 HTMLElement.register(name, this.prototype); | |
11300 // register the custom type | |
11301 this.ctor = document.registerElement(name, info); | |
11302 }, | |
11303 | |
11304 findTypeExtension: function(name) { | |
11305 if (name && name.indexOf('-') < 0) { | |
11306 return name; | |
11307 } else { | |
11308 var p = this.findBasePrototype(name); | |
11309 if (p.element) { | |
11310 return this.findTypeExtension(p.element.extends); | |
11311 } | |
11312 } | |
11313 } | |
11314 | |
11315 }; | |
11316 | |
11317 // memoize base prototypes | |
11318 var memoizedBases = {}; | |
11319 | |
11320 // implementation of 'chainObject' depends on support for __proto__ | |
11321 if (Object.__proto__) { | |
11322 prototype.chainObject = function(object, inherited) { | |
11323 if (object && inherited && object !== inherited) { | |
11324 object.__proto__ = inherited; | |
11325 } | |
11326 return object; | |
11327 } | |
11328 } else { | |
11329 prototype.chainObject = function(object, inherited) { | |
11330 if (object && inherited && object !== inherited) { | |
11331 var chained = Object.create(inherited); | |
11332 object = extend(chained, object); | |
11333 } | |
11334 return object; | |
11335 } | |
11336 } | |
11337 | |
11338 // On platforms that do not support __proto__ (versions of IE), the prototype | |
11339 // chain of a custom element is simulated via installation of __proto__. | |
11340 // Although custom elements manages this, we install it here so it's | |
11341 // available during desugaring. | |
11342 function ensurePrototypeTraversal(prototype) { | |
11343 if (!Object.__proto__) { | |
11344 var ancestor = Object.getPrototypeOf(prototype); | |
11345 prototype.__proto__ = ancestor; | |
11346 if (isBase(ancestor)) { | |
11347 ancestor.__proto__ = Object.getPrototypeOf(ancestor); | |
11348 } | |
11349 } | |
11350 } | |
11351 | |
11352 // exports | |
11353 | |
11354 api.declaration.prototype = prototype; | |
11355 | |
11356 })(Polymer); | |
11357 | |
11358 (function(scope) { | |
11359 | |
11360 /* | |
11361 | |
11362 Elements are added to a registration queue so that they register in | |
11363 the proper order at the appropriate time. We do this for a few reasons: | |
11364 | |
11365 * to enable elements to load resources (like stylesheets) | |
11366 asynchronously. We need to do this until the platform provides an efficient | |
11367 alternative. One issue is that remote @import stylesheets are | |
11368 re-fetched whenever stamped into a shadowRoot. | |
11369 | |
11370 * to ensure elements loaded 'at the same time' (e.g. via some set of | |
11371 imports) are registered as a batch. This allows elements to be enured from | |
11372 upgrade ordering as long as they query the dom tree 1 task after | |
11373 upgrade (aka domReady). This is a performance tradeoff. On the one hand, | |
11374 elements that could register while imports are loading are prevented from | |
11375 doing so. On the other, grouping upgrades into a single task means less | |
11376 incremental work (for example style recalcs), Also, we can ensure the | |
11377 document is in a known state at the single quantum of time when | |
11378 elements upgrade. | |
11379 | |
11380 */ | |
11381 var queue = { | |
11382 | |
11383 // tell the queue to wait for an element to be ready | |
11384 wait: function(element) { | |
11385 if (!element.__queue) { | |
11386 element.__queue = {}; | |
11387 elements.push(element); | |
11388 } | |
11389 }, | |
11390 | |
11391 // enqueue an element to the next spot in the queue. | |
11392 enqueue: function(element, check, go) { | |
11393 var shouldAdd = element.__queue && !element.__queue.check; | |
11394 if (shouldAdd) { | |
11395 queueForElement(element).push(element); | |
11396 element.__queue.check = check; | |
11397 element.__queue.go = go; | |
11398 } | |
11399 return (this.indexOf(element) !== 0); | |
11400 }, | |
11401 | |
11402 indexOf: function(element) { | |
11403 var i = queueForElement(element).indexOf(element); | |
11404 if (i >= 0 && document.contains(element)) { | |
11405 i += (HTMLImports.useNative || HTMLImports.ready) ? | |
11406 importQueue.length : 1e9; | |
11407 } | |
11408 return i; | |
11409 }, | |
11410 | |
11411 // tell the queue an element is ready to be registered | |
11412 go: function(element) { | |
11413 var readied = this.remove(element); | |
11414 if (readied) { | |
11415 element.__queue.flushable = true; | |
11416 this.addToFlushQueue(readied); | |
11417 this.check(); | |
11418 } | |
11419 }, | |
11420 | |
11421 remove: function(element) { | |
11422 var i = this.indexOf(element); | |
11423 if (i !== 0) { | |
11424 //console.warn('queue order wrong', i); | |
11425 return; | |
11426 } | |
11427 return queueForElement(element).shift(); | |
11428 }, | |
11429 | |
11430 check: function() { | |
11431 // next | |
11432 var element = this.nextElement(); | |
11433 if (element) { | |
11434 element.__queue.check.call(element); | |
11435 } | |
11436 if (this.canReady()) { | |
11437 this.ready(); | |
11438 return true; | |
11439 } | |
11440 }, | |
11441 | |
11442 nextElement: function() { | |
11443 return nextQueued(); | |
11444 }, | |
11445 | |
11446 canReady: function() { | |
11447 return !this.waitToReady && this.isEmpty(); | |
11448 }, | |
11449 | |
11450 isEmpty: function() { | |
11451 for (var i=0, l=elements.length, e; (i<l) && | |
11452 (e=elements[i]); i++) { | |
11453 if (e.__queue && !e.__queue.flushable) { | |
11454 return; | |
11455 } | |
11456 } | |
11457 return true; | |
11458 }, | |
11459 | |
11460 addToFlushQueue: function(element) { | |
11461 flushQueue.push(element); | |
11462 }, | |
11463 | |
11464 flush: function() { | |
11465 // prevent re-entrance | |
11466 if (this.flushing) { | |
11467 return; | |
11468 } | |
11469 this.flushing = true; | |
11470 var element; | |
11471 while (flushQueue.length) { | |
11472 element = flushQueue.shift(); | |
11473 element.__queue.go.call(element); | |
11474 element.__queue = null; | |
11475 } | |
11476 this.flushing = false; | |
11477 }, | |
11478 | |
11479 ready: function() { | |
11480 // TODO(sorvell): As an optimization, turn off CE polyfill upgrading | |
11481 // while registering. This way we avoid having to upgrade each document | |
11482 // piecemeal per registration and can instead register all elements | |
11483 // and upgrade once in a batch. Without this optimization, upgrade time | |
11484 // degrades significantly when SD polyfill is used. This is mainly because | |
11485 // querying the document tree for elements is slow under the SD polyfill. | |
11486 var polyfillWasReady = CustomElements.ready; | |
11487 CustomElements.ready = false; | |
11488 this.flush(); | |
11489 if (!CustomElements.useNative) { | |
11490 CustomElements.upgradeDocumentTree(document); | |
11491 } | |
11492 CustomElements.ready = polyfillWasReady; | |
11493 Polymer.flush(); | |
11494 requestAnimationFrame(this.flushReadyCallbacks); | |
11495 }, | |
11496 | |
11497 addReadyCallback: function(callback) { | |
11498 if (callback) { | |
11499 readyCallbacks.push(callback); | |
11500 } | |
11501 }, | |
11502 | |
11503 flushReadyCallbacks: function() { | |
11504 if (readyCallbacks) { | |
11505 var fn; | |
11506 while (readyCallbacks.length) { | |
11507 fn = readyCallbacks.shift(); | |
11508 fn(); | |
11509 } | |
11510 } | |
11511 }, | |
11512 | |
11513 /** | |
11514 Returns a list of elements that have had polymer-elements created but | |
11515 are not yet ready to register. The list is an array of element definitions. | |
11516 */ | |
11517 waitingFor: function() { | |
11518 var e$ = []; | |
11519 for (var i=0, l=elements.length, e; (i<l) && | |
11520 (e=elements[i]); i++) { | |
11521 if (e.__queue && !e.__queue.flushable) { | |
11522 e$.push(e); | |
11523 } | |
11524 } | |
11525 return e$; | |
11526 }, | |
11527 | |
11528 waitToReady: true | |
11529 | |
11530 }; | |
11531 | |
11532 var elements = []; | |
11533 var flushQueue = []; | |
11534 var importQueue = []; | |
11535 var mainQueue = []; | |
11536 var readyCallbacks = []; | |
11537 | |
11538 function queueForElement(element) { | |
11539 return document.contains(element) ? mainQueue : importQueue; | |
11540 } | |
11541 | |
11542 function nextQueued() { | |
11543 return importQueue.length ? importQueue[0] : mainQueue[0]; | |
11544 } | |
11545 | |
11546 function whenReady(callback) { | |
11547 queue.waitToReady = true; | |
11548 Polymer.endOfMicrotask(function() { | |
11549 HTMLImports.whenReady(function() { | |
11550 queue.addReadyCallback(callback); | |
11551 queue.waitToReady = false; | |
11552 queue.check(); | |
11553 }); | |
11554 }); | |
11555 } | |
11556 | |
11557 /** | |
11558 Forces polymer to register any pending elements. Can be used to abort | |
11559 waiting for elements that are partially defined. | |
11560 @param timeout {Integer} Optional timeout in milliseconds | |
11561 */ | |
11562 function forceReady(timeout) { | |
11563 if (timeout === undefined) { | |
11564 queue.ready(); | |
11565 return; | |
11566 } | |
11567 var handle = setTimeout(function() { | |
11568 queue.ready(); | |
11569 }, timeout); | |
11570 Polymer.whenReady(function() { | |
11571 clearTimeout(handle); | |
11572 }); | |
11573 } | |
11574 | |
11575 // exports | |
11576 scope.elements = elements; | |
11577 scope.waitingFor = queue.waitingFor.bind(queue); | |
11578 scope.forceReady = forceReady; | |
11579 scope.queue = queue; | |
11580 scope.whenReady = scope.whenPolymerReady = whenReady; | |
11581 })(Polymer); | |
11582 | |
11583 (function(scope) { | |
11584 | |
11585 // imports | |
11586 | |
11587 var extend = scope.extend; | |
11588 var api = scope.api; | |
11589 var queue = scope.queue; | |
11590 var whenReady = scope.whenReady; | |
11591 var getRegisteredPrototype = scope.getRegisteredPrototype; | |
11592 var waitingForPrototype = scope.waitingForPrototype; | |
11593 | |
11594 // declarative implementation: <polymer-element> | |
11595 | |
11596 var prototype = extend(Object.create(HTMLElement.prototype), { | |
11597 | |
11598 createdCallback: function() { | |
11599 if (this.getAttribute('name')) { | |
11600 this.init(); | |
11601 } | |
11602 }, | |
11603 | |
11604 init: function() { | |
11605 // fetch declared values | |
11606 this.name = this.getAttribute('name'); | |
11607 this.extends = this.getAttribute('extends'); | |
11608 queue.wait(this); | |
11609 // initiate any async resource fetches | |
11610 this.loadResources(); | |
11611 // register when all constraints are met | |
11612 this.registerWhenReady(); | |
11613 }, | |
11614 | |
11615 // TODO(sorvell): we currently queue in the order the prototypes are | |
11616 // registered, but we should queue in the order that polymer-elements | |
11617 // are registered. We are currently blocked from doing this based on | |
11618 // crbug.com/395686. | |
11619 registerWhenReady: function() { | |
11620 if (this.registered | |
11621 || this.waitingForPrototype(this.name) | |
11622 || this.waitingForQueue() | |
11623 || this.waitingForResources()) { | |
11624 return; | |
11625 } | |
11626 queue.go(this); | |
11627 }, | |
11628 | |
11629 _register: function() { | |
11630 //console.log('registering', this.name); | |
11631 // warn if extending from a custom element not registered via Polymer | |
11632 if (isCustomTag(this.extends) && !isRegistered(this.extends)) { | |
11633 console.warn('%s is attempting to extend %s, an unregistered element ' + | |
11634 'or one that was not registered with Polymer.', this.name, | |
11635 this.extends); | |
11636 } | |
11637 this.register(this.name, this.extends); | |
11638 this.registered = true; | |
11639 }, | |
11640 | |
11641 waitingForPrototype: function(name) { | |
11642 if (!getRegisteredPrototype(name)) { | |
11643 // then wait for a prototype | |
11644 waitingForPrototype(name, this); | |
11645 // emulate script if user is not supplying one | |
11646 this.handleNoScript(name); | |
11647 // prototype not ready yet | |
11648 return true; | |
11649 } | |
11650 }, | |
11651 | |
11652 handleNoScript: function(name) { | |
11653 // if explicitly marked as 'noscript' | |
11654 if (this.hasAttribute('noscript') && !this.noscript) { | |
11655 this.noscript = true; | |
11656 // imperative element registration | |
11657 Polymer(name); | |
11658 } | |
11659 }, | |
11660 | |
11661 waitingForResources: function() { | |
11662 return this._needsResources; | |
11663 }, | |
11664 | |
11665 // NOTE: Elements must be queued in proper order for inheritance/composition | |
11666 // dependency resolution. Previously this was enforced for inheritance, | |
11667 // and by rule for composition. It's now entirely by rule. | |
11668 waitingForQueue: function() { | |
11669 return queue.enqueue(this, this.registerWhenReady, this._register); | |
11670 }, | |
11671 | |
11672 loadResources: function() { | |
11673 this._needsResources = true; | |
11674 this.loadStyles(function() { | |
11675 this._needsResources = false; | |
11676 this.registerWhenReady(); | |
11677 }.bind(this)); | |
11678 } | |
11679 | |
11680 }); | |
11681 | |
11682 // semi-pluggable APIs | |
11683 | |
11684 // TODO(sjmiles): should be fully pluggable (aka decoupled, currently | |
11685 // the various plugins are allowed to depend on each other directly) | |
11686 api.publish(api.declaration, prototype); | |
11687 | |
11688 // utility and bookkeeping | |
11689 | |
11690 function isRegistered(name) { | |
11691 return Boolean(HTMLElement.getPrototypeForTag(name)); | |
11692 } | |
11693 | |
11694 function isCustomTag(name) { | |
11695 return (name && name.indexOf('-') >= 0); | |
11696 } | |
11697 | |
11698 // boot tasks | |
11699 | |
11700 whenReady(function() { | |
11701 document.body.removeAttribute('unresolved'); | |
11702 document.dispatchEvent( | |
11703 new CustomEvent('polymer-ready', {bubbles: true}) | |
11704 ); | |
11705 }); | |
11706 | |
11707 // register polymer-element with document | |
11708 | |
11709 document.registerElement('polymer-element', {prototype: prototype}); | |
11710 | |
11711 })(Polymer); | |
11712 | |
11713 (function(scope) { | |
11714 | |
11715 /** | |
11716 * @class Polymer | |
11717 */ | |
11718 | |
11719 var whenReady = scope.whenReady; | |
11720 | |
11721 /** | |
11722 * Loads the set of HTMLImports contained in `node`. Notifies when all | |
11723 * the imports have loaded by calling the `callback` function argument. | |
11724 * This method can be used to lazily load imports. For example, given a | |
11725 * template: | |
11726 * | |
11727 * <template> | |
11728 * <link rel="import" href="my-import1.html"> | |
11729 * <link rel="import" href="my-import2.html"> | |
11730 * </template> | |
11731 * | |
11732 * Polymer.importElements(template.content, function() { | |
11733 * console.log('imports lazily loaded'); | |
11734 * }); | |
11735 * | |
11736 * @method importElements | |
11737 * @param {Node} node Node containing the HTMLImports to load. | |
11738 * @param {Function} callback Callback called when all imports have loaded. | |
11739 */ | |
11740 function importElements(node, callback) { | |
11741 if (node) { | |
11742 document.head.appendChild(node); | |
11743 whenReady(callback); | |
11744 } else if (callback) { | |
11745 callback(); | |
11746 } | |
11747 } | |
11748 | |
11749 /** | |
11750 * Loads an HTMLImport for each url specified in the `urls` array. | |
11751 * Notifies when all the imports have loaded by calling the `callback` | |
11752 * function argument. This method can be used to lazily load imports. | |
11753 * For example, | |
11754 * | |
11755 * Polymer.import(['my-import1.html', 'my-import2.html'], function() { | |
11756 * console.log('imports lazily loaded'); | |
11757 * }); | |
11758 * | |
11759 * @method import | |
11760 * @param {Array} urls Array of urls to load as HTMLImports. | |
11761 * @param {Function} callback Callback called when all imports have loaded. | |
11762 */ | |
11763 function _import(urls, callback) { | |
11764 if (urls && urls.length) { | |
11765 var frag = document.createDocumentFragment(); | |
11766 for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) { | |
11767 link = document.createElement('link'); | |
11768 link.rel = 'import'; | |
11769 link.href = url; | |
11770 frag.appendChild(link); | |
11771 } | |
11772 importElements(frag, callback); | |
11773 } else if (callback) { | |
11774 callback(); | |
11775 } | |
11776 } | |
11777 | |
11778 // exports | |
11779 scope.import = _import; | |
11780 scope.importElements = importElements; | |
11781 | |
11782 })(Polymer); | |
11783 | |
11784 /** | |
11785 * The `auto-binding` element extends the template element. It provides a quick | |
11786 * and easy way to do data binding without the need to setup a model. | |
11787 * The `auto-binding` element itself serves as the model and controller for the | |
11788 * elements it contains. Both data and event handlers can be bound. | |
11789 * | |
11790 * The `auto-binding` element acts just like a template that is bound to | |
11791 * a model. It stamps its content in the dom adjacent to itself. When the | |
11792 * content is stamped, the `template-bound` event is fired. | |
11793 * | |
11794 * Example: | |
11795 * | |
11796 * <template is="auto-binding"> | |
11797 * <div>Say something: <input value="{{value}}"></div> | |
11798 * <div>You said: {{value}}</div> | |
11799 * <button on-tap="{{buttonTap}}">Tap me!</button> | |
11800 * </template> | |
11801 * <script> | |
11802 * var template = document.querySelector('template'); | |
11803 * template.value = 'something'; | |
11804 * template.buttonTap = function() { | |
11805 * console.log('tap!'); | |
11806 * }; | |
11807 * </script> | |
11808 * | |
11809 * @module Polymer | |
11810 * @status stable | |
11811 */ | |
11812 | |
11813 (function() { | |
11814 | |
11815 var element = document.createElement('polymer-element'); | |
11816 element.setAttribute('name', 'auto-binding'); | |
11817 element.setAttribute('extends', 'template'); | |
11818 element.init(); | |
11819 | |
11820 Polymer('auto-binding', { | |
11821 | |
11822 createdCallback: function() { | |
11823 this.syntax = this.bindingDelegate = this.makeSyntax(); | |
11824 // delay stamping until polymer-ready so that auto-binding is not | |
11825 // required to load last. | |
11826 Polymer.whenPolymerReady(function() { | |
11827 this.model = this; | |
11828 this.setAttribute('bind', ''); | |
11829 // we don't bother with an explicit signal here, we could ust a MO | |
11830 // if necessary | |
11831 this.async(function() { | |
11832 // note: this will marshall *all* the elements in the parentNode | |
11833 // rather than just stamped ones. We'd need to use createInstance | |
11834 // to fix this or something else fancier. | |
11835 this.marshalNodeReferences(this.parentNode); | |
11836 // template stamping is asynchronous so stamping isn't complete | |
11837 // by polymer-ready; fire an event so users can use stamped elements | |
11838 this.fire('template-bound'); | |
11839 }); | |
11840 }.bind(this)); | |
11841 }, | |
11842 | |
11843 makeSyntax: function() { | |
11844 var events = Object.create(Polymer.api.declaration.events); | |
11845 var self = this; | |
11846 events.findController = function() { return self.model; }; | |
11847 | |
11848 var syntax = new PolymerExpressions(); | |
11849 var prepareBinding = syntax.prepareBinding; | |
11850 syntax.prepareBinding = function(pathString, name, node) { | |
11851 return events.prepareEventBinding(pathString, name, node) || | |
11852 prepareBinding.call(syntax, pathString, name, node); | |
11853 }; | |
11854 return syntax; | |
11855 } | |
11856 | |
11857 }); | |
11858 | |
11859 })(); | |
OLD | NEW |