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://polymer.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/CONTRIBUTORS.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/PATENTS.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_FRAGMENT_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 <= rect.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, including 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.createShadowRoot; 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 properties 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.clientY, path: inEvent.path}; 699 } 700 // use event path if available, otherwise build a path from target finding 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 Safari, 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 listening 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 recognizer 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 recognizer 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_also 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 mouse 1135 if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointermap.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 under 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 came 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 path 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.MSPOINTER_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 https://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.buttons === 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\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(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(code2), 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 need 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 range'); 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.NumericLiteral) { 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.Keyword) { 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') || matchKeyword('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, alternate); 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://polymer.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/CONTRIBUTORS.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/PATENTS.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 compatibility 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 forward 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.addedCount) { 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.addedCount); 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 + splice.addedCount, 5839 splice.removed, 0, splice.removed.length)); 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' && module ? 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://polymer.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/CONTRIBUTORS.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/PATENTS.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://polymer.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/CONTRIBUTORS.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/PATENTS.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._username += 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.length == 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 platform.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_REGEXP); 8262 return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEXP); 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 received 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] addHostListeners:', 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.getEventHandler(this, this, methodName)); 9123 } 9124 }, 9125 // call 'method' or function method on 'obj' with 'args', if the method exists 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, capture); 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 expecting; 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 observer [%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 unbindAll', 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 signal 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 method, 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 `attributeChanged` 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 var dom = this.instanceTemplate(template); 10040 // append to shadow dom 10041 root.appendChild(dom); 10042 // perform post-construction initialization tasks on shadow root 10043 this.shadowRootReady(root, template); 10044 // return the created shadow root 10045 return root; 10046 } 10047 }, 10048 10049 // utility function that stamps a <template> into light-dom 10050 lightFromTemplate: function(template, refNode) { 10051 if (template) { 10052 // TODO(sorvell): mark this element as an eventController so that 10053 // event listeners on bound nodes inside it will be called on it. 10054 // Note, the expectation here is that events on all descendants 10055 // should be handled by this element. 10056 this.eventController = this; 10057 // stamp template 10058 // which includes parsing and applying MDV bindings before being 10059 // inserted (to avoid {{}} in attribute values). 10060 var dom = this.instanceTemplate(template); 10061 // append to shadow dom 10062 if (refNode) { 10063 this.insertBefore(dom, refNode); 10064 } else { 10065 this.appendChild(dom); 10066 } 10067 // perform post-construction initialization tasks on ahem, light root 10068 this.shadowRootReady(this); 10069 // return the created shadow root 10070 return dom; 10071 } 10072 }, 10073 10074 shadowRootReady: function(root) { 10075 // locate nodes with id and store references to them in this.$ hash 10076 this.marshalNodeReferences(root); 10077 }, 10078 10079 // locate nodes with id and store references to them in this.$ hash 10080 marshalNodeReferences: function(root) { 10081 // establish $ instance variable 10082 var $ = this.$ = this.$ || {}; 10083 // populate $ from nodes with ID from the LOCAL tree 10084 if (root) { 10085 var n$ = root.querySelectorAll("[id]"); 10086 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { 10087 $[n.id] = n; 10088 }; 10089 } 10090 }, 10091 10092 /** 10093 * Register a one-time callback when a child-list or sub-tree mutation 10094 * occurs on node. 10095 * 10096 * For persistent callbacks, call onMutation from your listener. 10097 * 10098 * @method onMutation 10099 * @param Node {Node} node Node to watch for mutations. 10100 * @param Function {Function} listener Function to call on mutation. The function is invoked as `listener.call(this, observer, mutations);` where `observer` is the MutationObserver that triggered the notification, and `mutations` is the native mutation list. 10101 */ 10102 onMutation: function(node, listener) { 10103 var observer = new MutationObserver(function(mutations) { 10104 listener.call(this, observer, mutations); 10105 observer.disconnect(); 10106 }.bind(this)); 10107 observer.observe(node, {childList: true, subtree: true}); 10108 } 10109 }; 10110 10111 /** 10112 * @class Polymer 10113 */ 10114 10115 /** 10116 * Returns true if the object includes <a href="#polymer-base">polymer-base</a> in it's prototype chain. 10117 * 10118 * @method isBase 10119 * @param Object {Object} object Object to test. 10120 * @type Boolean 10121 */ 10122 function isBase(object) { 10123 return object.hasOwnProperty('PolymerBase') 10124 } 10125 10126 // name a base constructor for dev tools 10127 10128 /** 10129 * The Polymer base-class constructor. 10130 * 10131 * @property Base 10132 * @type Function 10133 */ 10134 function PolymerBase() {}; 10135 PolymerBase.prototype = base; 10136 base.constructor = PolymerBase; 10137 10138 // exports 10139 10140 scope.Base = PolymerBase; 10141 scope.isBase = isBase; 10142 scope.api.instance.base = base; 10143 10144 })(Polymer); 10145 10146 (function(scope) { 10147 10148 // imports 10149 10150 var log = window.WebComponents ? WebComponents.flags.log : {}; 10151 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; 10152 10153 // magic words 10154 10155 var STYLE_SCOPE_ATTRIBUTE = 'element'; 10156 var STYLE_CONTROLLER_SCOPE = 'controller'; 10157 10158 var styles = { 10159 STYLE_SCOPE_ATTRIBUTE: STYLE_SCOPE_ATTRIBUTE, 10160 /** 10161 * Installs external stylesheets and <style> elements with the attribute 10162 * polymer-scope='controller' into the scope of element. This is intended 10163 * to be a called during custom element construction. 10164 */ 10165 installControllerStyles: function() { 10166 // apply controller styles, but only if they are not yet applied 10167 var scope = this.findStyleScope(); 10168 if (scope && !this.scopeHasNamedStyle(scope, this.localName)) { 10169 // allow inherited controller styles 10170 var proto = getPrototypeOf(this), cssText = ''; 10171 while (proto && proto.element) { 10172 cssText += proto.element.cssTextForScope(STYLE_CONTROLLER_SCOPE); 10173 proto = getPrototypeOf(proto); 10174 } 10175 if (cssText) { 10176 this.installScopeCssText(cssText, scope); 10177 } 10178 } 10179 }, 10180 installScopeStyle: function(style, name, scope) { 10181 var scope = scope || this.findStyleScope(), name = name || ''; 10182 if (scope && !this.scopeHasNamedStyle(scope, this.localName + name)) { 10183 var cssText = ''; 10184 if (style instanceof Array) { 10185 for (var i=0, l=style.length, s; (i<l) && (s=style[i]); i++) { 10186 cssText += s.textContent + '\n\n'; 10187 } 10188 } else { 10189 cssText = style.textContent; 10190 } 10191 this.installScopeCssText(cssText, scope, name); 10192 } 10193 }, 10194 installScopeCssText: function(cssText, scope, name) { 10195 scope = scope || this.findStyleScope(); 10196 name = name || ''; 10197 if (!scope) { 10198 return; 10199 } 10200 if (hasShadowDOMPolyfill) { 10201 cssText = shimCssText(cssText, scope.host); 10202 } 10203 var style = this.element.cssTextToScopeStyle(cssText, 10204 STYLE_CONTROLLER_SCOPE); 10205 Polymer.applyStyleToScope(style, scope); 10206 // cache that this style has been applied 10207 this.styleCacheForScope(scope)[this.localName + name] = true; 10208 }, 10209 findStyleScope: function(node) { 10210 // find the shadow root that contains this element 10211 var n = node || this; 10212 while (n.parentNode) { 10213 n = n.parentNode; 10214 } 10215 return n; 10216 }, 10217 scopeHasNamedStyle: function(scope, name) { 10218 var cache = this.styleCacheForScope(scope); 10219 return cache[name]; 10220 }, 10221 styleCacheForScope: function(scope) { 10222 if (hasShadowDOMPolyfill) { 10223 var scopeName = scope.host ? scope.host.localName : scope.localName; 10224 return polyfillScopeStyleCache[scopeName] || (polyfillScopeStyleCache[scopeName] = {}); 10225 } else { 10226 return scope._scopeStyles = (scope._scopeStyles || {}); 10227 } 10228 } 10229 }; 10230 10231 var polyfillScopeStyleCache = {}; 10232 10233 // NOTE: use raw prototype traversal so that we ensure correct traversal 10234 // on platforms where the protoype chain is simulated via __proto__ (IE10) 10235 function getPrototypeOf(prototype) { 10236 return prototype.__proto__; 10237 } 10238 10239 function shimCssText(cssText, host) { 10240 var name = '', is = false; 10241 if (host) { 10242 name = host.localName; 10243 is = host.hasAttribute('is'); 10244 } 10245 var selector = WebComponents.ShadowCSS.makeScopeSelector(name, is); 10246 return WebComponents.ShadowCSS.shimCssText(cssText, selector); 10247 } 10248 10249 // exports 10250 10251 scope.api.instance.styles = styles; 10252 10253 })(Polymer); 10254 10255 (function(scope) { 10256 10257 // imports 10258 10259 var extend = scope.extend; 10260 var api = scope.api; 10261 10262 // imperative implementation: Polymer() 10263 10264 // specify an 'own' prototype for tag `name` 10265 function element(name, prototype) { 10266 if (typeof name !== 'string') { 10267 var script = prototype || document._currentScript; 10268 prototype = name; 10269 name = script && script.parentNode && script.parentNode.getAttribute ? 10270 script.parentNode.getAttribute('name') : ''; 10271 if (!name) { 10272 throw 'Element name could not be inferred.'; 10273 } 10274 } 10275 if (getRegisteredPrototype(name)) { 10276 throw 'Already registered (Polymer) prototype for element ' + name; 10277 } 10278 // cache the prototype 10279 registerPrototype(name, prototype); 10280 // notify the registrar waiting for 'name', if any 10281 notifyPrototype(name); 10282 } 10283 10284 // async prototype source 10285 10286 function waitingForPrototype(name, client) { 10287 waitPrototype[name] = client; 10288 } 10289 10290 var waitPrototype = {}; 10291 10292 function notifyPrototype(name) { 10293 if (waitPrototype[name]) { 10294 waitPrototype[name].registerWhenReady(); 10295 delete waitPrototype[name]; 10296 } 10297 } 10298 10299 // utility and bookkeeping 10300 10301 // maps tag names to prototypes, as registered with 10302 // Polymer. Prototypes associated with a tag name 10303 // using document.registerElement are available from 10304 // HTMLElement.getPrototypeForTag(). 10305 // If an element was fully registered by Polymer, then 10306 // Polymer.getRegisteredPrototype(name) === 10307 // HTMLElement.getPrototypeForTag(name) 10308 10309 var prototypesByName = {}; 10310 10311 function registerPrototype(name, prototype) { 10312 return prototypesByName[name] = prototype || {}; 10313 } 10314 10315 function getRegisteredPrototype(name) { 10316 return prototypesByName[name]; 10317 } 10318 10319 function instanceOfType(element, type) { 10320 if (typeof type !== 'string') { 10321 return false; 10322 } 10323 var proto = HTMLElement.getPrototypeForTag(type); 10324 var ctor = proto && proto.constructor; 10325 if (!ctor) { 10326 return false; 10327 } 10328 if (CustomElements.instanceof) { 10329 return CustomElements.instanceof(element, ctor); 10330 } 10331 return element instanceof ctor; 10332 } 10333 10334 // exports 10335 10336 scope.getRegisteredPrototype = getRegisteredPrototype; 10337 scope.waitingForPrototype = waitingForPrototype; 10338 scope.instanceOfType = instanceOfType; 10339 10340 // namespace shenanigans so we can expose our scope on the registration 10341 // function 10342 10343 // make window.Polymer reference `element()` 10344 10345 window.Polymer = element; 10346 10347 // TODO(sjmiles): find a way to do this that is less terrible 10348 // copy window.Polymer properties onto `element()` 10349 10350 extend(Polymer, scope); 10351 10352 // Under the HTMLImports polyfill, scripts in the main document 10353 // do not block on imports; we want to allow calls to Polymer in the main 10354 // document. WebComponents collects those calls until we can process them, which 10355 // we do here. 10356 10357 if (WebComponents.consumeDeclarations) { 10358 WebComponents.consumeDeclarations(function(declarations) { 10359 if (declarations) { 10360 for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i++) { 10361 element.apply(null, d); 10362 } 10363 } 10364 }); 10365 } 10366 10367 })(Polymer); 10368 10369 (function(scope) { 10370 10371 /** 10372 * @class polymer-base 10373 */ 10374 10375 /** 10376 * Resolve a url path to be relative to a `base` url. If unspecified, `base` 10377 * defaults to the element's ownerDocument url. Can be used to resolve 10378 * paths from element's in templates loaded in HTMLImports to be relative 10379 * to the document containing the element. Polymer automatically does this for 10380 * url attributes in element templates; however, if a url, for 10381 * example, contains a binding, then `resolvePath` can be used to ensure it is 10382 * relative to the element document. For example, in an element's template, 10383 * 10384 * <a href="{{resolvePath(path)}}">Resolved</a> 10385 * 10386 * @method resolvePath 10387 * @param {String} url Url path to resolve. 10388 * @param {String} base Optional base url against which to resolve, defaults 10389 * to the element's ownerDocument url. 10390 * returns {String} resolved url. 10391 */ 10392 10393 var path = { 10394 resolveElementPaths: function(node) { 10395 Polymer.urlResolver.resolveDom(node); 10396 }, 10397 addResolvePathApi: function() { 10398 // let assetpath attribute modify the resolve path 10399 var assetPath = this.getAttribute('assetpath') || ''; 10400 var root = new URL(assetPath, this.ownerDocument.baseURI); 10401 this.prototype.resolvePath = function(urlPath, base) { 10402 var u = new URL(urlPath, base || root); 10403 return u.href; 10404 }; 10405 } 10406 }; 10407 10408 // exports 10409 scope.api.declaration.path = path; 10410 10411 })(Polymer); 10412 10413 (function(scope) { 10414 10415 // imports 10416 10417 var log = window.WebComponents ? WebComponents.flags.log : {}; 10418 var api = scope.api.instance.styles; 10419 var STYLE_SCOPE_ATTRIBUTE = api.STYLE_SCOPE_ATTRIBUTE; 10420 10421 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; 10422 10423 // magic words 10424 10425 var STYLE_SELECTOR = 'style'; 10426 var STYLE_LOADABLE_MATCH = '@import'; 10427 var SHEET_SELECTOR = 'link[rel=stylesheet]'; 10428 var STYLE_GLOBAL_SCOPE = 'global'; 10429 var SCOPE_ATTR = 'polymer-scope'; 10430 10431 var styles = { 10432 // returns true if resources are loading 10433 loadStyles: function(callback) { 10434 var template = this.fetchTemplate(); 10435 var content = template && this.templateContent(); 10436 if (content) { 10437 this.convertSheetsToStyles(content); 10438 var styles = this.findLoadableStyles(content); 10439 if (styles.length) { 10440 var templateUrl = template.ownerDocument.baseURI; 10441 return Polymer.styleResolver.loadStyles(styles, templateUrl, callback); 10442 } 10443 } 10444 if (callback) { 10445 callback(); 10446 } 10447 }, 10448 convertSheetsToStyles: function(root) { 10449 var s$ = root.querySelectorAll(SHEET_SELECTOR); 10450 for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) { 10451 c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI), 10452 this.ownerDocument); 10453 this.copySheetAttributes(c, s); 10454 s.parentNode.replaceChild(c, s); 10455 } 10456 }, 10457 copySheetAttributes: function(style, link) { 10458 for (var i=0, a$=link.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) { 10459 if (a.name !== 'rel' && a.name !== 'href') { 10460 style.setAttribute(a.name, a.value); 10461 } 10462 } 10463 }, 10464 findLoadableStyles: function(root) { 10465 var loadables = []; 10466 if (root) { 10467 var s$ = root.querySelectorAll(STYLE_SELECTOR); 10468 for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) { 10469 if (s.textContent.match(STYLE_LOADABLE_MATCH)) { 10470 loadables.push(s); 10471 } 10472 } 10473 } 10474 return loadables; 10475 }, 10476 /** 10477 * Install external stylesheets loaded in <polymer-element> elements into the 10478 * element's template. 10479 * @param elementElement The <element> element to style. 10480 */ 10481 installSheets: function() { 10482 this.cacheSheets(); 10483 this.cacheStyles(); 10484 this.installLocalSheets(); 10485 this.installGlobalStyles(); 10486 }, 10487 /** 10488 * Remove all sheets from element and store for later use. 10489 */ 10490 cacheSheets: function() { 10491 this.sheets = this.findNodes(SHEET_SELECTOR); 10492 this.sheets.forEach(function(s) { 10493 if (s.parentNode) { 10494 s.parentNode.removeChild(s); 10495 } 10496 }); 10497 }, 10498 cacheStyles: function() { 10499 this.styles = this.findNodes(STYLE_SELECTOR + '[' + SCOPE_ATTR + ']'); 10500 this.styles.forEach(function(s) { 10501 if (s.parentNode) { 10502 s.parentNode.removeChild(s); 10503 } 10504 }); 10505 }, 10506 /** 10507 * Takes external stylesheets loaded in an <element> element and moves 10508 * their content into a <style> element inside the <element>'s template. 10509 * The sheet is then removed from the <element>. This is done only so 10510 * that if the element is loaded in the main document, the sheet does 10511 * not become active. 10512 * Note, ignores sheets with the attribute 'polymer-scope'. 10513 * @param elementElement The <element> element to style. 10514 */ 10515 installLocalSheets: function () { 10516 var sheets = this.sheets.filter(function(s) { 10517 return !s.hasAttribute(SCOPE_ATTR); 10518 }); 10519 var content = this.templateContent(); 10520 if (content) { 10521 var cssText = ''; 10522 sheets.forEach(function(sheet) { 10523 cssText += cssTextFromSheet(sheet) + '\n'; 10524 }); 10525 if (cssText) { 10526 var style = createStyleElement(cssText, this.ownerDocument); 10527 content.insertBefore(style, content.firstChild); 10528 } 10529 } 10530 }, 10531 findNodes: function(selector, matcher) { 10532 var nodes = this.querySelectorAll(selector).array(); 10533 var content = this.templateContent(); 10534 if (content) { 10535 var templateNodes = content.querySelectorAll(selector).array(); 10536 nodes = nodes.concat(templateNodes); 10537 } 10538 return matcher ? nodes.filter(matcher) : nodes; 10539 }, 10540 /** 10541 * Promotes external stylesheets and <style> elements with the attribute 10542 * polymer-scope='global' into global scope. 10543 * This is particularly useful for defining @keyframe rules which 10544 * currently do not function in scoped or shadow style elements. 10545 * (See wkb.ug/72462) 10546 * @param elementElement The <element> element to style. 10547 */ 10548 // TODO(sorvell): remove when wkb.ug/72462 is addressed. 10549 installGlobalStyles: function() { 10550 var style = this.styleForScope(STYLE_GLOBAL_SCOPE); 10551 applyStyleToScope(style, document.head); 10552 }, 10553 cssTextForScope: function(scopeDescriptor) { 10554 var cssText = ''; 10555 // handle stylesheets 10556 var selector = '[' + SCOPE_ATTR + '=' + scopeDescriptor + ']'; 10557 var matcher = function(s) { 10558 return matchesSelector(s, selector); 10559 }; 10560 var sheets = this.sheets.filter(matcher); 10561 sheets.forEach(function(sheet) { 10562 cssText += cssTextFromSheet(sheet) + '\n\n'; 10563 }); 10564 // handle cached style elements 10565 var styles = this.styles.filter(matcher); 10566 styles.forEach(function(style) { 10567 cssText += style.textContent + '\n\n'; 10568 }); 10569 return cssText; 10570 }, 10571 styleForScope: function(scopeDescriptor) { 10572 var cssText = this.cssTextForScope(scopeDescriptor); 10573 return this.cssTextToScopeStyle(cssText, scopeDescriptor); 10574 }, 10575 cssTextToScopeStyle: function(cssText, scopeDescriptor) { 10576 if (cssText) { 10577 var style = createStyleElement(cssText); 10578 style.setAttribute(STYLE_SCOPE_ATTRIBUTE, this.getAttribute('name') + 10579 '-' + scopeDescriptor); 10580 return style; 10581 } 10582 } 10583 }; 10584 10585 function importRuleForSheet(sheet, baseUrl) { 10586 var href = new URL(sheet.getAttribute('href'), baseUrl).href; 10587 return '@import \'' + href + '\';'; 10588 } 10589 10590 function applyStyleToScope(style, scope) { 10591 if (style) { 10592 if (scope === document) { 10593 scope = document.head; 10594 } 10595 if (hasShadowDOMPolyfill) { 10596 scope = document.head; 10597 } 10598 // TODO(sorvell): necessary for IE 10599 // see https://connect.microsoft.com/IE/feedback/details/790212/ 10600 // cloning-a-style-element-and-adding-to-document-produces 10601 // -unexpected-result#details 10602 // var clone = style.cloneNode(true); 10603 var clone = createStyleElement(style.textContent); 10604 var attr = style.getAttribute(STYLE_SCOPE_ATTRIBUTE); 10605 if (attr) { 10606 clone.setAttribute(STYLE_SCOPE_ATTRIBUTE, attr); 10607 } 10608 // TODO(sorvell): probably too brittle; try to figure out 10609 // where to put the element. 10610 var refNode = scope.firstElementChild; 10611 if (scope === document.head) { 10612 var selector = 'style[' + STYLE_SCOPE_ATTRIBUTE + ']'; 10613 var s$ = document.head.querySelectorAll(selector); 10614 if (s$.length) { 10615 refNode = s$[s$.length-1].nextElementSibling; 10616 } 10617 } 10618 scope.insertBefore(clone, refNode); 10619 } 10620 } 10621 10622 function createStyleElement(cssText, scope) { 10623 scope = scope || document; 10624 scope = scope.createElement ? scope : scope.ownerDocument; 10625 var style = scope.createElement('style'); 10626 style.textContent = cssText; 10627 return style; 10628 } 10629 10630 function cssTextFromSheet(sheet) { 10631 return (sheet && sheet.__resource) || ''; 10632 } 10633 10634 function matchesSelector(node, inSelector) { 10635 if (matches) { 10636 return matches.call(node, inSelector); 10637 } 10638 } 10639 var p = HTMLElement.prototype; 10640 var matches = p.matches || p.matchesSelector || p.webkitMatchesSelector 10641 || p.mozMatchesSelector; 10642 10643 // exports 10644 10645 scope.api.declaration.styles = styles; 10646 scope.applyStyleToScope = applyStyleToScope; 10647 10648 })(Polymer); 10649 10650 (function(scope) { 10651 10652 // imports 10653 10654 var log = window.WebComponents ? WebComponents.flags.log : {}; 10655 var api = scope.api.instance.events; 10656 var EVENT_PREFIX = api.EVENT_PREFIX; 10657 10658 var mixedCaseEventTypes = {}; 10659 [ 10660 'webkitAnimationStart', 10661 'webkitAnimationEnd', 10662 'webkitTransitionEnd', 10663 'DOMFocusOut', 10664 'DOMFocusIn', 10665 'DOMMouseScroll' 10666 ].forEach(function(e) { 10667 mixedCaseEventTypes[e.toLowerCase()] = e; 10668 }); 10669 10670 // polymer-element declarative api: events feature 10671 var events = { 10672 parseHostEvents: function() { 10673 // our delegates map 10674 var delegates = this.prototype.eventDelegates; 10675 // extract data from attributes into delegates 10676 this.addAttributeDelegates(delegates); 10677 }, 10678 addAttributeDelegates: function(delegates) { 10679 // for each attribute 10680 for (var i=0, a; a=this.attributes[i]; i++) { 10681 // does it have magic marker identifying it as an event delegate? 10682 if (this.hasEventPrefix(a.name)) { 10683 // if so, add the info to delegates 10684 delegates[this.removeEventPrefix(a.name)] = a.value.replace('{{', '') 10685 .replace('}}', '').trim(); 10686 } 10687 } 10688 }, 10689 // starts with 'on-' 10690 hasEventPrefix: function (n) { 10691 return n && (n[0] === 'o') && (n[1] === 'n') && (n[2] === '-'); 10692 }, 10693 removeEventPrefix: function(n) { 10694 return n.slice(prefixLength); 10695 }, 10696 findController: function(node) { 10697 while (node.parentNode) { 10698 if (node.eventController) { 10699 return node.eventController; 10700 } 10701 node = node.parentNode; 10702 } 10703 return node.host; 10704 }, 10705 getEventHandler: function(controller, target, method) { 10706 var events = this; 10707 return function(e) { 10708 if (!controller || !controller.PolymerBase) { 10709 controller = events.findController(target); 10710 } 10711 10712 var args = [e, e.detail, e.currentTarget]; 10713 controller.dispatchMethod(controller, method, args); 10714 }; 10715 }, 10716 prepareEventBinding: function(pathString, name, node) { 10717 if (!this.hasEventPrefix(name)) 10718 return; 10719 10720 var eventType = this.removeEventPrefix(name); 10721 eventType = mixedCaseEventTypes[eventType] || eventType; 10722 10723 var events = this; 10724 10725 return function(model, node, oneTime) { 10726 var handler = events.getEventHandler(undefined, node, pathString); 10727 PolymerGestures.addEventListener(node, eventType, handler); 10728 10729 if (oneTime) 10730 return; 10731 10732 // TODO(rafaelw): This is really pointless work. Aside from the cost 10733 // of these allocations, NodeBind is going to setAttribute back to its 10734 // current value. Fixing this would mean changing the TemplateBinding 10735 // binding delegate API. 10736 function bindingValue() { 10737 return '{{ ' + pathString + ' }}'; 10738 } 10739 10740 return { 10741 open: bindingValue, 10742 discardChanges: bindingValue, 10743 close: function() { 10744 PolymerGestures.removeEventListener(node, eventType, handler); 10745 } 10746 }; 10747 }; 10748 } 10749 }; 10750 10751 var prefixLength = EVENT_PREFIX.length; 10752 10753 // exports 10754 scope.api.declaration.events = events; 10755 10756 })(Polymer); 10757 10758 (function(scope) { 10759 10760 // element api 10761 10762 var observationBlacklist = ['attribute']; 10763 10764 var properties = { 10765 inferObservers: function(prototype) { 10766 // called before prototype.observe is chained to inherited object 10767 var observe = prototype.observe, property; 10768 for (var n in prototype) { 10769 if (n.slice(-7) === 'Changed') { 10770 property = n.slice(0, -7); 10771 if (this.canObserveProperty(property)) { 10772 if (!observe) { 10773 observe = (prototype.observe = {}); 10774 } 10775 observe[property] = observe[property] || n; 10776 } 10777 } 10778 } 10779 }, 10780 canObserveProperty: function(property) { 10781 return (observationBlacklist.indexOf(property) < 0); 10782 }, 10783 explodeObservers: function(prototype) { 10784 // called before prototype.observe is chained to inherited object 10785 var o = prototype.observe; 10786 if (o) { 10787 var exploded = {}; 10788 for (var n in o) { 10789 var names = n.split(' '); 10790 for (var i=0, ni; ni=names[i]; i++) { 10791 exploded[ni] = o[n]; 10792 } 10793 } 10794 prototype.observe = exploded; 10795 } 10796 }, 10797 optimizePropertyMaps: function(prototype) { 10798 if (prototype.observe) { 10799 // construct name list 10800 var a = prototype._observeNames = []; 10801 for (var n in prototype.observe) { 10802 var names = n.split(' '); 10803 for (var i=0, ni; ni=names[i]; i++) { 10804 a.push(ni); 10805 } 10806 } 10807 } 10808 if (prototype.publish) { 10809 // construct name list 10810 var a = prototype._publishNames = []; 10811 for (var n in prototype.publish) { 10812 a.push(n); 10813 } 10814 } 10815 if (prototype.computed) { 10816 // construct name list 10817 var a = prototype._computedNames = []; 10818 for (var n in prototype.computed) { 10819 a.push(n); 10820 } 10821 } 10822 }, 10823 publishProperties: function(prototype, base) { 10824 // if we have any properties to publish 10825 var publish = prototype.publish; 10826 if (publish) { 10827 // transcribe `publish` entries onto own prototype 10828 this.requireProperties(publish, prototype, base); 10829 // warn and remove accessor names that are broken on some browsers 10830 this.filterInvalidAccessorNames(publish); 10831 // construct map of lower-cased property names 10832 prototype._publishLC = this.lowerCaseMap(publish); 10833 } 10834 var computed = prototype.computed; 10835 if (computed) { 10836 // warn and remove accessor names that are broken on some browsers 10837 this.filterInvalidAccessorNames(computed); 10838 } 10839 }, 10840 // Publishing/computing a property where the name might conflict with a 10841 // browser property is not currently supported to help users of Polymer 10842 // avoid browser bugs: 10843 // 10844 // https://code.google.com/p/chromium/issues/detail?id=43394 10845 // https://bugs.webkit.org/show_bug.cgi?id=49739 10846 // 10847 // We can lift this restriction when those bugs are fixed. 10848 filterInvalidAccessorNames: function(propertyNames) { 10849 for (var name in propertyNames) { 10850 // Check if the name is in our blacklist. 10851 if (this.propertyNameBlacklist[name]) { 10852 console.warn('Cannot define property "' + name + '" for element "' + 10853 this.name + '" because it has the same name as an HTMLElement ' + 10854 'property, and not all browsers support overriding that. ' + 10855 'Consider giving it a different name.'); 10856 // Remove the invalid accessor from the list. 10857 delete propertyNames[name]; 10858 } 10859 } 10860 }, 10861 // 10862 // `name: value` entries in the `publish` object may need to generate 10863 // matching properties on the prototype. 10864 // 10865 // Values that are objects may have a `reflect` property, which 10866 // signals that the value describes property control metadata. 10867 // In metadata objects, the prototype default value (if any) 10868 // is encoded in the `value` property. 10869 // 10870 // publish: { 10871 // foo: 5, 10872 // bar: {value: true, reflect: true}, 10873 // zot: {} 10874 // } 10875 // 10876 // `reflect` metadata property controls whether changes to the property 10877 // are reflected back to the attribute (default false). 10878 // 10879 // A value is stored on the prototype unless it's === `undefined`, 10880 // in which case the base chain is checked for a value. 10881 // If the basal value is also undefined, `null` is stored on the prototype. 10882 // 10883 // The reflection data is stored on another prototype object, `reflect` 10884 // which also can be specified directly. 10885 // 10886 // reflect: { 10887 // foo: true 10888 // } 10889 // 10890 requireProperties: function(propertyInfos, prototype, base) { 10891 // per-prototype storage for reflected properties 10892 prototype.reflect = prototype.reflect || {}; 10893 // ensure a prototype value for each property 10894 // and update the property's reflect to attribute status 10895 for (var n in propertyInfos) { 10896 var value = propertyInfos[n]; 10897 // value has metadata if it has a `reflect` property 10898 if (value && value.reflect !== undefined) { 10899 prototype.reflect[n] = Boolean(value.reflect); 10900 value = value.value; 10901 } 10902 // only set a value if one is specified 10903 if (value !== undefined) { 10904 prototype[n] = value; 10905 } 10906 } 10907 }, 10908 lowerCaseMap: function(properties) { 10909 var map = {}; 10910 for (var n in properties) { 10911 map[n.toLowerCase()] = n; 10912 } 10913 return map; 10914 }, 10915 createPropertyAccessor: function(name, ignoreWrites) { 10916 var proto = this.prototype; 10917 10918 var privateName = name + '_'; 10919 var privateObservable = name + 'Observable_'; 10920 proto[privateName] = proto[name]; 10921 10922 Object.defineProperty(proto, name, { 10923 get: function() { 10924 var observable = this[privateObservable]; 10925 if (observable) 10926 observable.deliver(); 10927 10928 return this[privateName]; 10929 }, 10930 set: function(value) { 10931 if (ignoreWrites) { 10932 return this[privateName]; 10933 } 10934 10935 var observable = this[privateObservable]; 10936 if (observable) { 10937 observable.setValue(value); 10938 return; 10939 } 10940 10941 var oldValue = this[privateName]; 10942 this[privateName] = value; 10943 this.emitPropertyChangeRecord(name, value, oldValue); 10944 10945 return value; 10946 }, 10947 configurable: true 10948 }); 10949 }, 10950 createPropertyAccessors: function(prototype) { 10951 var n$ = prototype._computedNames; 10952 if (n$ && n$.length) { 10953 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { 10954 this.createPropertyAccessor(n, true); 10955 } 10956 } 10957 var n$ = prototype._publishNames; 10958 if (n$ && n$.length) { 10959 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { 10960 // If the property is computed and published, the accessor is created 10961 // above. 10962 if (!prototype.computed || !prototype.computed[n]) { 10963 this.createPropertyAccessor(n); 10964 } 10965 } 10966 } 10967 }, 10968 // This list contains some property names that people commonly want to use, 10969 // but won't work because of Chrome/Safari bugs. It isn't an exhaustive 10970 // list. In particular it doesn't contain any property names found on 10971 // subtypes of HTMLElement (e.g. name, value). Rather it attempts to catch 10972 // some common cases. 10973 propertyNameBlacklist: { 10974 children: 1, 10975 'class': 1, 10976 id: 1, 10977 hidden: 1, 10978 style: 1, 10979 title: 1, 10980 } 10981 }; 10982 10983 // exports 10984 10985 scope.api.declaration.properties = properties; 10986 10987 })(Polymer); 10988 10989 (function(scope) { 10990 10991 // magic words 10992 10993 var ATTRIBUTES_ATTRIBUTE = 'attributes'; 10994 var ATTRIBUTES_REGEX = /\s|,/; 10995 10996 // attributes api 10997 10998 var attributes = { 10999 11000 inheritAttributesObjects: function(prototype) { 11001 // chain our lower-cased publish map to the inherited version 11002 this.inheritObject(prototype, 'publishLC'); 11003 // chain our instance attributes map to the inherited version 11004 this.inheritObject(prototype, '_instanceAttributes'); 11005 }, 11006 11007 publishAttributes: function(prototype, base) { 11008 // merge names from 'attributes' attribute into the 'publish' object 11009 var attributes = this.getAttribute(ATTRIBUTES_ATTRIBUTE); 11010 if (attributes) { 11011 // create a `publish` object if needed. 11012 // the `publish` object is only relevant to this prototype, the 11013 // publishing logic in `declaration/properties.js` is responsible for 11014 // managing property values on the prototype chain. 11015 // TODO(sjmiles): the `publish` object is later chained to it's 11016 // ancestor object, presumably this is only for 11017 // reflection or other non-library uses. 11018 var publish = prototype.publish || (prototype.publish = {}); 11019 // names='a b c' or names='a,b,c' 11020 var names = attributes.split(ATTRIBUTES_REGEX); 11021 // record each name for publishing 11022 for (var i=0, l=names.length, n; i<l; i++) { 11023 // remove excess ws 11024 n = names[i].trim(); 11025 // looks weird, but causes n to exist on `publish` if it does not; 11026 // a more careful test would need expensive `in` operator 11027 if (n && publish[n] === undefined) { 11028 publish[n] = undefined; 11029 } 11030 } 11031 } 11032 }, 11033 11034 // record clonable attributes from <element> 11035 accumulateInstanceAttributes: function() { 11036 // inherit instance attributes 11037 var clonable = this.prototype._instanceAttributes; 11038 // merge attributes from element 11039 var a$ = this.attributes; 11040 for (var i=0, l=a$.length, a; (i<l) && (a=a$[i]); i++) { 11041 if (this.isInstanceAttribute(a.name)) { 11042 clonable[a.name] = a.value; 11043 } 11044 } 11045 }, 11046 11047 isInstanceAttribute: function(name) { 11048 return !this.blackList[name] && name.slice(0,3) !== 'on-'; 11049 }, 11050 11051 // do not clone these attributes onto instances 11052 blackList: { 11053 name: 1, 11054 'extends': 1, 11055 constructor: 1, 11056 noscript: 1, 11057 assetpath: 1, 11058 'cache-csstext': 1 11059 } 11060 11061 }; 11062 11063 // add ATTRIBUTES_ATTRIBUTE to the blacklist 11064 attributes.blackList[ATTRIBUTES_ATTRIBUTE] = 1; 11065 11066 // exports 11067 11068 scope.api.declaration.attributes = attributes; 11069 11070 })(Polymer); 11071 11072 (function(scope) { 11073 11074 // imports 11075 var events = scope.api.declaration.events; 11076 11077 var syntax = new PolymerExpressions(); 11078 var prepareBinding = syntax.prepareBinding; 11079 11080 // Polymer takes a first crack at the binding to see if it's a declarative 11081 // event handler. 11082 syntax.prepareBinding = function(pathString, name, node) { 11083 return events.prepareEventBinding(pathString, name, node) || 11084 prepareBinding.call(syntax, pathString, name, node); 11085 }; 11086 11087 // declaration api supporting mdv 11088 var mdv = { 11089 syntax: syntax, 11090 fetchTemplate: function() { 11091 return this.querySelector('template'); 11092 }, 11093 templateContent: function() { 11094 var template = this.fetchTemplate(); 11095 return template && template.content; 11096 }, 11097 installBindingDelegate: function(template) { 11098 if (template) { 11099 template.bindingDelegate = this.syntax; 11100 } 11101 } 11102 }; 11103 11104 // exports 11105 scope.api.declaration.mdv = mdv; 11106 11107 })(Polymer); 11108 11109 (function(scope) { 11110 11111 // imports 11112 11113 var api = scope.api; 11114 var isBase = scope.isBase; 11115 var extend = scope.extend; 11116 11117 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; 11118 11119 // prototype api 11120 11121 var prototype = { 11122 11123 register: function(name, extendeeName) { 11124 // build prototype combining extendee, Polymer base, and named api 11125 this.buildPrototype(name, extendeeName); 11126 // register our custom element with the platform 11127 this.registerPrototype(name, extendeeName); 11128 // reference constructor in a global named by 'constructor' attribute 11129 this.publishConstructor(); 11130 }, 11131 11132 buildPrototype: function(name, extendeeName) { 11133 // get our custom prototype (before chaining) 11134 var extension = scope.getRegisteredPrototype(name); 11135 // get basal prototype 11136 var base = this.generateBasePrototype(extendeeName); 11137 // implement declarative features 11138 this.desugarBeforeChaining(extension, base); 11139 // join prototypes 11140 this.prototype = this.chainPrototypes(extension, base); 11141 // more declarative features 11142 this.desugarAfterChaining(name, extendeeName); 11143 }, 11144 11145 desugarBeforeChaining: function(prototype, base) { 11146 // back reference declaration element 11147 // TODO(sjmiles): replace `element` with `elementElement` or `declaration` 11148 prototype.element = this; 11149 // transcribe `attributes` declarations onto own prototype's `publish` 11150 this.publishAttributes(prototype, base); 11151 // `publish` properties to the prototype and to attribute watch 11152 this.publishProperties(prototype, base); 11153 // infer observers for `observe` list based on method names 11154 this.inferObservers(prototype); 11155 // desugar compound observer syntax, e.g. 'a b c' 11156 this.explodeObservers(prototype); 11157 }, 11158 11159 chainPrototypes: function(prototype, base) { 11160 // chain various meta-data objects to inherited versions 11161 this.inheritMetaData(prototype, base); 11162 // chain custom api to inherited 11163 var chained = this.chainObject(prototype, base); 11164 // x-platform fixup 11165 ensurePrototypeTraversal(chained); 11166 return chained; 11167 }, 11168 11169 inheritMetaData: function(prototype, base) { 11170 // chain observe object to inherited 11171 this.inheritObject('observe', prototype, base); 11172 // chain publish object to inherited 11173 this.inheritObject('publish', prototype, base); 11174 // chain reflect object to inherited 11175 this.inheritObject('reflect', prototype, base); 11176 // chain our lower-cased publish map to the inherited version 11177 this.inheritObject('_publishLC', prototype, base); 11178 // chain our instance attributes map to the inherited version 11179 this.inheritObject('_instanceAttributes', prototype, base); 11180 // chain our event delegates map to the inherited version 11181 this.inheritObject('eventDelegates', prototype, base); 11182 }, 11183 11184 // implement various declarative features 11185 desugarAfterChaining: function(name, extendee) { 11186 // build side-chained lists to optimize iterations 11187 this.optimizePropertyMaps(this.prototype); 11188 this.createPropertyAccessors(this.prototype); 11189 // install mdv delegate on template 11190 this.installBindingDelegate(this.fetchTemplate()); 11191 // install external stylesheets as if they are inline 11192 this.installSheets(); 11193 // adjust any paths in dom from imports 11194 this.resolveElementPaths(this); 11195 // compile list of attributes to copy to instances 11196 this.accumulateInstanceAttributes(); 11197 // parse on-* delegates declared on `this` element 11198 this.parseHostEvents(); 11199 // 11200 // install a helper method this.resolvePath to aid in 11201 // setting resource urls. e.g. 11202 // this.$.image.src = this.resolvePath('images/foo.png') 11203 this.addResolvePathApi(); 11204 // under ShadowDOMPolyfill, transforms to approximate missing CSS features 11205 if (hasShadowDOMPolyfill) { 11206 WebComponents.ShadowCSS.shimStyling(this.templateContent(), name, 11207 extendee); 11208 } 11209 // allow custom element access to the declarative context 11210 if (this.prototype.registerCallback) { 11211 this.prototype.registerCallback(this); 11212 } 11213 }, 11214 11215 // if a named constructor is requested in element, map a reference 11216 // to the constructor to the given symbol 11217 publishConstructor: function() { 11218 var symbol = this.getAttribute('constructor'); 11219 if (symbol) { 11220 window[symbol] = this.ctor; 11221 } 11222 }, 11223 11224 // build prototype combining extendee, Polymer base, and named api 11225 generateBasePrototype: function(extnds) { 11226 var prototype = this.findBasePrototype(extnds); 11227 if (!prototype) { 11228 // create a prototype based on tag-name extension 11229 var prototype = HTMLElement.getPrototypeForTag(extnds); 11230 // insert base api in inheritance chain (if needed) 11231 prototype = this.ensureBaseApi(prototype); 11232 // memoize this base 11233 memoizedBases[extnds] = prototype; 11234 } 11235 return prototype; 11236 }, 11237 11238 findBasePrototype: function(name) { 11239 return memoizedBases[name]; 11240 }, 11241 11242 // install Polymer instance api into prototype chain, as needed 11243 ensureBaseApi: function(prototype) { 11244 if (prototype.PolymerBase) { 11245 return prototype; 11246 } 11247 var extended = Object.create(prototype); 11248 // we need a unique copy of base api for each base prototype 11249 // therefore we 'extend' here instead of simply chaining 11250 api.publish(api.instance, extended); 11251 // TODO(sjmiles): sharing methods across prototype chains is 11252 // not supported by 'super' implementation which optimizes 11253 // by memoizing prototype relationships. 11254 // Probably we should have a version of 'extend' that is 11255 // share-aware: it could study the text of each function, 11256 // look for usage of 'super', and wrap those functions in 11257 // closures. 11258 // As of now, there is only one problematic method, so 11259 // we just patch it manually. 11260 // To avoid re-entrancy problems, the special super method 11261 // installed is called `mixinSuper` and the mixin method 11262 // must use this method instead of the default `super`. 11263 this.mixinMethod(extended, prototype, api.instance.mdv, 'bind'); 11264 // return buffed-up prototype 11265 return extended; 11266 }, 11267 11268 mixinMethod: function(extended, prototype, api, name) { 11269 var $super = function(args) { 11270 return prototype[name].apply(this, args); 11271 }; 11272 extended[name] = function() { 11273 this.mixinSuper = $super; 11274 return api[name].apply(this, arguments); 11275 } 11276 }, 11277 11278 // ensure prototype[name] inherits from a prototype.prototype[name] 11279 inheritObject: function(name, prototype, base) { 11280 // require an object 11281 var source = prototype[name] || {}; 11282 // chain inherited properties onto a new object 11283 prototype[name] = this.chainObject(source, base[name]); 11284 }, 11285 11286 // register 'prototype' to custom element 'name', store constructor 11287 registerPrototype: function(name, extendee) { 11288 var info = { 11289 prototype: this.prototype 11290 } 11291 // native element must be specified in extends 11292 var typeExtension = this.findTypeExtension(extendee); 11293 if (typeExtension) { 11294 info.extends = typeExtension; 11295 } 11296 // register the prototype with HTMLElement for name lookup 11297 HTMLElement.register(name, this.prototype); 11298 // register the custom type 11299 this.ctor = document.registerElement(name, info); 11300 }, 11301 11302 findTypeExtension: function(name) { 11303 if (name && name.indexOf('-') < 0) { 11304 return name; 11305 } else { 11306 var p = this.findBasePrototype(name); 11307 if (p.element) { 11308 return this.findTypeExtension(p.element.extends); 11309 } 11310 } 11311 } 11312 11313 }; 11314 11315 // memoize base prototypes 11316 var memoizedBases = {}; 11317 11318 // implementation of 'chainObject' depends on support for __proto__ 11319 if (Object.__proto__) { 11320 prototype.chainObject = function(object, inherited) { 11321 if (object && inherited && object !== inherited) { 11322 object.__proto__ = inherited; 11323 } 11324 return object; 11325 } 11326 } else { 11327 prototype.chainObject = function(object, inherited) { 11328 if (object && inherited && object !== inherited) { 11329 var chained = Object.create(inherited); 11330 object = extend(chained, object); 11331 } 11332 return object; 11333 } 11334 } 11335 11336 // On platforms that do not support __proto__ (versions of IE), the prototype 11337 // chain of a custom element is simulated via installation of __proto__. 11338 // Although custom elements manages this, we install it here so it's 11339 // available during desugaring. 11340 function ensurePrototypeTraversal(prototype) { 11341 if (!Object.__proto__) { 11342 var ancestor = Object.getPrototypeOf(prototype); 11343 prototype.__proto__ = ancestor; 11344 if (isBase(ancestor)) { 11345 ancestor.__proto__ = Object.getPrototypeOf(ancestor); 11346 } 11347 } 11348 } 11349 11350 // exports 11351 11352 api.declaration.prototype = prototype; 11353 11354 })(Polymer); 11355 11356 (function(scope) { 11357 11358 /* 11359 11360 Elements are added to a registration queue so that they register in 11361 the proper order at the appropriate time. We do this for a few reasons: 11362 11363 * to enable elements to load resources (like stylesheets) 11364 asynchronously. We need to do this until the platform provides an efficient 11365 alternative. One issue is that remote @import stylesheets are 11366 re-fetched whenever stamped into a shadowRoot. 11367 11368 * to ensure elements loaded 'at the same time' (e.g. via some set of 11369 imports) are registered as a batch. This allows elements to be enured from 11370 upgrade ordering as long as they query the dom tree 1 task after 11371 upgrade (aka domReady). This is a performance tradeoff. On the one hand, 11372 elements that could register while imports are loading are prevented from 11373 doing so. On the other, grouping upgrades into a single task means less 11374 incremental work (for example style recalcs), Also, we can ensure the 11375 document is in a known state at the single quantum of time when 11376 elements upgrade. 11377 11378 */ 11379 var queue = { 11380 11381 // tell the queue to wait for an element to be ready 11382 wait: function(element) { 11383 if (!element.__queue) { 11384 element.__queue = {}; 11385 elements.push(element); 11386 } 11387 }, 11388 11389 // enqueue an element to the next spot in the queue. 11390 enqueue: function(element, check, go) { 11391 var shouldAdd = element.__queue && !element.__queue.check; 11392 if (shouldAdd) { 11393 queueForElement(element).push(element); 11394 element.__queue.check = check; 11395 element.__queue.go = go; 11396 } 11397 return (this.indexOf(element) !== 0); 11398 }, 11399 11400 indexOf: function(element) { 11401 var i = queueForElement(element).indexOf(element); 11402 if (i >= 0 && document.contains(element)) { 11403 i += (HTMLImports.useNative || HTMLImports.ready) ? 11404 importQueue.length : 1e9; 11405 } 11406 return i; 11407 }, 11408 11409 // tell the queue an element is ready to be registered 11410 go: function(element) { 11411 var readied = this.remove(element); 11412 if (readied) { 11413 element.__queue.flushable = true; 11414 this.addToFlushQueue(readied); 11415 this.check(); 11416 } 11417 }, 11418 11419 remove: function(element) { 11420 var i = this.indexOf(element); 11421 if (i !== 0) { 11422 //console.warn('queue order wrong', i); 11423 return; 11424 } 11425 return queueForElement(element).shift(); 11426 }, 11427 11428 check: function() { 11429 // next 11430 var element = this.nextElement(); 11431 if (element) { 11432 element.__queue.check.call(element); 11433 } 11434 if (this.canReady()) { 11435 this.ready(); 11436 return true; 11437 } 11438 }, 11439 11440 nextElement: function() { 11441 return nextQueued(); 11442 }, 11443 11444 canReady: function() { 11445 return !this.waitToReady && this.isEmpty(); 11446 }, 11447 11448 isEmpty: function() { 11449 for (var i=0, l=elements.length, e; (i<l) && 11450 (e=elements[i]); i++) { 11451 if (e.__queue && !e.__queue.flushable) { 11452 return; 11453 } 11454 } 11455 return true; 11456 }, 11457 11458 addToFlushQueue: function(element) { 11459 flushQueue.push(element); 11460 }, 11461 11462 flush: function() { 11463 // prevent re-entrance 11464 if (this.flushing) { 11465 return; 11466 } 11467 this.flushing = true; 11468 var element; 11469 while (flushQueue.length) { 11470 element = flushQueue.shift(); 11471 element.__queue.go.call(element); 11472 element.__queue = null; 11473 } 11474 this.flushing = false; 11475 }, 11476 11477 ready: function() { 11478 // TODO(sorvell): As an optimization, turn off CE polyfill upgrading 11479 // while registering. This way we avoid having to upgrade each document 11480 // piecemeal per registration and can instead register all elements 11481 // and upgrade once in a batch. Without this optimization, upgrade time 11482 // degrades significantly when SD polyfill is used. This is mainly because 11483 // querying the document tree for elements is slow under the SD polyfill. 11484 var polyfillWasReady = CustomElements.ready; 11485 CustomElements.ready = false; 11486 this.flush(); 11487 if (!CustomElements.useNative) { 11488 CustomElements.upgradeDocumentTree(document); 11489 } 11490 CustomElements.ready = polyfillWasReady; 11491 Polymer.flush(); 11492 requestAnimationFrame(this.flushReadyCallbacks); 11493 }, 11494 11495 addReadyCallback: function(callback) { 11496 if (callback) { 11497 readyCallbacks.push(callback); 11498 } 11499 }, 11500 11501 flushReadyCallbacks: function() { 11502 if (readyCallbacks) { 11503 var fn; 11504 while (readyCallbacks.length) { 11505 fn = readyCallbacks.shift(); 11506 fn(); 11507 } 11508 } 11509 }, 11510 11511 /** 11512 Returns a list of elements that have had polymer-elements created but 11513 are not yet ready to register. The list is an array of element definitions. 11514 */ 11515 waitingFor: function() { 11516 var e$ = []; 11517 for (var i=0, l=elements.length, e; (i<l) && 11518 (e=elements[i]); i++) { 11519 if (e.__queue && !e.__queue.flushable) { 11520 e$.push(e); 11521 } 11522 } 11523 return e$; 11524 }, 11525 11526 waitToReady: true 11527 11528 }; 11529 11530 var elements = []; 11531 var flushQueue = []; 11532 var importQueue = []; 11533 var mainQueue = []; 11534 var readyCallbacks = []; 11535 11536 function queueForElement(element) { 11537 return document.contains(element) ? mainQueue : importQueue; 11538 } 11539 11540 function nextQueued() { 11541 return importQueue.length ? importQueue[0] : mainQueue[0]; 11542 } 11543 11544 function whenReady(callback) { 11545 queue.waitToReady = true; 11546 Polymer.endOfMicrotask(function() { 11547 HTMLImports.whenReady(function() { 11548 queue.addReadyCallback(callback); 11549 queue.waitToReady = false; 11550 queue.check(); 11551 }); 11552 }); 11553 } 11554 11555 /** 11556 Forces polymer to register any pending elements. Can be used to abort 11557 waiting for elements that are partially defined. 11558 @param timeout {Integer} Optional timeout in milliseconds 11559 */ 11560 function forceReady(timeout) { 11561 if (timeout === undefined) { 11562 queue.ready(); 11563 return; 11564 } 11565 var handle = setTimeout(function() { 11566 queue.ready(); 11567 }, timeout); 11568 Polymer.whenReady(function() { 11569 clearTimeout(handle); 11570 }); 11571 } 11572 11573 // exports 11574 scope.elements = elements; 11575 scope.waitingFor = queue.waitingFor.bind(queue); 11576 scope.forceReady = forceReady; 11577 scope.queue = queue; 11578 scope.whenReady = scope.whenPolymerReady = whenReady; 11579 })(Polymer); 11580 11581 (function(scope) { 11582 11583 // imports 11584 11585 var extend = scope.extend; 11586 var api = scope.api; 11587 var queue = scope.queue; 11588 var whenReady = scope.whenReady; 11589 var getRegisteredPrototype = scope.getRegisteredPrototype; 11590 var waitingForPrototype = scope.waitingForPrototype; 11591 11592 // declarative implementation: <polymer-element> 11593 11594 var prototype = extend(Object.create(HTMLElement.prototype), { 11595 11596 createdCallback: function() { 11597 if (this.getAttribute('name')) { 11598 this.init(); 11599 } 11600 }, 11601 11602 init: function() { 11603 // fetch declared values 11604 this.name = this.getAttribute('name'); 11605 this.extends = this.getAttribute('extends'); 11606 queue.wait(this); 11607 // initiate any async resource fetches 11608 this.loadResources(); 11609 // register when all constraints are met 11610 this.registerWhenReady(); 11611 }, 11612 11613 // TODO(sorvell): we currently queue in the order the prototypes are 11614 // registered, but we should queue in the order that polymer-elements 11615 // are registered. We are currently blocked from doing this based on 11616 // crbug.com/395686. 11617 registerWhenReady: function() { 11618 if (this.registered 11619 || this.waitingForPrototype(this.name) 11620 || this.waitingForQueue() 11621 || this.waitingForResources()) { 11622 return; 11623 } 11624 queue.go(this); 11625 }, 11626 11627 _register: function() { 11628 //console.log('registering', this.name); 11629 // warn if extending from a custom element not registered via Polymer 11630 if (isCustomTag(this.extends) && !isRegistered(this.extends)) { 11631 console.warn('%s is attempting to extend %s, an unregistered element ' + 11632 'or one that was not registered with Polymer.', this.name, 11633 this.extends); 11634 } 11635 this.register(this.name, this.extends); 11636 this.registered = true; 11637 }, 11638 11639 waitingForPrototype: function(name) { 11640 if (!getRegisteredPrototype(name)) { 11641 // then wait for a prototype 11642 waitingForPrototype(name, this); 11643 // emulate script if user is not supplying one 11644 this.handleNoScript(name); 11645 // prototype not ready yet 11646 return true; 11647 } 11648 }, 11649 11650 handleNoScript: function(name) { 11651 // if explicitly marked as 'noscript' 11652 if (this.hasAttribute('noscript') && !this.noscript) { 11653 this.noscript = true; 11654 // imperative element registration 11655 Polymer(name); 11656 } 11657 }, 11658 11659 waitingForResources: function() { 11660 return this._needsResources; 11661 }, 11662 11663 // NOTE: Elements must be queued in proper order for inheritance/composition 11664 // dependency resolution. Previously this was enforced for inheritance, 11665 // and by rule for composition. It's now entirely by rule. 11666 waitingForQueue: function() { 11667 return queue.enqueue(this, this.registerWhenReady, this._register); 11668 }, 11669 11670 loadResources: function() { 11671 this._needsResources = true; 11672 this.loadStyles(function() { 11673 this._needsResources = false; 11674 this.registerWhenReady(); 11675 }.bind(this)); 11676 } 11677 11678 }); 11679 11680 // semi-pluggable APIs 11681 11682 // TODO(sjmiles): should be fully pluggable (aka decoupled, currently 11683 // the various plugins are allowed to depend on each other directly) 11684 api.publish(api.declaration, prototype); 11685 11686 // utility and bookkeeping 11687 11688 function isRegistered(name) { 11689 return Boolean(HTMLElement.getPrototypeForTag(name)); 11690 } 11691 11692 function isCustomTag(name) { 11693 return (name && name.indexOf('-') >= 0); 11694 } 11695 11696 // boot tasks 11697 11698 whenReady(function() { 11699 document.body.removeAttribute('unresolved'); 11700 document.dispatchEvent( 11701 new CustomEvent('polymer-ready', {bubbles: true}) 11702 ); 11703 }); 11704 11705 // register polymer-element with document 11706 11707 document.registerElement('polymer-element', {prototype: prototype}); 11708 11709 })(Polymer); 11710 11711 (function(scope) { 11712 11713 /** 11714 * @class Polymer 11715 */ 11716 11717 var whenReady = scope.whenReady; 11718 11719 /** 11720 * Loads the set of HTMLImports contained in `node`. Notifies when all 11721 * the imports have loaded by calling the `callback` function argument. 11722 * This method can be used to lazily load imports. For example, given a 11723 * template: 11724 * 11725 * <template> 11726 * <link rel="import" href="my-import1.html"> 11727 * <link rel="import" href="my-import2.html"> 11728 * </template> 11729 * 11730 * Polymer.importElements(template.content, function() { 11731 * console.log('imports lazily loaded'); 11732 * }); 11733 * 11734 * @method importElements 11735 * @param {Node} node Node containing the HTMLImports to load. 11736 * @param {Function} callback Callback called when all imports have loaded. 11737 */ 11738 function importElements(node, callback) { 11739 if (node) { 11740 document.head.appendChild(node); 11741 whenReady(callback); 11742 } else if (callback) { 11743 callback(); 11744 } 11745 } 11746 11747 /** 11748 * Loads an HTMLImport for each url specified in the `urls` array. 11749 * Notifies when all the imports have loaded by calling the `callback` 11750 * function argument. This method can be used to lazily load imports. 11751 * For example, 11752 * 11753 * Polymer.import(['my-import1.html', 'my-import2.html'], function() { 11754 * console.log('imports lazily loaded'); 11755 * }); 11756 * 11757 * @method import 11758 * @param {Array} urls Array of urls to load as HTMLImports. 11759 * @param {Function} callback Callback called when all imports have loaded. 11760 */ 11761 function _import(urls, callback) { 11762 if (urls && urls.length) { 11763 var frag = document.createDocumentFragment(); 11764 for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) { 11765 link = document.createElement('link'); 11766 link.rel = 'import'; 11767 link.href = url; 11768 frag.appendChild(link); 11769 } 11770 importElements(frag, callback); 11771 } else if (callback) { 11772 callback(); 11773 } 11774 } 11775 11776 // exports 11777 scope.import = _import; 11778 scope.importElements = importElements; 11779 11780 })(Polymer); 11781 11782 /** 11783 * The `auto-binding` element extends the template element. It provides a quick 11784 * and easy way to do data binding without the need to setup a model. 11785 * The `auto-binding` element itself serves as the model and controller for the 11786 * elements it contains. Both data and event handlers can be bound. 11787 * 11788 * The `auto-binding` element acts just like a template that is bound to 11789 * a model. It stamps its content in the dom adjacent to itself. When the 11790 * content is stamped, the `template-bound` event is fired. 11791 * 11792 * Example: 11793 * 11794 * <template is="auto-binding"> 11795 * <div>Say something: <input value="{{value}}"></div> 11796 * <div>You said: {{value}}</div> 11797 * <button on-tap="{{buttonTap}}">Tap me!</button> 11798 * </template> 11799 * <script> 11800 * var template = document.querySelector('template'); 11801 * template.value = 'something'; 11802 * template.buttonTap = function() { 11803 * console.log('tap!'); 11804 * }; 11805 * </script> 11806 * 11807 * @module Polymer 11808 * @status stable 11809 */ 11810 11811 (function() { 11812 11813 var element = document.createElement('polymer-element'); 11814 element.setAttribute('name', 'auto-binding'); 11815 element.setAttribute('extends', 'template'); 11816 element.init(); 11817 11818 Polymer('auto-binding', { 11819 11820 createdCallback: function() { 11821 this.syntax = this.bindingDelegate = this.makeSyntax(); 11822 // delay stamping until polymer-ready so that auto-binding is not 11823 // required to load last. 11824 Polymer.whenPolymerReady(function() { 11825 this.model = this; 11826 this.setAttribute('bind', ''); 11827 // we don't bother with an explicit signal here, we could ust a MO 11828 // if necessary 11829 this.async(function() { 11830 // note: this will marshall *all* the elements in the parentNode 11831 // rather than just stamped ones. We'd need to use createInstance 11832 // to fix this or something else fancier. 11833 this.marshalNodeReferences(this.parentNode); 11834 // template stamping is asynchronous so stamping isn't complete 11835 // by polymer-ready; fire an event so users can use stamped elements 11836 this.fire('template-bound'); 11837 }); 11838 }.bind(this)); 11839 }, 11840 11841 makeSyntax: function() { 11842 var events = Object.create(Polymer.api.declaration.events); 11843 var self = this; 11844 events.findController = function() { return self.model; }; 11845 11846 var syntax = new PolymerExpressions(); 11847 var prepareBinding = syntax.prepareBinding; 11848 syntax.prepareBinding = function(pathString, name, node) { 11849 return events.prepareEventBinding(pathString, name, node) || 11850 prepareBinding.call(syntax, pathString, name, node); 11851 }; 11852 return syntax; 11853 } 11854 11855 }); 11856 11857 })(); 11858