OLD | NEW |
(Empty) | |
| 1 /** |
| 2 * @license |
| 3 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 4 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 5 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 6 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 7 * Code distributed by Google as part of the polymer project is also |
| 8 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 9 */ |
| 10 // @version 0.5.1 |
| 11 window.PolymerGestures = {}; |
| 12 |
| 13 (function(scope) { |
| 14 var HAS_FULL_PATH = 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 HAS_FULL_PATH = ev.path[0] === s; |
| 26 } |
| 27 ev.stopPropagation(); |
| 28 }); |
| 29 var ev = new CustomEvent('testpath', {bubbles: true}); |
| 30 // must add node to DOM to trigger event listener |
| 31 document.head.appendChild(pathTest); |
| 32 s.dispatchEvent(ev); |
| 33 pathTest.parentNode.removeChild(pathTest); |
| 34 sr = s = null; |
| 35 } |
| 36 pathTest = null; |
| 37 |
| 38 var target = { |
| 39 shadow: function(inEl) { |
| 40 if (inEl) { |
| 41 return inEl.shadowRoot || inEl.webkitShadowRoot; |
| 42 } |
| 43 }, |
| 44 canTarget: function(shadow) { |
| 45 return shadow && Boolean(shadow.elementFromPoint); |
| 46 }, |
| 47 targetingShadow: function(inEl) { |
| 48 var s = this.shadow(inEl); |
| 49 if (this.canTarget(s)) { |
| 50 return s; |
| 51 } |
| 52 }, |
| 53 olderShadow: function(shadow) { |
| 54 var os = shadow.olderShadowRoot; |
| 55 if (!os) { |
| 56 var se = shadow.querySelector('shadow'); |
| 57 if (se) { |
| 58 os = se.olderShadowRoot; |
| 59 } |
| 60 } |
| 61 return os; |
| 62 }, |
| 63 allShadows: function(element) { |
| 64 var shadows = [], s = this.shadow(element); |
| 65 while(s) { |
| 66 shadows.push(s); |
| 67 s = this.olderShadow(s); |
| 68 } |
| 69 return shadows; |
| 70 }, |
| 71 searchRoot: function(inRoot, x, y) { |
| 72 var t, st, sr, os; |
| 73 if (inRoot) { |
| 74 t = inRoot.elementFromPoint(x, y); |
| 75 if (t) { |
| 76 // found element, check if it has a ShadowRoot |
| 77 sr = this.targetingShadow(t); |
| 78 } else if (inRoot !== document) { |
| 79 // check for sibling roots |
| 80 sr = this.olderShadow(inRoot); |
| 81 } |
| 82 // search other roots, fall back to light dom element |
| 83 return this.searchRoot(sr, x, y) || t; |
| 84 } |
| 85 }, |
| 86 owner: function(element) { |
| 87 if (!element) { |
| 88 return document; |
| 89 } |
| 90 var s = element; |
| 91 // walk up until you hit the shadow root or document |
| 92 while (s.parentNode) { |
| 93 s = s.parentNode; |
| 94 } |
| 95 // the owner element is expected to be a Document or ShadowRoot |
| 96 if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGME
NT_NODE) { |
| 97 s = document; |
| 98 } |
| 99 return s; |
| 100 }, |
| 101 findTarget: function(inEvent) { |
| 102 if (HAS_FULL_PATH && 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 (HAS_FULL_PATH && inEvent.path && inEvent.path.length) { |
| 117 var path = inEvent.path; |
| 118 for (var i = 0; i < path.length; i++) { |
| 119 n = path[i]; |
| 120 if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')
) { |
| 121 return n.getAttribute('touch-action'); |
| 122 } |
| 123 } |
| 124 } else { |
| 125 n = inEvent.target; |
| 126 while(n) { |
| 127 if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')
) { |
| 128 return n.getAttribute('touch-action'); |
| 129 } |
| 130 n = n.parentNode || n.host; |
| 131 } |
| 132 } |
| 133 // auto is default |
| 134 return "auto"; |
| 135 }, |
| 136 LCA: function(a, b) { |
| 137 if (a === b) { |
| 138 return a; |
| 139 } |
| 140 if (a && !b) { |
| 141 return a; |
| 142 } |
| 143 if (b && !a) { |
| 144 return b; |
| 145 } |
| 146 if (!b && !a) { |
| 147 return document; |
| 148 } |
| 149 // fast case, a is a direct descendant of b or vice versa |
| 150 if (a.contains && a.contains(b)) { |
| 151 return a; |
| 152 } |
| 153 if (b.contains && b.contains(a)) { |
| 154 return b; |
| 155 } |
| 156 var adepth = this.depth(a); |
| 157 var bdepth = this.depth(b); |
| 158 var d = adepth - bdepth; |
| 159 if (d >= 0) { |
| 160 a = this.walk(a, d); |
| 161 } else { |
| 162 b = this.walk(b, -d); |
| 163 } |
| 164 while (a && b && a !== b) { |
| 165 a = a.parentNode || a.host; |
| 166 b = b.parentNode || b.host; |
| 167 } |
| 168 return a; |
| 169 }, |
| 170 walk: function(n, u) { |
| 171 for (var i = 0; n && (i < u); i++) { |
| 172 n = n.parentNode || n.host; |
| 173 } |
| 174 return n; |
| 175 }, |
| 176 depth: function(n) { |
| 177 var d = 0; |
| 178 while(n) { |
| 179 d++; |
| 180 n = n.parentNode || n.host; |
| 181 } |
| 182 return d; |
| 183 }, |
| 184 deepContains: function(a, b) { |
| 185 var common = this.LCA(a, b); |
| 186 // if a is the common ancestor, it must "deeply" contain b |
| 187 return common === a; |
| 188 }, |
| 189 insideNode: function(node, x, y) { |
| 190 var rect = node.getBoundingClientRect(); |
| 191 return (rect.left <= x) && (x <= rect.right) && (rect.top <= y) && (y <= r
ect.bottom); |
| 192 }, |
| 193 path: function(event) { |
| 194 var p; |
| 195 if (HAS_FULL_PATH && event.path && event.path.length) { |
| 196 p = event.path; |
| 197 } else { |
| 198 p = []; |
| 199 var n = this.findTarget(event); |
| 200 while (n) { |
| 201 p.push(n); |
| 202 n = n.parentNode || n.host; |
| 203 } |
| 204 } |
| 205 return p; |
| 206 } |
| 207 }; |
| 208 scope.targetFinding = target; |
| 209 /** |
| 210 * Given an event, finds the "deepest" node that could have been the original
target before ShadowDOM retargetting |
| 211 * |
| 212 * @param {Event} Event An event object with clientX and clientY properties |
| 213 * @return {Element} The probable event origninator |
| 214 */ |
| 215 scope.findTarget = target.findTarget.bind(target); |
| 216 /** |
| 217 * Determines if the "container" node deeply contains the "containee" node, in
cluding situations where the "containee" is contained by one or more ShadowDOM |
| 218 * roots. |
| 219 * |
| 220 * @param {Node} container |
| 221 * @param {Node} containee |
| 222 * @return {Boolean} |
| 223 */ |
| 224 scope.deepContains = target.deepContains.bind(target); |
| 225 |
| 226 /** |
| 227 * Determines if the x/y position is inside the given node. |
| 228 * |
| 229 * Example: |
| 230 * |
| 231 * function upHandler(event) { |
| 232 * var innode = PolymerGestures.insideNode(event.target, event.clientX,
event.clientY); |
| 233 * if (innode) { |
| 234 * // wait for tap? |
| 235 * } else { |
| 236 * // tap will never happen |
| 237 * } |
| 238 * } |
| 239 * |
| 240 * @param {Node} node |
| 241 * @param {Number} x Screen X position |
| 242 * @param {Number} y screen Y position |
| 243 * @return {Boolean} |
| 244 */ |
| 245 scope.insideNode = target.insideNode; |
| 246 |
| 247 })(window.PolymerGestures); |
| 248 |
| 249 (function() { |
| 250 function shadowSelector(v) { |
| 251 return 'html /deep/ ' + selector(v); |
| 252 } |
| 253 function selector(v) { |
| 254 return '[touch-action="' + v + '"]'; |
| 255 } |
| 256 function rule(v) { |
| 257 return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + ';}'; |
| 258 } |
| 259 var attrib2css = [ |
| 260 'none', |
| 261 'auto', |
| 262 'pan-x', |
| 263 'pan-y', |
| 264 { |
| 265 rule: 'pan-x pan-y', |
| 266 selectors: [ |
| 267 'pan-x pan-y', |
| 268 'pan-y pan-x' |
| 269 ] |
| 270 }, |
| 271 'manipulation' |
| 272 ]; |
| 273 var styles = ''; |
| 274 // only install stylesheet if the browser has touch action support |
| 275 var hasTouchAction = typeof document.head.style.touchAction === 'string'; |
| 276 // only add shadow selectors if shadowdom is supported |
| 277 var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoo
t; |
| 278 |
| 279 if (hasTouchAction) { |
| 280 attrib2css.forEach(function(r) { |
| 281 if (String(r) === r) { |
| 282 styles += selector(r) + rule(r) + '\n'; |
| 283 if (hasShadowRoot) { |
| 284 styles += shadowSelector(r) + rule(r) + '\n'; |
| 285 } |
| 286 } else { |
| 287 styles += r.selectors.map(selector) + rule(r.rule) + '\n'; |
| 288 if (hasShadowRoot) { |
| 289 styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; |
| 290 } |
| 291 } |
| 292 }); |
| 293 |
| 294 var el = document.createElement('style'); |
| 295 el.textContent = styles; |
| 296 document.head.appendChild(el); |
| 297 } |
| 298 })(); |
| 299 |
| 300 /** |
| 301 * This is the constructor for new PointerEvents. |
| 302 * |
| 303 * New Pointer Events must be given a type, and an optional dictionary of |
| 304 * initialization properties. |
| 305 * |
| 306 * Due to certain platform requirements, events returned from the constructor |
| 307 * identify as MouseEvents. |
| 308 * |
| 309 * @constructor |
| 310 * @param {String} inType The type of the event to create. |
| 311 * @param {Object} [inDict] An optional dictionary of initial event properties. |
| 312 * @return {Event} A new PointerEvent of type `inType` and initialized with prop
erties from `inDict`. |
| 313 */ |
| 314 (function(scope) { |
| 315 |
| 316 var MOUSE_PROPS = [ |
| 317 'bubbles', |
| 318 'cancelable', |
| 319 'view', |
| 320 'detail', |
| 321 'screenX', |
| 322 'screenY', |
| 323 'clientX', |
| 324 'clientY', |
| 325 'ctrlKey', |
| 326 'altKey', |
| 327 'shiftKey', |
| 328 'metaKey', |
| 329 'button', |
| 330 'relatedTarget', |
| 331 'pageX', |
| 332 'pageY' |
| 333 ]; |
| 334 |
| 335 var MOUSE_DEFAULTS = [ |
| 336 false, |
| 337 false, |
| 338 null, |
| 339 null, |
| 340 0, |
| 341 0, |
| 342 0, |
| 343 0, |
| 344 false, |
| 345 false, |
| 346 false, |
| 347 false, |
| 348 0, |
| 349 null, |
| 350 0, |
| 351 0 |
| 352 ]; |
| 353 |
| 354 var NOP_FACTORY = function(){ return function(){}; }; |
| 355 |
| 356 var eventFactory = { |
| 357 // TODO(dfreedm): this is overridden by tap recognizer, needs review |
| 358 preventTap: NOP_FACTORY, |
| 359 makeBaseEvent: function(inType, inDict) { |
| 360 var e = document.createEvent('Event'); |
| 361 e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); |
| 362 e.preventTap = eventFactory.preventTap(e); |
| 363 return e; |
| 364 }, |
| 365 makeGestureEvent: function(inType, inDict) { |
| 366 inDict = inDict || Object.create(null); |
| 367 |
| 368 var e = this.makeBaseEvent(inType, inDict); |
| 369 for (var i = 0, keys = Object.keys(inDict), k; i < keys.length; i++) { |
| 370 k = keys[i]; |
| 371 e[k] = inDict[k]; |
| 372 } |
| 373 return e; |
| 374 }, |
| 375 makePointerEvent: function(inType, inDict) { |
| 376 inDict = inDict || Object.create(null); |
| 377 |
| 378 var e = this.makeBaseEvent(inType, inDict); |
| 379 // define inherited MouseEvent properties |
| 380 for(var i = 0, p; i < MOUSE_PROPS.length; i++) { |
| 381 p = MOUSE_PROPS[i]; |
| 382 e[p] = inDict[p] || MOUSE_DEFAULTS[i]; |
| 383 } |
| 384 e.buttons = inDict.buttons || 0; |
| 385 |
| 386 // Spec requires that pointers without pressure specified use 0.5 for down |
| 387 // state and 0 for up state. |
| 388 var pressure = 0; |
| 389 if (inDict.pressure) { |
| 390 pressure = inDict.pressure; |
| 391 } else { |
| 392 pressure = e.buttons ? 0.5 : 0; |
| 393 } |
| 394 |
| 395 // add x/y properties aliased to clientX/Y |
| 396 e.x = e.clientX; |
| 397 e.y = e.clientY; |
| 398 |
| 399 // define the properties of the PointerEvent interface |
| 400 e.pointerId = inDict.pointerId || 0; |
| 401 e.width = inDict.width || 0; |
| 402 e.height = inDict.height || 0; |
| 403 e.pressure = pressure; |
| 404 e.tiltX = inDict.tiltX || 0; |
| 405 e.tiltY = inDict.tiltY || 0; |
| 406 e.pointerType = inDict.pointerType || ''; |
| 407 e.hwTimestamp = inDict.hwTimestamp || 0; |
| 408 e.isPrimary = inDict.isPrimary || false; |
| 409 e._source = inDict._source || ''; |
| 410 return e; |
| 411 } |
| 412 }; |
| 413 |
| 414 scope.eventFactory = eventFactory; |
| 415 })(window.PolymerGestures); |
| 416 |
| 417 /** |
| 418 * This module implements an map of pointer states |
| 419 */ |
| 420 (function(scope) { |
| 421 var USE_MAP = window.Map && window.Map.prototype.forEach; |
| 422 var POINTERS_FN = function(){ return this.size; }; |
| 423 function PointerMap() { |
| 424 if (USE_MAP) { |
| 425 var m = new Map(); |
| 426 m.pointers = POINTERS_FN; |
| 427 return m; |
| 428 } else { |
| 429 this.keys = []; |
| 430 this.values = []; |
| 431 } |
| 432 } |
| 433 |
| 434 PointerMap.prototype = { |
| 435 set: function(inId, inEvent) { |
| 436 var i = this.keys.indexOf(inId); |
| 437 if (i > -1) { |
| 438 this.values[i] = inEvent; |
| 439 } else { |
| 440 this.keys.push(inId); |
| 441 this.values.push(inEvent); |
| 442 } |
| 443 }, |
| 444 has: function(inId) { |
| 445 return this.keys.indexOf(inId) > -1; |
| 446 }, |
| 447 'delete': function(inId) { |
| 448 var i = this.keys.indexOf(inId); |
| 449 if (i > -1) { |
| 450 this.keys.splice(i, 1); |
| 451 this.values.splice(i, 1); |
| 452 } |
| 453 }, |
| 454 get: function(inId) { |
| 455 var i = this.keys.indexOf(inId); |
| 456 return this.values[i]; |
| 457 }, |
| 458 clear: function() { |
| 459 this.keys.length = 0; |
| 460 this.values.length = 0; |
| 461 }, |
| 462 // return value, key, map |
| 463 forEach: function(callback, thisArg) { |
| 464 this.values.forEach(function(v, i) { |
| 465 callback.call(thisArg, v, this.keys[i], this); |
| 466 }, this); |
| 467 }, |
| 468 pointers: function() { |
| 469 return this.keys.length; |
| 470 } |
| 471 }; |
| 472 |
| 473 scope.PointerMap = PointerMap; |
| 474 })(window.PolymerGestures); |
| 475 |
| 476 (function(scope) { |
| 477 var CLONE_PROPS = [ |
| 478 // MouseEvent |
| 479 'bubbles', |
| 480 'cancelable', |
| 481 'view', |
| 482 'detail', |
| 483 'screenX', |
| 484 'screenY', |
| 485 'clientX', |
| 486 'clientY', |
| 487 'ctrlKey', |
| 488 'altKey', |
| 489 'shiftKey', |
| 490 'metaKey', |
| 491 'button', |
| 492 'relatedTarget', |
| 493 // DOM Level 3 |
| 494 'buttons', |
| 495 // PointerEvent |
| 496 'pointerId', |
| 497 'width', |
| 498 'height', |
| 499 'pressure', |
| 500 'tiltX', |
| 501 'tiltY', |
| 502 'pointerType', |
| 503 'hwTimestamp', |
| 504 'isPrimary', |
| 505 // event instance |
| 506 'type', |
| 507 'target', |
| 508 'currentTarget', |
| 509 'which', |
| 510 'pageX', |
| 511 'pageY', |
| 512 'timeStamp', |
| 513 // gesture addons |
| 514 'preventTap', |
| 515 'tapPrevented', |
| 516 '_source' |
| 517 ]; |
| 518 |
| 519 var CLONE_DEFAULTS = [ |
| 520 // MouseEvent |
| 521 false, |
| 522 false, |
| 523 null, |
| 524 null, |
| 525 0, |
| 526 0, |
| 527 0, |
| 528 0, |
| 529 false, |
| 530 false, |
| 531 false, |
| 532 false, |
| 533 0, |
| 534 null, |
| 535 // DOM Level 3 |
| 536 0, |
| 537 // PointerEvent |
| 538 0, |
| 539 0, |
| 540 0, |
| 541 0, |
| 542 0, |
| 543 0, |
| 544 '', |
| 545 0, |
| 546 false, |
| 547 // event instance |
| 548 '', |
| 549 null, |
| 550 null, |
| 551 0, |
| 552 0, |
| 553 0, |
| 554 0, |
| 555 function(){}, |
| 556 false |
| 557 ]; |
| 558 |
| 559 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); |
| 560 |
| 561 var eventFactory = scope.eventFactory; |
| 562 |
| 563 // set of recognizers to run for the currently handled event |
| 564 var currentGestures; |
| 565 |
| 566 /** |
| 567 * This module is for normalizing events. Mouse and Touch events will be |
| 568 * collected here, and fire PointerEvents that have the same semantics, no |
| 569 * matter the source. |
| 570 * Events fired: |
| 571 * - pointerdown: a pointing is added |
| 572 * - pointerup: a pointer is removed |
| 573 * - pointermove: a pointer is moved |
| 574 * - pointerover: a pointer crosses into an element |
| 575 * - pointerout: a pointer leaves an element |
| 576 * - pointercancel: a pointer will no longer generate events |
| 577 */ |
| 578 var dispatcher = { |
| 579 IS_IOS: false, |
| 580 pointermap: new scope.PointerMap(), |
| 581 requiredGestures: new scope.PointerMap(), |
| 582 eventMap: Object.create(null), |
| 583 // Scope objects for native events. |
| 584 // This exists for ease of testing. |
| 585 eventSources: Object.create(null), |
| 586 eventSourceList: [], |
| 587 gestures: [], |
| 588 // map gesture event -> {listeners: int, index: gestures[int]} |
| 589 dependencyMap: { |
| 590 // make sure down and up are in the map to trigger "register" |
| 591 down: {listeners: 0, index: -1}, |
| 592 up: {listeners: 0, index: -1} |
| 593 }, |
| 594 gestureQueue: [], |
| 595 /** |
| 596 * Add a new event source that will generate pointer events. |
| 597 * |
| 598 * `inSource` must contain an array of event names named `events`, and |
| 599 * functions with the names specified in the `events` array. |
| 600 * @param {string} name A name for the event source |
| 601 * @param {Object} source A new source of platform events. |
| 602 */ |
| 603 registerSource: function(name, source) { |
| 604 var s = source; |
| 605 var newEvents = s.events; |
| 606 if (newEvents) { |
| 607 newEvents.forEach(function(e) { |
| 608 if (s[e]) { |
| 609 this.eventMap[e] = s[e].bind(s); |
| 610 } |
| 611 }, this); |
| 612 this.eventSources[name] = s; |
| 613 this.eventSourceList.push(s); |
| 614 } |
| 615 }, |
| 616 registerGesture: function(name, source) { |
| 617 var obj = Object.create(null); |
| 618 obj.listeners = 0; |
| 619 obj.index = this.gestures.length; |
| 620 for (var i = 0, g; i < source.exposes.length; i++) { |
| 621 g = source.exposes[i].toLowerCase(); |
| 622 this.dependencyMap[g] = obj; |
| 623 } |
| 624 this.gestures.push(source); |
| 625 }, |
| 626 register: function(element, initial) { |
| 627 var l = this.eventSourceList.length; |
| 628 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
| 629 // call eventsource register |
| 630 es.register.call(es, element, initial); |
| 631 } |
| 632 }, |
| 633 unregister: function(element) { |
| 634 var l = this.eventSourceList.length; |
| 635 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
| 636 // call eventsource register |
| 637 es.unregister.call(es, element); |
| 638 } |
| 639 }, |
| 640 // EVENTS |
| 641 down: function(inEvent) { |
| 642 this.requiredGestures.set(inEvent.pointerId, currentGestures); |
| 643 this.fireEvent('down', inEvent); |
| 644 }, |
| 645 move: function(inEvent) { |
| 646 // pipe move events into gesture queue directly |
| 647 inEvent.type = 'move'; |
| 648 this.fillGestureQueue(inEvent); |
| 649 }, |
| 650 up: function(inEvent) { |
| 651 this.fireEvent('up', inEvent); |
| 652 this.requiredGestures.delete(inEvent.pointerId); |
| 653 }, |
| 654 cancel: function(inEvent) { |
| 655 inEvent.tapPrevented = true; |
| 656 this.fireEvent('up', inEvent); |
| 657 this.requiredGestures.delete(inEvent.pointerId); |
| 658 }, |
| 659 addGestureDependency: function(node, currentGestures) { |
| 660 var gesturesWanted = node._pgEvents; |
| 661 if (gesturesWanted && currentGestures) { |
| 662 var gk = Object.keys(gesturesWanted); |
| 663 for (var i = 0, r, ri, g; i < gk.length; i++) { |
| 664 // gesture |
| 665 g = gk[i]; |
| 666 if (gesturesWanted[g] > 0) { |
| 667 // lookup gesture recognizer |
| 668 r = this.dependencyMap[g]; |
| 669 // recognizer index |
| 670 ri = r ? r.index : -1; |
| 671 currentGestures[ri] = true; |
| 672 } |
| 673 } |
| 674 } |
| 675 }, |
| 676 // LISTENER LOGIC |
| 677 eventHandler: function(inEvent) { |
| 678 // This is used to prevent multiple dispatch of events from |
| 679 // platform events. This can happen when two elements in different scopes |
| 680 // are set up to create pointer events, which is relevant to Shadow DOM. |
| 681 |
| 682 var type = inEvent.type; |
| 683 |
| 684 // only generate the list of desired events on "down" |
| 685 if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown
' || type === 'MSPointerDown') { |
| 686 if (!inEvent._handledByPG) { |
| 687 currentGestures = {}; |
| 688 } |
| 689 |
| 690 // in IOS mode, there is only a listener on the document, so this is not
re-entrant |
| 691 if (this.IS_IOS) { |
| 692 var ev = inEvent; |
| 693 if (type === 'touchstart') { |
| 694 var ct = inEvent.changedTouches[0]; |
| 695 // set up a fake event to give to the path builder |
| 696 ev = {target: inEvent.target, clientX: ct.clientX, clientY: ct.clien
tY, path: inEvent.path}; |
| 697 } |
| 698 // use event path if available, otherwise build a path from target fin
ding |
| 699 var nodes = inEvent.path || scope.targetFinding.path(ev); |
| 700 for (var i = 0, n; i < nodes.length; i++) { |
| 701 n = nodes[i]; |
| 702 this.addGestureDependency(n, currentGestures); |
| 703 } |
| 704 } else { |
| 705 this.addGestureDependency(inEvent.currentTarget, currentGestures); |
| 706 } |
| 707 } |
| 708 |
| 709 if (inEvent._handledByPG) { |
| 710 return; |
| 711 } |
| 712 var fn = this.eventMap && this.eventMap[type]; |
| 713 if (fn) { |
| 714 fn(inEvent); |
| 715 } |
| 716 inEvent._handledByPG = true; |
| 717 }, |
| 718 // set up event listeners |
| 719 listen: function(target, events) { |
| 720 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { |
| 721 this.addEvent(target, e); |
| 722 } |
| 723 }, |
| 724 // remove event listeners |
| 725 unlisten: function(target, events) { |
| 726 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { |
| 727 this.removeEvent(target, e); |
| 728 } |
| 729 }, |
| 730 addEvent: function(target, eventName) { |
| 731 target.addEventListener(eventName, this.boundHandler); |
| 732 }, |
| 733 removeEvent: function(target, eventName) { |
| 734 target.removeEventListener(eventName, this.boundHandler); |
| 735 }, |
| 736 // EVENT CREATION AND TRACKING |
| 737 /** |
| 738 * Creates a new Event of type `inType`, based on the information in |
| 739 * `inEvent`. |
| 740 * |
| 741 * @param {string} inType A string representing the type of event to create |
| 742 * @param {Event} inEvent A platform event with a target |
| 743 * @return {Event} A PointerEvent of type `inType` |
| 744 */ |
| 745 makeEvent: function(inType, inEvent) { |
| 746 var e = eventFactory.makePointerEvent(inType, inEvent); |
| 747 e.preventDefault = inEvent.preventDefault; |
| 748 e.tapPrevented = inEvent.tapPrevented; |
| 749 e._target = e._target || inEvent.target; |
| 750 return e; |
| 751 }, |
| 752 // make and dispatch an event in one call |
| 753 fireEvent: function(inType, inEvent) { |
| 754 var e = this.makeEvent(inType, inEvent); |
| 755 return this.dispatchEvent(e); |
| 756 }, |
| 757 /** |
| 758 * Returns a snapshot of inEvent, with writable properties. |
| 759 * |
| 760 * @param {Event} inEvent An event that contains properties to copy. |
| 761 * @return {Object} An object containing shallow copies of `inEvent`'s |
| 762 * properties. |
| 763 */ |
| 764 cloneEvent: function(inEvent) { |
| 765 var eventCopy = Object.create(null), p; |
| 766 for (var i = 0; i < CLONE_PROPS.length; i++) { |
| 767 p = CLONE_PROPS[i]; |
| 768 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; |
| 769 // Work around SVGInstanceElement shadow tree |
| 770 // Return the <use> element that is represented by the instance for Safa
ri, Chrome, IE. |
| 771 // This is the behavior implemented by Firefox. |
| 772 if (p === 'target' || p === 'relatedTarget') { |
| 773 if (HAS_SVG_INSTANCE && eventCopy[p] instanceof SVGElementInstance) { |
| 774 eventCopy[p] = eventCopy[p].correspondingUseElement; |
| 775 } |
| 776 } |
| 777 } |
| 778 // keep the semantics of preventDefault |
| 779 eventCopy.preventDefault = function() { |
| 780 inEvent.preventDefault(); |
| 781 }; |
| 782 return eventCopy; |
| 783 }, |
| 784 /** |
| 785 * Dispatches the event to its target. |
| 786 * |
| 787 * @param {Event} inEvent The event to be dispatched. |
| 788 * @return {Boolean} True if an event handler returns true, false otherwise. |
| 789 */ |
| 790 dispatchEvent: function(inEvent) { |
| 791 var t = inEvent._target; |
| 792 if (t) { |
| 793 t.dispatchEvent(inEvent); |
| 794 // clone the event for the gesture system to process |
| 795 // clone after dispatch to pick up gesture prevention code |
| 796 var clone = this.cloneEvent(inEvent); |
| 797 clone.target = t; |
| 798 this.fillGestureQueue(clone); |
| 799 } |
| 800 }, |
| 801 gestureTrigger: function() { |
| 802 // process the gesture queue |
| 803 for (var i = 0, e, rg; i < this.gestureQueue.length; i++) { |
| 804 e = this.gestureQueue[i]; |
| 805 rg = e._requiredGestures; |
| 806 if (rg) { |
| 807 for (var j = 0, g, fn; j < this.gestures.length; j++) { |
| 808 // only run recognizer if an element in the source event's path is l
istening for those gestures |
| 809 if (rg[j]) { |
| 810 g = this.gestures[j]; |
| 811 fn = g[e.type]; |
| 812 if (fn) { |
| 813 fn.call(g, e); |
| 814 } |
| 815 } |
| 816 } |
| 817 } |
| 818 } |
| 819 this.gestureQueue.length = 0; |
| 820 }, |
| 821 fillGestureQueue: function(ev) { |
| 822 // only trigger the gesture queue once |
| 823 if (!this.gestureQueue.length) { |
| 824 requestAnimationFrame(this.boundGestureTrigger); |
| 825 } |
| 826 ev._requiredGestures = this.requiredGestures.get(ev.pointerId); |
| 827 this.gestureQueue.push(ev); |
| 828 } |
| 829 }; |
| 830 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); |
| 831 dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); |
| 832 scope.dispatcher = dispatcher; |
| 833 |
| 834 /** |
| 835 * Listen for `gesture` on `node` with the `handler` function |
| 836 * |
| 837 * If `handler` is the first listener for `gesture`, the underlying gesture re
cognizer is then enabled. |
| 838 * |
| 839 * @param {Element} node |
| 840 * @param {string} gesture |
| 841 * @return Boolean `gesture` is a valid gesture |
| 842 */ |
| 843 scope.activateGesture = function(node, gesture) { |
| 844 var g = gesture.toLowerCase(); |
| 845 var dep = dispatcher.dependencyMap[g]; |
| 846 if (dep) { |
| 847 var recognizer = dispatcher.gestures[dep.index]; |
| 848 if (!node._pgListeners) { |
| 849 dispatcher.register(node); |
| 850 node._pgListeners = 0; |
| 851 } |
| 852 // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes |
| 853 if (recognizer) { |
| 854 var touchAction = recognizer.defaultActions && recognizer.defaultActions
[g]; |
| 855 var actionNode; |
| 856 switch(node.nodeType) { |
| 857 case Node.ELEMENT_NODE: |
| 858 actionNode = node; |
| 859 break; |
| 860 case Node.DOCUMENT_FRAGMENT_NODE: |
| 861 actionNode = node.host; |
| 862 break; |
| 863 default: |
| 864 actionNode = null; |
| 865 break; |
| 866 } |
| 867 if (touchAction && actionNode && !actionNode.hasAttribute('touch-action'
)) { |
| 868 actionNode.setAttribute('touch-action', touchAction); |
| 869 } |
| 870 } |
| 871 if (!node._pgEvents) { |
| 872 node._pgEvents = {}; |
| 873 } |
| 874 node._pgEvents[g] = (node._pgEvents[g] || 0) + 1; |
| 875 node._pgListeners++; |
| 876 } |
| 877 return Boolean(dep); |
| 878 }; |
| 879 |
| 880 /** |
| 881 * |
| 882 * Listen for `gesture` from `node` with `handler` function. |
| 883 * |
| 884 * @param {Element} node |
| 885 * @param {string} gesture |
| 886 * @param {Function} handler |
| 887 * @param {Boolean} capture |
| 888 */ |
| 889 scope.addEventListener = function(node, gesture, handler, capture) { |
| 890 if (handler) { |
| 891 scope.activateGesture(node, gesture); |
| 892 node.addEventListener(gesture, handler, capture); |
| 893 } |
| 894 }; |
| 895 |
| 896 /** |
| 897 * Tears down the gesture configuration for `node` |
| 898 * |
| 899 * If `handler` is the last listener for `gesture`, the underlying gesture rec
ognizer is disabled. |
| 900 * |
| 901 * @param {Element} node |
| 902 * @param {string} gesture |
| 903 * @return Boolean `gesture` is a valid gesture |
| 904 */ |
| 905 scope.deactivateGesture = function(node, gesture) { |
| 906 var g = gesture.toLowerCase(); |
| 907 var dep = dispatcher.dependencyMap[g]; |
| 908 if (dep) { |
| 909 if (node._pgListeners > 0) { |
| 910 node._pgListeners--; |
| 911 } |
| 912 if (node._pgListeners === 0) { |
| 913 dispatcher.unregister(node); |
| 914 } |
| 915 if (node._pgEvents) { |
| 916 if (node._pgEvents[g] > 0) { |
| 917 node._pgEvents[g]--; |
| 918 } else { |
| 919 node._pgEvents[g] = 0; |
| 920 } |
| 921 } |
| 922 } |
| 923 return Boolean(dep); |
| 924 }; |
| 925 |
| 926 /** |
| 927 * Stop listening for `gesture` from `node` with `handler` function. |
| 928 * |
| 929 * @param {Element} node |
| 930 * @param {string} gesture |
| 931 * @param {Function} handler |
| 932 * @param {Boolean} capture |
| 933 */ |
| 934 scope.removeEventListener = function(node, gesture, handler, capture) { |
| 935 if (handler) { |
| 936 scope.deactivateGesture(node, gesture); |
| 937 node.removeEventListener(gesture, handler, capture); |
| 938 } |
| 939 }; |
| 940 })(window.PolymerGestures); |
| 941 |
| 942 (function(scope) { |
| 943 var dispatcher = scope.dispatcher; |
| 944 var pointermap = dispatcher.pointermap; |
| 945 // radius around touchend that swallows mouse events |
| 946 var DEDUP_DIST = 25; |
| 947 |
| 948 var WHICH_TO_BUTTONS = [0, 1, 4, 2]; |
| 949 |
| 950 var CURRENT_BUTTONS = 0; |
| 951 |
| 952 var FIREFOX_LINUX = /Linux.*Firefox\//i; |
| 953 |
| 954 var HAS_BUTTONS = (function() { |
| 955 // firefox on linux returns spec-incorrect values for mouseup.buttons |
| 956 // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.buttons#See_a
lso |
| 957 // https://codereview.chromium.org/727593003/#msg16 |
| 958 if (FIREFOX_LINUX.test(navigator.userAgent)) { |
| 959 return false; |
| 960 } |
| 961 try { |
| 962 return new MouseEvent('test', {buttons: 1}).buttons === 1; |
| 963 } catch (e) { |
| 964 return false; |
| 965 } |
| 966 })(); |
| 967 |
| 968 // handler block for native mouse events |
| 969 var mouseEvents = { |
| 970 POINTER_ID: 1, |
| 971 POINTER_TYPE: 'mouse', |
| 972 events: [ |
| 973 'mousedown', |
| 974 'mousemove', |
| 975 'mouseup' |
| 976 ], |
| 977 exposes: [ |
| 978 'down', |
| 979 'up', |
| 980 'move' |
| 981 ], |
| 982 register: function(target) { |
| 983 dispatcher.listen(target, this.events); |
| 984 }, |
| 985 unregister: function(target) { |
| 986 if (target === document) { |
| 987 return; |
| 988 } |
| 989 dispatcher.unlisten(target, this.events); |
| 990 }, |
| 991 lastTouches: [], |
| 992 // collide with the global mouse listener |
| 993 isEventSimulatedFromTouch: function(inEvent) { |
| 994 var lts = this.lastTouches; |
| 995 var x = inEvent.clientX, y = inEvent.clientY; |
| 996 for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { |
| 997 // simulated mouse events will be swallowed near a primary touchend |
| 998 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); |
| 999 if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { |
| 1000 return true; |
| 1001 } |
| 1002 } |
| 1003 }, |
| 1004 prepareEvent: function(inEvent) { |
| 1005 var e = dispatcher.cloneEvent(inEvent); |
| 1006 e.pointerId = this.POINTER_ID; |
| 1007 e.isPrimary = true; |
| 1008 e.pointerType = this.POINTER_TYPE; |
| 1009 e._source = 'mouse'; |
| 1010 if (!HAS_BUTTONS) { |
| 1011 var type = inEvent.type; |
| 1012 var bit = WHICH_TO_BUTTONS[inEvent.which] || 0; |
| 1013 if (type === 'mousedown') { |
| 1014 CURRENT_BUTTONS |= bit; |
| 1015 } else if (type === 'mouseup') { |
| 1016 CURRENT_BUTTONS &= ~bit; |
| 1017 } |
| 1018 e.buttons = CURRENT_BUTTONS; |
| 1019 } |
| 1020 return e; |
| 1021 }, |
| 1022 mousedown: function(inEvent) { |
| 1023 if (!this.isEventSimulatedFromTouch(inEvent)) { |
| 1024 var p = pointermap.has(this.POINTER_ID); |
| 1025 var e = this.prepareEvent(inEvent); |
| 1026 e.target = scope.findTarget(inEvent); |
| 1027 pointermap.set(this.POINTER_ID, e.target); |
| 1028 dispatcher.down(e); |
| 1029 } |
| 1030 }, |
| 1031 mousemove: function(inEvent) { |
| 1032 if (!this.isEventSimulatedFromTouch(inEvent)) { |
| 1033 var target = pointermap.get(this.POINTER_ID); |
| 1034 if (target) { |
| 1035 var e = this.prepareEvent(inEvent); |
| 1036 e.target = target; |
| 1037 // handle case where we missed a mouseup |
| 1038 if ((HAS_BUTTONS ? e.buttons : e.which) === 0) { |
| 1039 if (!HAS_BUTTONS) { |
| 1040 CURRENT_BUTTONS = e.buttons = 0; |
| 1041 } |
| 1042 dispatcher.cancel(e); |
| 1043 this.cleanupMouse(e.buttons); |
| 1044 } else { |
| 1045 dispatcher.move(e); |
| 1046 } |
| 1047 } |
| 1048 } |
| 1049 }, |
| 1050 mouseup: function(inEvent) { |
| 1051 if (!this.isEventSimulatedFromTouch(inEvent)) { |
| 1052 var e = this.prepareEvent(inEvent); |
| 1053 e.relatedTarget = scope.findTarget(inEvent); |
| 1054 e.target = pointermap.get(this.POINTER_ID); |
| 1055 dispatcher.up(e); |
| 1056 this.cleanupMouse(e.buttons); |
| 1057 } |
| 1058 }, |
| 1059 cleanupMouse: function(buttons) { |
| 1060 if (buttons === 0) { |
| 1061 pointermap.delete(this.POINTER_ID); |
| 1062 } |
| 1063 } |
| 1064 }; |
| 1065 |
| 1066 scope.mouseEvents = mouseEvents; |
| 1067 })(window.PolymerGestures); |
| 1068 |
| 1069 (function(scope) { |
| 1070 var dispatcher = scope.dispatcher; |
| 1071 var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); |
| 1072 var pointermap = dispatcher.pointermap; |
| 1073 var touchMap = Array.prototype.map.call.bind(Array.prototype.map); |
| 1074 // This should be long enough to ignore compat mouse events made by touch |
| 1075 var DEDUP_TIMEOUT = 2500; |
| 1076 var DEDUP_DIST = 25; |
| 1077 var CLICK_COUNT_TIMEOUT = 200; |
| 1078 var HYSTERESIS = 20; |
| 1079 var ATTRIB = 'touch-action'; |
| 1080 // TODO(dfreedm): disable until http://crbug.com/399765 is resolved |
| 1081 // var HAS_TOUCH_ACTION = ATTRIB in document.head.style; |
| 1082 var HAS_TOUCH_ACTION = false; |
| 1083 |
| 1084 // handler block for native touch events |
| 1085 var touchEvents = { |
| 1086 IS_IOS: false, |
| 1087 events: [ |
| 1088 'touchstart', |
| 1089 'touchmove', |
| 1090 'touchend', |
| 1091 'touchcancel' |
| 1092 ], |
| 1093 exposes: [ |
| 1094 'down', |
| 1095 'up', |
| 1096 'move' |
| 1097 ], |
| 1098 register: function(target, initial) { |
| 1099 if (this.IS_IOS ? initial : !initial) { |
| 1100 dispatcher.listen(target, this.events); |
| 1101 } |
| 1102 }, |
| 1103 unregister: function(target) { |
| 1104 if (!this.IS_IOS) { |
| 1105 dispatcher.unlisten(target, this.events); |
| 1106 } |
| 1107 }, |
| 1108 scrollTypes: { |
| 1109 EMITTER: 'none', |
| 1110 XSCROLLER: 'pan-x', |
| 1111 YSCROLLER: 'pan-y', |
| 1112 }, |
| 1113 touchActionToScrollType: function(touchAction) { |
| 1114 var t = touchAction; |
| 1115 var st = this.scrollTypes; |
| 1116 if (t === st.EMITTER) { |
| 1117 return 'none'; |
| 1118 } else if (t === st.XSCROLLER) { |
| 1119 return 'X'; |
| 1120 } else if (t === st.YSCROLLER) { |
| 1121 return 'Y'; |
| 1122 } else { |
| 1123 return 'XY'; |
| 1124 } |
| 1125 }, |
| 1126 POINTER_TYPE: 'touch', |
| 1127 firstTouch: null, |
| 1128 isPrimaryTouch: function(inTouch) { |
| 1129 return this.firstTouch === inTouch.identifier; |
| 1130 }, |
| 1131 setPrimaryTouch: function(inTouch) { |
| 1132 // set primary touch if there no pointers, or the only pointer is the mous
e |
| 1133 if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointer
map.has(1))) { |
| 1134 this.firstTouch = inTouch.identifier; |
| 1135 this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; |
| 1136 this.firstTarget = inTouch.target; |
| 1137 this.scrolling = null; |
| 1138 this.cancelResetClickCount(); |
| 1139 } |
| 1140 }, |
| 1141 removePrimaryPointer: function(inPointer) { |
| 1142 if (inPointer.isPrimary) { |
| 1143 this.firstTouch = null; |
| 1144 this.firstXY = null; |
| 1145 this.resetClickCount(); |
| 1146 } |
| 1147 }, |
| 1148 clickCount: 0, |
| 1149 resetId: null, |
| 1150 resetClickCount: function() { |
| 1151 var fn = function() { |
| 1152 this.clickCount = 0; |
| 1153 this.resetId = null; |
| 1154 }.bind(this); |
| 1155 this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); |
| 1156 }, |
| 1157 cancelResetClickCount: function() { |
| 1158 if (this.resetId) { |
| 1159 clearTimeout(this.resetId); |
| 1160 } |
| 1161 }, |
| 1162 typeToButtons: function(type) { |
| 1163 var ret = 0; |
| 1164 if (type === 'touchstart' || type === 'touchmove') { |
| 1165 ret = 1; |
| 1166 } |
| 1167 return ret; |
| 1168 }, |
| 1169 findTarget: function(touch, id) { |
| 1170 if (this.currentTouchEvent.type === 'touchstart') { |
| 1171 if (this.isPrimaryTouch(touch)) { |
| 1172 var fastPath = { |
| 1173 clientX: touch.clientX, |
| 1174 clientY: touch.clientY, |
| 1175 path: this.currentTouchEvent.path, |
| 1176 target: this.currentTouchEvent.target |
| 1177 }; |
| 1178 return scope.findTarget(fastPath); |
| 1179 } else { |
| 1180 return scope.findTarget(touch); |
| 1181 } |
| 1182 } |
| 1183 // reuse target we found in touchstart |
| 1184 return pointermap.get(id); |
| 1185 }, |
| 1186 touchToPointer: function(inTouch) { |
| 1187 var cte = this.currentTouchEvent; |
| 1188 var e = dispatcher.cloneEvent(inTouch); |
| 1189 // Spec specifies that pointerId 1 is reserved for Mouse. |
| 1190 // Touch identifiers can start at 0. |
| 1191 // Add 2 to the touch identifier for compatibility. |
| 1192 var id = e.pointerId = inTouch.identifier + 2; |
| 1193 e.target = this.findTarget(inTouch, id); |
| 1194 e.bubbles = true; |
| 1195 e.cancelable = true; |
| 1196 e.detail = this.clickCount; |
| 1197 e.buttons = this.typeToButtons(cte.type); |
| 1198 e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; |
| 1199 e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; |
| 1200 e.pressure = inTouch.webkitForce || inTouch.force || 0.5; |
| 1201 e.isPrimary = this.isPrimaryTouch(inTouch); |
| 1202 e.pointerType = this.POINTER_TYPE; |
| 1203 e._source = 'touch'; |
| 1204 // forward touch preventDefaults |
| 1205 var self = this; |
| 1206 e.preventDefault = function() { |
| 1207 self.scrolling = false; |
| 1208 self.firstXY = null; |
| 1209 cte.preventDefault(); |
| 1210 }; |
| 1211 return e; |
| 1212 }, |
| 1213 processTouches: function(inEvent, inFunction) { |
| 1214 var tl = inEvent.changedTouches; |
| 1215 this.currentTouchEvent = inEvent; |
| 1216 for (var i = 0, t, p; i < tl.length; i++) { |
| 1217 t = tl[i]; |
| 1218 p = this.touchToPointer(t); |
| 1219 if (inEvent.type === 'touchstart') { |
| 1220 pointermap.set(p.pointerId, p.target); |
| 1221 } |
| 1222 if (pointermap.has(p.pointerId)) { |
| 1223 inFunction.call(this, p); |
| 1224 } |
| 1225 if (inEvent.type === 'touchend' || inEvent._cancel) { |
| 1226 this.cleanUpPointer(p); |
| 1227 } |
| 1228 } |
| 1229 }, |
| 1230 // For single axis scrollers, determines whether the element should emit |
| 1231 // pointer events or behave as a scroller |
| 1232 shouldScroll: function(inEvent) { |
| 1233 if (this.firstXY) { |
| 1234 var ret; |
| 1235 var touchAction = scope.targetFinding.findTouchAction(inEvent); |
| 1236 var scrollAxis = this.touchActionToScrollType(touchAction); |
| 1237 if (scrollAxis === 'none') { |
| 1238 // this element is a touch-action: none, should never scroll |
| 1239 ret = false; |
| 1240 } else if (scrollAxis === 'XY') { |
| 1241 // this element should always scroll |
| 1242 ret = true; |
| 1243 } else { |
| 1244 var t = inEvent.changedTouches[0]; |
| 1245 // check the intended scroll axis, and other axis |
| 1246 var a = scrollAxis; |
| 1247 var oa = scrollAxis === 'Y' ? 'X' : 'Y'; |
| 1248 var da = Math.abs(t['client' + a] - this.firstXY[a]); |
| 1249 var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); |
| 1250 // if delta in the scroll axis > delta other axis, scroll instead of |
| 1251 // making events |
| 1252 ret = da >= doa; |
| 1253 } |
| 1254 return ret; |
| 1255 } |
| 1256 }, |
| 1257 findTouch: function(inTL, inId) { |
| 1258 for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { |
| 1259 if (t.identifier === inId) { |
| 1260 return true; |
| 1261 } |
| 1262 } |
| 1263 }, |
| 1264 // In some instances, a touchstart can happen without a touchend. This |
| 1265 // leaves the pointermap in a broken state. |
| 1266 // Therefore, on every touchstart, we remove the touches that did not fire a |
| 1267 // touchend event. |
| 1268 // To keep state globally consistent, we fire a |
| 1269 // pointercancel for this "abandoned" touch |
| 1270 vacuumTouches: function(inEvent) { |
| 1271 var tl = inEvent.touches; |
| 1272 // pointermap.pointers() should be < tl.length here, as the touchstart has
not |
| 1273 // been processed yet. |
| 1274 if (pointermap.pointers() >= tl.length) { |
| 1275 var d = []; |
| 1276 pointermap.forEach(function(value, key) { |
| 1277 // Never remove pointerId == 1, which is mouse. |
| 1278 // Touch identifiers are 2 smaller than their pointerId, which is the |
| 1279 // index in pointermap. |
| 1280 if (key !== 1 && !this.findTouch(tl, key - 2)) { |
| 1281 var p = value; |
| 1282 d.push(p); |
| 1283 } |
| 1284 }, this); |
| 1285 d.forEach(function(p) { |
| 1286 this.cancel(p); |
| 1287 pointermap.delete(p.pointerId); |
| 1288 }, this); |
| 1289 } |
| 1290 }, |
| 1291 touchstart: function(inEvent) { |
| 1292 this.vacuumTouches(inEvent); |
| 1293 this.setPrimaryTouch(inEvent.changedTouches[0]); |
| 1294 this.dedupSynthMouse(inEvent); |
| 1295 if (!this.scrolling) { |
| 1296 this.clickCount++; |
| 1297 this.processTouches(inEvent, this.down); |
| 1298 } |
| 1299 }, |
| 1300 down: function(inPointer) { |
| 1301 dispatcher.down(inPointer); |
| 1302 }, |
| 1303 touchmove: function(inEvent) { |
| 1304 if (HAS_TOUCH_ACTION) { |
| 1305 // touchevent.cancelable == false is sent when the page is scrolling und
er native Touch Action in Chrome 36 |
| 1306 // https://groups.google.com/a/chromium.org/d/msg/input-dev/wHnyukcYBcA/
b9kmtwM1jJQJ |
| 1307 if (inEvent.cancelable) { |
| 1308 this.processTouches(inEvent, this.move); |
| 1309 } |
| 1310 } else { |
| 1311 if (!this.scrolling) { |
| 1312 if (this.scrolling === null && this.shouldScroll(inEvent)) { |
| 1313 this.scrolling = true; |
| 1314 } else { |
| 1315 this.scrolling = false; |
| 1316 inEvent.preventDefault(); |
| 1317 this.processTouches(inEvent, this.move); |
| 1318 } |
| 1319 } else if (this.firstXY) { |
| 1320 var t = inEvent.changedTouches[0]; |
| 1321 var dx = t.clientX - this.firstXY.X; |
| 1322 var dy = t.clientY - this.firstXY.Y; |
| 1323 var dd = Math.sqrt(dx * dx + dy * dy); |
| 1324 if (dd >= HYSTERESIS) { |
| 1325 this.touchcancel(inEvent); |
| 1326 this.scrolling = true; |
| 1327 this.firstXY = null; |
| 1328 } |
| 1329 } |
| 1330 } |
| 1331 }, |
| 1332 move: function(inPointer) { |
| 1333 dispatcher.move(inPointer); |
| 1334 }, |
| 1335 touchend: function(inEvent) { |
| 1336 this.dedupSynthMouse(inEvent); |
| 1337 this.processTouches(inEvent, this.up); |
| 1338 }, |
| 1339 up: function(inPointer) { |
| 1340 inPointer.relatedTarget = scope.findTarget(inPointer); |
| 1341 dispatcher.up(inPointer); |
| 1342 }, |
| 1343 cancel: function(inPointer) { |
| 1344 dispatcher.cancel(inPointer); |
| 1345 }, |
| 1346 touchcancel: function(inEvent) { |
| 1347 inEvent._cancel = true; |
| 1348 this.processTouches(inEvent, this.cancel); |
| 1349 }, |
| 1350 cleanUpPointer: function(inPointer) { |
| 1351 pointermap['delete'](inPointer.pointerId); |
| 1352 this.removePrimaryPointer(inPointer); |
| 1353 }, |
| 1354 // prevent synth mouse events from creating pointer events |
| 1355 dedupSynthMouse: function(inEvent) { |
| 1356 var lts = scope.mouseEvents.lastTouches; |
| 1357 var t = inEvent.changedTouches[0]; |
| 1358 // only the primary finger will synth mouse events |
| 1359 if (this.isPrimaryTouch(t)) { |
| 1360 // remember x/y of last touch |
| 1361 var lt = {x: t.clientX, y: t.clientY}; |
| 1362 lts.push(lt); |
| 1363 var fn = (function(lts, lt){ |
| 1364 var i = lts.indexOf(lt); |
| 1365 if (i > -1) { |
| 1366 lts.splice(i, 1); |
| 1367 } |
| 1368 }).bind(null, lts, lt); |
| 1369 setTimeout(fn, DEDUP_TIMEOUT); |
| 1370 } |
| 1371 } |
| 1372 }; |
| 1373 |
| 1374 // prevent "ghost clicks" that come from elements that were removed in a touch
handler |
| 1375 var STOP_PROP_FN = Event.prototype.stopImmediatePropagation || Event.prototype
.stopPropagation; |
| 1376 document.addEventListener('click', function(ev) { |
| 1377 var x = ev.clientX, y = ev.clientY; |
| 1378 // check if a click is within DEDUP_DIST px radius of the touchstart |
| 1379 var closeTo = function(touch) { |
| 1380 var dx = Math.abs(x - touch.x), dy = Math.abs(y - touch.y); |
| 1381 return (dx <= DEDUP_DIST && dy <= DEDUP_DIST); |
| 1382 }; |
| 1383 // if click coordinates are close to touch coordinates, assume the click cam
e from a touch |
| 1384 var wasTouched = scope.mouseEvents.lastTouches.some(closeTo); |
| 1385 // if the click came from touch, and the touchstart target is not in the pat
h of the click event, |
| 1386 // then the touchstart target was probably removed, and the click should be
"busted" |
| 1387 var path = scope.targetFinding.path(ev); |
| 1388 if (wasTouched) { |
| 1389 for (var i = 0; i < path.length; i++) { |
| 1390 if (path[i] === touchEvents.firstTarget) { |
| 1391 return; |
| 1392 } |
| 1393 } |
| 1394 ev.preventDefault(); |
| 1395 STOP_PROP_FN.call(ev); |
| 1396 } |
| 1397 }, true); |
| 1398 |
| 1399 scope.touchEvents = touchEvents; |
| 1400 })(window.PolymerGestures); |
| 1401 |
| 1402 (function(scope) { |
| 1403 var dispatcher = scope.dispatcher; |
| 1404 var pointermap = dispatcher.pointermap; |
| 1405 var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MS
POINTER_TYPE_MOUSE === 'number'; |
| 1406 var msEvents = { |
| 1407 events: [ |
| 1408 'MSPointerDown', |
| 1409 'MSPointerMove', |
| 1410 'MSPointerUp', |
| 1411 'MSPointerCancel', |
| 1412 ], |
| 1413 register: function(target) { |
| 1414 dispatcher.listen(target, this.events); |
| 1415 }, |
| 1416 unregister: function(target) { |
| 1417 if (target === document) { |
| 1418 return; |
| 1419 } |
| 1420 dispatcher.unlisten(target, this.events); |
| 1421 }, |
| 1422 POINTER_TYPES: [ |
| 1423 '', |
| 1424 'unavailable', |
| 1425 'touch', |
| 1426 'pen', |
| 1427 'mouse' |
| 1428 ], |
| 1429 prepareEvent: function(inEvent) { |
| 1430 var e = inEvent; |
| 1431 e = dispatcher.cloneEvent(inEvent); |
| 1432 if (HAS_BITMAP_TYPE) { |
| 1433 e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; |
| 1434 } |
| 1435 e._source = 'ms'; |
| 1436 return e; |
| 1437 }, |
| 1438 cleanup: function(id) { |
| 1439 pointermap['delete'](id); |
| 1440 }, |
| 1441 MSPointerDown: function(inEvent) { |
| 1442 var e = this.prepareEvent(inEvent); |
| 1443 e.target = scope.findTarget(inEvent); |
| 1444 pointermap.set(inEvent.pointerId, e.target); |
| 1445 dispatcher.down(e); |
| 1446 }, |
| 1447 MSPointerMove: function(inEvent) { |
| 1448 var target = pointermap.get(inEvent.pointerId); |
| 1449 if (target) { |
| 1450 var e = this.prepareEvent(inEvent); |
| 1451 e.target = target; |
| 1452 dispatcher.move(e); |
| 1453 } |
| 1454 }, |
| 1455 MSPointerUp: function(inEvent) { |
| 1456 var e = this.prepareEvent(inEvent); |
| 1457 e.relatedTarget = scope.findTarget(inEvent); |
| 1458 e.target = pointermap.get(e.pointerId); |
| 1459 dispatcher.up(e); |
| 1460 this.cleanup(inEvent.pointerId); |
| 1461 }, |
| 1462 MSPointerCancel: function(inEvent) { |
| 1463 var e = this.prepareEvent(inEvent); |
| 1464 e.relatedTarget = scope.findTarget(inEvent); |
| 1465 e.target = pointermap.get(e.pointerId); |
| 1466 dispatcher.cancel(e); |
| 1467 this.cleanup(inEvent.pointerId); |
| 1468 } |
| 1469 }; |
| 1470 |
| 1471 scope.msEvents = msEvents; |
| 1472 })(window.PolymerGestures); |
| 1473 |
| 1474 (function(scope) { |
| 1475 var dispatcher = scope.dispatcher; |
| 1476 var pointermap = dispatcher.pointermap; |
| 1477 var pointerEvents = { |
| 1478 events: [ |
| 1479 'pointerdown', |
| 1480 'pointermove', |
| 1481 'pointerup', |
| 1482 'pointercancel' |
| 1483 ], |
| 1484 prepareEvent: function(inEvent) { |
| 1485 var e = dispatcher.cloneEvent(inEvent); |
| 1486 e._source = 'pointer'; |
| 1487 return e; |
| 1488 }, |
| 1489 register: function(target) { |
| 1490 dispatcher.listen(target, this.events); |
| 1491 }, |
| 1492 unregister: function(target) { |
| 1493 if (target === document) { |
| 1494 return; |
| 1495 } |
| 1496 dispatcher.unlisten(target, this.events); |
| 1497 }, |
| 1498 cleanup: function(id) { |
| 1499 pointermap['delete'](id); |
| 1500 }, |
| 1501 pointerdown: function(inEvent) { |
| 1502 var e = this.prepareEvent(inEvent); |
| 1503 e.target = scope.findTarget(inEvent); |
| 1504 pointermap.set(e.pointerId, e.target); |
| 1505 dispatcher.down(e); |
| 1506 }, |
| 1507 pointermove: function(inEvent) { |
| 1508 var target = pointermap.get(inEvent.pointerId); |
| 1509 if (target) { |
| 1510 var e = this.prepareEvent(inEvent); |
| 1511 e.target = target; |
| 1512 dispatcher.move(e); |
| 1513 } |
| 1514 }, |
| 1515 pointerup: function(inEvent) { |
| 1516 var e = this.prepareEvent(inEvent); |
| 1517 e.relatedTarget = scope.findTarget(inEvent); |
| 1518 e.target = pointermap.get(e.pointerId); |
| 1519 dispatcher.up(e); |
| 1520 this.cleanup(inEvent.pointerId); |
| 1521 }, |
| 1522 pointercancel: function(inEvent) { |
| 1523 var e = this.prepareEvent(inEvent); |
| 1524 e.relatedTarget = scope.findTarget(inEvent); |
| 1525 e.target = pointermap.get(e.pointerId); |
| 1526 dispatcher.cancel(e); |
| 1527 this.cleanup(inEvent.pointerId); |
| 1528 } |
| 1529 }; |
| 1530 |
| 1531 scope.pointerEvents = pointerEvents; |
| 1532 })(window.PolymerGestures); |
| 1533 |
| 1534 /** |
| 1535 * This module contains the handlers for native platform events. |
| 1536 * From here, the dispatcher is called to create unified pointer events. |
| 1537 * Included are touch events (v1), mouse events, and MSPointerEvents. |
| 1538 */ |
| 1539 (function(scope) { |
| 1540 |
| 1541 var dispatcher = scope.dispatcher; |
| 1542 var nav = window.navigator; |
| 1543 |
| 1544 if (window.PointerEvent) { |
| 1545 dispatcher.registerSource('pointer', scope.pointerEvents); |
| 1546 } else if (nav.msPointerEnabled) { |
| 1547 dispatcher.registerSource('ms', scope.msEvents); |
| 1548 } else { |
| 1549 dispatcher.registerSource('mouse', scope.mouseEvents); |
| 1550 if (window.ontouchstart !== undefined) { |
| 1551 dispatcher.registerSource('touch', scope.touchEvents); |
| 1552 } |
| 1553 } |
| 1554 |
| 1555 // Work around iOS bugs https://bugs.webkit.org/show_bug.cgi?id=135628 and htt
ps://bugs.webkit.org/show_bug.cgi?id=136506 |
| 1556 var ua = navigator.userAgent; |
| 1557 var IS_IOS = ua.match(/iPad|iPhone|iPod/) && 'ontouchstart' in window; |
| 1558 |
| 1559 dispatcher.IS_IOS = IS_IOS; |
| 1560 scope.touchEvents.IS_IOS = IS_IOS; |
| 1561 |
| 1562 dispatcher.register(document, true); |
| 1563 })(window.PolymerGestures); |
| 1564 |
| 1565 /** |
| 1566 * This event denotes the beginning of a series of tracking events. |
| 1567 * |
| 1568 * @module PointerGestures |
| 1569 * @submodule Events |
| 1570 * @class trackstart |
| 1571 */ |
| 1572 /** |
| 1573 * Pixels moved in the x direction since trackstart. |
| 1574 * @type Number |
| 1575 * @property dx |
| 1576 */ |
| 1577 /** |
| 1578 * Pixes moved in the y direction since trackstart. |
| 1579 * @type Number |
| 1580 * @property dy |
| 1581 */ |
| 1582 /** |
| 1583 * Pixels moved in the x direction since the last track. |
| 1584 * @type Number |
| 1585 * @property ddx |
| 1586 */ |
| 1587 /** |
| 1588 * Pixles moved in the y direction since the last track. |
| 1589 * @type Number |
| 1590 * @property ddy |
| 1591 */ |
| 1592 /** |
| 1593 * The clientX position of the track gesture. |
| 1594 * @type Number |
| 1595 * @property clientX |
| 1596 */ |
| 1597 /** |
| 1598 * The clientY position of the track gesture. |
| 1599 * @type Number |
| 1600 * @property clientY |
| 1601 */ |
| 1602 /** |
| 1603 * The pageX position of the track gesture. |
| 1604 * @type Number |
| 1605 * @property pageX |
| 1606 */ |
| 1607 /** |
| 1608 * The pageY position of the track gesture. |
| 1609 * @type Number |
| 1610 * @property pageY |
| 1611 */ |
| 1612 /** |
| 1613 * The screenX position of the track gesture. |
| 1614 * @type Number |
| 1615 * @property screenX |
| 1616 */ |
| 1617 /** |
| 1618 * The screenY position of the track gesture. |
| 1619 * @type Number |
| 1620 * @property screenY |
| 1621 */ |
| 1622 /** |
| 1623 * The last x axis direction of the pointer. |
| 1624 * @type Number |
| 1625 * @property xDirection |
| 1626 */ |
| 1627 /** |
| 1628 * The last y axis direction of the pointer. |
| 1629 * @type Number |
| 1630 * @property yDirection |
| 1631 */ |
| 1632 /** |
| 1633 * A shared object between all tracking events. |
| 1634 * @type Object |
| 1635 * @property trackInfo |
| 1636 */ |
| 1637 /** |
| 1638 * The element currently under the pointer. |
| 1639 * @type Element |
| 1640 * @property relatedTarget |
| 1641 */ |
| 1642 /** |
| 1643 * The type of pointer that make the track gesture. |
| 1644 * @type String |
| 1645 * @property pointerType |
| 1646 */ |
| 1647 /** |
| 1648 * |
| 1649 * This event fires for all pointer movement being tracked. |
| 1650 * |
| 1651 * @class track |
| 1652 * @extends trackstart |
| 1653 */ |
| 1654 /** |
| 1655 * This event fires when the pointer is no longer being tracked. |
| 1656 * |
| 1657 * @class trackend |
| 1658 * @extends trackstart |
| 1659 */ |
| 1660 |
| 1661 (function(scope) { |
| 1662 var dispatcher = scope.dispatcher; |
| 1663 var eventFactory = scope.eventFactory; |
| 1664 var pointermap = new scope.PointerMap(); |
| 1665 var track = { |
| 1666 events: [ |
| 1667 'down', |
| 1668 'move', |
| 1669 'up', |
| 1670 ], |
| 1671 exposes: [ |
| 1672 'trackstart', |
| 1673 'track', |
| 1674 'trackx', |
| 1675 'tracky', |
| 1676 'trackend' |
| 1677 ], |
| 1678 defaultActions: { |
| 1679 'track': 'none', |
| 1680 'trackx': 'pan-y', |
| 1681 'tracky': 'pan-x' |
| 1682 }, |
| 1683 WIGGLE_THRESHOLD: 4, |
| 1684 clampDir: function(inDelta) { |
| 1685 return inDelta > 0 ? 1 : -1; |
| 1686 }, |
| 1687 calcPositionDelta: function(inA, inB) { |
| 1688 var x = 0, y = 0; |
| 1689 if (inA && inB) { |
| 1690 x = inB.pageX - inA.pageX; |
| 1691 y = inB.pageY - inA.pageY; |
| 1692 } |
| 1693 return {x: x, y: y}; |
| 1694 }, |
| 1695 fireTrack: function(inType, inEvent, inTrackingData) { |
| 1696 var t = inTrackingData; |
| 1697 var d = this.calcPositionDelta(t.downEvent, inEvent); |
| 1698 var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); |
| 1699 if (dd.x) { |
| 1700 t.xDirection = this.clampDir(dd.x); |
| 1701 } else if (inType === 'trackx') { |
| 1702 return; |
| 1703 } |
| 1704 if (dd.y) { |
| 1705 t.yDirection = this.clampDir(dd.y); |
| 1706 } else if (inType === 'tracky') { |
| 1707 return; |
| 1708 } |
| 1709 var gestureProto = { |
| 1710 bubbles: true, |
| 1711 cancelable: true, |
| 1712 trackInfo: t.trackInfo, |
| 1713 relatedTarget: inEvent.relatedTarget, |
| 1714 pointerType: inEvent.pointerType, |
| 1715 pointerId: inEvent.pointerId, |
| 1716 _source: 'track' |
| 1717 }; |
| 1718 if (inType !== 'tracky') { |
| 1719 gestureProto.x = inEvent.x; |
| 1720 gestureProto.dx = d.x; |
| 1721 gestureProto.ddx = dd.x; |
| 1722 gestureProto.clientX = inEvent.clientX; |
| 1723 gestureProto.pageX = inEvent.pageX; |
| 1724 gestureProto.screenX = inEvent.screenX; |
| 1725 gestureProto.xDirection = t.xDirection; |
| 1726 } |
| 1727 if (inType !== 'trackx') { |
| 1728 gestureProto.dy = d.y; |
| 1729 gestureProto.ddy = dd.y; |
| 1730 gestureProto.y = inEvent.y; |
| 1731 gestureProto.clientY = inEvent.clientY; |
| 1732 gestureProto.pageY = inEvent.pageY; |
| 1733 gestureProto.screenY = inEvent.screenY; |
| 1734 gestureProto.yDirection = t.yDirection; |
| 1735 } |
| 1736 var e = eventFactory.makeGestureEvent(inType, gestureProto); |
| 1737 t.downTarget.dispatchEvent(e); |
| 1738 }, |
| 1739 down: function(inEvent) { |
| 1740 if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.butto
ns === 1 : true)) { |
| 1741 var p = { |
| 1742 downEvent: inEvent, |
| 1743 downTarget: inEvent.target, |
| 1744 trackInfo: {}, |
| 1745 lastMoveEvent: null, |
| 1746 xDirection: 0, |
| 1747 yDirection: 0, |
| 1748 tracking: false |
| 1749 }; |
| 1750 pointermap.set(inEvent.pointerId, p); |
| 1751 } |
| 1752 }, |
| 1753 move: function(inEvent) { |
| 1754 var p = pointermap.get(inEvent.pointerId); |
| 1755 if (p) { |
| 1756 if (!p.tracking) { |
| 1757 var d = this.calcPositionDelta(p.downEvent, inEvent); |
| 1758 var move = d.x * d.x + d.y * d.y; |
| 1759 // start tracking only if finger moves more than WIGGLE_THRESHOLD |
| 1760 if (move > this.WIGGLE_THRESHOLD) { |
| 1761 p.tracking = true; |
| 1762 p.lastMoveEvent = p.downEvent; |
| 1763 this.fireTrack('trackstart', inEvent, p); |
| 1764 } |
| 1765 } |
| 1766 if (p.tracking) { |
| 1767 this.fireTrack('track', inEvent, p); |
| 1768 this.fireTrack('trackx', inEvent, p); |
| 1769 this.fireTrack('tracky', inEvent, p); |
| 1770 } |
| 1771 p.lastMoveEvent = inEvent; |
| 1772 } |
| 1773 }, |
| 1774 up: function(inEvent) { |
| 1775 var p = pointermap.get(inEvent.pointerId); |
| 1776 if (p) { |
| 1777 if (p.tracking) { |
| 1778 this.fireTrack('trackend', inEvent, p); |
| 1779 } |
| 1780 pointermap.delete(inEvent.pointerId); |
| 1781 } |
| 1782 } |
| 1783 }; |
| 1784 dispatcher.registerGesture('track', track); |
| 1785 })(window.PolymerGestures); |
| 1786 |
| 1787 /** |
| 1788 * This event is fired when a pointer is held down for 200ms. |
| 1789 * |
| 1790 * @module PointerGestures |
| 1791 * @submodule Events |
| 1792 * @class hold |
| 1793 */ |
| 1794 /** |
| 1795 * Type of pointer that made the holding event. |
| 1796 * @type String |
| 1797 * @property pointerType |
| 1798 */ |
| 1799 /** |
| 1800 * Screen X axis position of the held pointer |
| 1801 * @type Number |
| 1802 * @property clientX |
| 1803 */ |
| 1804 /** |
| 1805 * Screen Y axis position of the held pointer |
| 1806 * @type Number |
| 1807 * @property clientY |
| 1808 */ |
| 1809 /** |
| 1810 * Type of pointer that made the holding event. |
| 1811 * @type String |
| 1812 * @property pointerType |
| 1813 */ |
| 1814 /** |
| 1815 * This event is fired every 200ms while a pointer is held down. |
| 1816 * |
| 1817 * @class holdpulse |
| 1818 * @extends hold |
| 1819 */ |
| 1820 /** |
| 1821 * Milliseconds pointer has been held down. |
| 1822 * @type Number |
| 1823 * @property holdTime |
| 1824 */ |
| 1825 /** |
| 1826 * This event is fired when a held pointer is released or moved. |
| 1827 * |
| 1828 * @class release |
| 1829 */ |
| 1830 |
| 1831 (function(scope) { |
| 1832 var dispatcher = scope.dispatcher; |
| 1833 var eventFactory = scope.eventFactory; |
| 1834 var hold = { |
| 1835 // wait at least HOLD_DELAY ms between hold and pulse events |
| 1836 HOLD_DELAY: 200, |
| 1837 // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold |
| 1838 WIGGLE_THRESHOLD: 16, |
| 1839 events: [ |
| 1840 'down', |
| 1841 'move', |
| 1842 'up', |
| 1843 ], |
| 1844 exposes: [ |
| 1845 'hold', |
| 1846 'holdpulse', |
| 1847 'release' |
| 1848 ], |
| 1849 heldPointer: null, |
| 1850 holdJob: null, |
| 1851 pulse: function() { |
| 1852 var hold = Date.now() - this.heldPointer.timeStamp; |
| 1853 var type = this.held ? 'holdpulse' : 'hold'; |
| 1854 this.fireHold(type, hold); |
| 1855 this.held = true; |
| 1856 }, |
| 1857 cancel: function() { |
| 1858 clearInterval(this.holdJob); |
| 1859 if (this.held) { |
| 1860 this.fireHold('release'); |
| 1861 } |
| 1862 this.held = false; |
| 1863 this.heldPointer = null; |
| 1864 this.target = null; |
| 1865 this.holdJob = null; |
| 1866 }, |
| 1867 down: function(inEvent) { |
| 1868 if (inEvent.isPrimary && !this.heldPointer) { |
| 1869 this.heldPointer = inEvent; |
| 1870 this.target = inEvent.target; |
| 1871 this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); |
| 1872 } |
| 1873 }, |
| 1874 up: function(inEvent) { |
| 1875 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ |
| 1876 this.cancel(); |
| 1877 } |
| 1878 }, |
| 1879 move: function(inEvent) { |
| 1880 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ |
| 1881 var x = inEvent.clientX - this.heldPointer.clientX; |
| 1882 var y = inEvent.clientY - this.heldPointer.clientY; |
| 1883 if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { |
| 1884 this.cancel(); |
| 1885 } |
| 1886 } |
| 1887 }, |
| 1888 fireHold: function(inType, inHoldTime) { |
| 1889 var p = { |
| 1890 bubbles: true, |
| 1891 cancelable: true, |
| 1892 pointerType: this.heldPointer.pointerType, |
| 1893 pointerId: this.heldPointer.pointerId, |
| 1894 x: this.heldPointer.clientX, |
| 1895 y: this.heldPointer.clientY, |
| 1896 _source: 'hold' |
| 1897 }; |
| 1898 if (inHoldTime) { |
| 1899 p.holdTime = inHoldTime; |
| 1900 } |
| 1901 var e = eventFactory.makeGestureEvent(inType, p); |
| 1902 this.target.dispatchEvent(e); |
| 1903 } |
| 1904 }; |
| 1905 dispatcher.registerGesture('hold', hold); |
| 1906 })(window.PolymerGestures); |
| 1907 |
| 1908 /** |
| 1909 * This event is fired when a pointer quickly goes down and up, and is used to |
| 1910 * denote activation. |
| 1911 * |
| 1912 * Any gesture event can prevent the tap event from being created by calling |
| 1913 * `event.preventTap`. |
| 1914 * |
| 1915 * Any pointer event can prevent the tap by setting the `tapPrevented` property |
| 1916 * on itself. |
| 1917 * |
| 1918 * @module PointerGestures |
| 1919 * @submodule Events |
| 1920 * @class tap |
| 1921 */ |
| 1922 /** |
| 1923 * X axis position of the tap. |
| 1924 * @property x |
| 1925 * @type Number |
| 1926 */ |
| 1927 /** |
| 1928 * Y axis position of the tap. |
| 1929 * @property y |
| 1930 * @type Number |
| 1931 */ |
| 1932 /** |
| 1933 * Type of the pointer that made the tap. |
| 1934 * @property pointerType |
| 1935 * @type String |
| 1936 */ |
| 1937 (function(scope) { |
| 1938 var dispatcher = scope.dispatcher; |
| 1939 var eventFactory = scope.eventFactory; |
| 1940 var pointermap = new scope.PointerMap(); |
| 1941 var tap = { |
| 1942 events: [ |
| 1943 'down', |
| 1944 'up' |
| 1945 ], |
| 1946 exposes: [ |
| 1947 'tap' |
| 1948 ], |
| 1949 down: function(inEvent) { |
| 1950 if (inEvent.isPrimary && !inEvent.tapPrevented) { |
| 1951 pointermap.set(inEvent.pointerId, { |
| 1952 target: inEvent.target, |
| 1953 buttons: inEvent.buttons, |
| 1954 x: inEvent.clientX, |
| 1955 y: inEvent.clientY |
| 1956 }); |
| 1957 } |
| 1958 }, |
| 1959 shouldTap: function(e, downState) { |
| 1960 var tap = true; |
| 1961 if (e.pointerType === 'mouse') { |
| 1962 // only allow left click to tap for mouse |
| 1963 tap = (e.buttons ^ 1) && (downState.buttons & 1); |
| 1964 } |
| 1965 return tap && !e.tapPrevented; |
| 1966 }, |
| 1967 up: function(inEvent) { |
| 1968 var start = pointermap.get(inEvent.pointerId); |
| 1969 if (start && this.shouldTap(inEvent, start)) { |
| 1970 // up.relatedTarget is target currently under finger |
| 1971 var t = scope.targetFinding.LCA(start.target, inEvent.relatedTarget); |
| 1972 if (t) { |
| 1973 var e = eventFactory.makeGestureEvent('tap', { |
| 1974 bubbles: true, |
| 1975 cancelable: true, |
| 1976 x: inEvent.clientX, |
| 1977 y: inEvent.clientY, |
| 1978 detail: inEvent.detail, |
| 1979 pointerType: inEvent.pointerType, |
| 1980 pointerId: inEvent.pointerId, |
| 1981 altKey: inEvent.altKey, |
| 1982 ctrlKey: inEvent.ctrlKey, |
| 1983 metaKey: inEvent.metaKey, |
| 1984 shiftKey: inEvent.shiftKey, |
| 1985 _source: 'tap' |
| 1986 }); |
| 1987 t.dispatchEvent(e); |
| 1988 } |
| 1989 } |
| 1990 pointermap.delete(inEvent.pointerId); |
| 1991 } |
| 1992 }; |
| 1993 // patch eventFactory to remove id from tap's pointermap for preventTap calls |
| 1994 eventFactory.preventTap = function(e) { |
| 1995 return function() { |
| 1996 e.tapPrevented = true; |
| 1997 pointermap.delete(e.pointerId); |
| 1998 }; |
| 1999 }; |
| 2000 dispatcher.registerGesture('tap', tap); |
| 2001 })(window.PolymerGestures); |
| 2002 |
| 2003 /* |
| 2004 * Basic strategy: find the farthest apart points, use as diameter of circle |
| 2005 * react to size change and rotation of the chord |
| 2006 */ |
| 2007 |
| 2008 /** |
| 2009 * @module pointer-gestures |
| 2010 * @submodule Events |
| 2011 * @class pinch |
| 2012 */ |
| 2013 /** |
| 2014 * Scale of the pinch zoom gesture |
| 2015 * @property scale |
| 2016 * @type Number |
| 2017 */ |
| 2018 /** |
| 2019 * Center X position of pointers causing pinch |
| 2020 * @property centerX |
| 2021 * @type Number |
| 2022 */ |
| 2023 /** |
| 2024 * Center Y position of pointers causing pinch |
| 2025 * @property centerY |
| 2026 * @type Number |
| 2027 */ |
| 2028 |
| 2029 /** |
| 2030 * @module pointer-gestures |
| 2031 * @submodule Events |
| 2032 * @class rotate |
| 2033 */ |
| 2034 /** |
| 2035 * Angle (in degrees) of rotation. Measured from starting positions of pointers. |
| 2036 * @property angle |
| 2037 * @type Number |
| 2038 */ |
| 2039 /** |
| 2040 * Center X position of pointers causing rotation |
| 2041 * @property centerX |
| 2042 * @type Number |
| 2043 */ |
| 2044 /** |
| 2045 * Center Y position of pointers causing rotation |
| 2046 * @property centerY |
| 2047 * @type Number |
| 2048 */ |
| 2049 (function(scope) { |
| 2050 var dispatcher = scope.dispatcher; |
| 2051 var eventFactory = scope.eventFactory; |
| 2052 var pointermap = new scope.PointerMap(); |
| 2053 var RAD_TO_DEG = 180 / Math.PI; |
| 2054 var pinch = { |
| 2055 events: [ |
| 2056 'down', |
| 2057 'up', |
| 2058 'move', |
| 2059 'cancel' |
| 2060 ], |
| 2061 exposes: [ |
| 2062 'pinchstart', |
| 2063 'pinch', |
| 2064 'pinchend', |
| 2065 'rotate' |
| 2066 ], |
| 2067 defaultActions: { |
| 2068 'pinch': 'none', |
| 2069 'rotate': 'none' |
| 2070 }, |
| 2071 reference: {}, |
| 2072 down: function(inEvent) { |
| 2073 pointermap.set(inEvent.pointerId, inEvent); |
| 2074 if (pointermap.pointers() == 2) { |
| 2075 var points = this.calcChord(); |
| 2076 var angle = this.calcAngle(points); |
| 2077 this.reference = { |
| 2078 angle: angle, |
| 2079 diameter: points.diameter, |
| 2080 target: scope.targetFinding.LCA(points.a.target, points.b.target) |
| 2081 }; |
| 2082 |
| 2083 this.firePinch('pinchstart', points.diameter, points); |
| 2084 } |
| 2085 }, |
| 2086 up: function(inEvent) { |
| 2087 var p = pointermap.get(inEvent.pointerId); |
| 2088 var num = pointermap.pointers(); |
| 2089 if (p) { |
| 2090 if (num === 2) { |
| 2091 // fire 'pinchend' before deleting pointer |
| 2092 var points = this.calcChord(); |
| 2093 this.firePinch('pinchend', points.diameter, points); |
| 2094 } |
| 2095 pointermap.delete(inEvent.pointerId); |
| 2096 } |
| 2097 }, |
| 2098 move: function(inEvent) { |
| 2099 if (pointermap.has(inEvent.pointerId)) { |
| 2100 pointermap.set(inEvent.pointerId, inEvent); |
| 2101 if (pointermap.pointers() > 1) { |
| 2102 this.calcPinchRotate(); |
| 2103 } |
| 2104 } |
| 2105 }, |
| 2106 cancel: function(inEvent) { |
| 2107 this.up(inEvent); |
| 2108 }, |
| 2109 firePinch: function(type, diameter, points) { |
| 2110 var zoom = diameter / this.reference.diameter; |
| 2111 var e = eventFactory.makeGestureEvent(type, { |
| 2112 bubbles: true, |
| 2113 cancelable: true, |
| 2114 scale: zoom, |
| 2115 centerX: points.center.x, |
| 2116 centerY: points.center.y, |
| 2117 _source: 'pinch' |
| 2118 }); |
| 2119 this.reference.target.dispatchEvent(e); |
| 2120 }, |
| 2121 fireRotate: function(angle, points) { |
| 2122 var diff = Math.round((angle - this.reference.angle) % 360); |
| 2123 var e = eventFactory.makeGestureEvent('rotate', { |
| 2124 bubbles: true, |
| 2125 cancelable: true, |
| 2126 angle: diff, |
| 2127 centerX: points.center.x, |
| 2128 centerY: points.center.y, |
| 2129 _source: 'pinch' |
| 2130 }); |
| 2131 this.reference.target.dispatchEvent(e); |
| 2132 }, |
| 2133 calcPinchRotate: function() { |
| 2134 var points = this.calcChord(); |
| 2135 var diameter = points.diameter; |
| 2136 var angle = this.calcAngle(points); |
| 2137 if (diameter != this.reference.diameter) { |
| 2138 this.firePinch('pinch', diameter, points); |
| 2139 } |
| 2140 if (angle != this.reference.angle) { |
| 2141 this.fireRotate(angle, points); |
| 2142 } |
| 2143 }, |
| 2144 calcChord: function() { |
| 2145 var pointers = []; |
| 2146 pointermap.forEach(function(p) { |
| 2147 pointers.push(p); |
| 2148 }); |
| 2149 var dist = 0; |
| 2150 // start with at least two pointers |
| 2151 var points = {a: pointers[0], b: pointers[1]}; |
| 2152 var x, y, d; |
| 2153 for (var i = 0; i < pointers.length; i++) { |
| 2154 var a = pointers[i]; |
| 2155 for (var j = i + 1; j < pointers.length; j++) { |
| 2156 var b = pointers[j]; |
| 2157 x = Math.abs(a.clientX - b.clientX); |
| 2158 y = Math.abs(a.clientY - b.clientY); |
| 2159 d = x + y; |
| 2160 if (d > dist) { |
| 2161 dist = d; |
| 2162 points = {a: a, b: b}; |
| 2163 } |
| 2164 } |
| 2165 } |
| 2166 x = Math.abs(points.a.clientX + points.b.clientX) / 2; |
| 2167 y = Math.abs(points.a.clientY + points.b.clientY) / 2; |
| 2168 points.center = { x: x, y: y }; |
| 2169 points.diameter = dist; |
| 2170 return points; |
| 2171 }, |
| 2172 calcAngle: function(points) { |
| 2173 var x = points.a.clientX - points.b.clientX; |
| 2174 var y = points.a.clientY - points.b.clientY; |
| 2175 return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; |
| 2176 } |
| 2177 }; |
| 2178 dispatcher.registerGesture('pinch', pinch); |
| 2179 })(window.PolymerGestures); |
| 2180 |
| 2181 (function (global) { |
| 2182 'use strict'; |
| 2183 |
| 2184 var Token, |
| 2185 TokenName, |
| 2186 Syntax, |
| 2187 Messages, |
| 2188 source, |
| 2189 index, |
| 2190 length, |
| 2191 delegate, |
| 2192 lookahead, |
| 2193 state; |
| 2194 |
| 2195 Token = { |
| 2196 BooleanLiteral: 1, |
| 2197 EOF: 2, |
| 2198 Identifier: 3, |
| 2199 Keyword: 4, |
| 2200 NullLiteral: 5, |
| 2201 NumericLiteral: 6, |
| 2202 Punctuator: 7, |
| 2203 StringLiteral: 8 |
| 2204 }; |
| 2205 |
| 2206 TokenName = {}; |
| 2207 TokenName[Token.BooleanLiteral] = 'Boolean'; |
| 2208 TokenName[Token.EOF] = '<end>'; |
| 2209 TokenName[Token.Identifier] = 'Identifier'; |
| 2210 TokenName[Token.Keyword] = 'Keyword'; |
| 2211 TokenName[Token.NullLiteral] = 'Null'; |
| 2212 TokenName[Token.NumericLiteral] = 'Numeric'; |
| 2213 TokenName[Token.Punctuator] = 'Punctuator'; |
| 2214 TokenName[Token.StringLiteral] = 'String'; |
| 2215 |
| 2216 Syntax = { |
| 2217 ArrayExpression: 'ArrayExpression', |
| 2218 BinaryExpression: 'BinaryExpression', |
| 2219 CallExpression: 'CallExpression', |
| 2220 ConditionalExpression: 'ConditionalExpression', |
| 2221 EmptyStatement: 'EmptyStatement', |
| 2222 ExpressionStatement: 'ExpressionStatement', |
| 2223 Identifier: 'Identifier', |
| 2224 Literal: 'Literal', |
| 2225 LabeledStatement: 'LabeledStatement', |
| 2226 LogicalExpression: 'LogicalExpression', |
| 2227 MemberExpression: 'MemberExpression', |
| 2228 ObjectExpression: 'ObjectExpression', |
| 2229 Program: 'Program', |
| 2230 Property: 'Property', |
| 2231 ThisExpression: 'ThisExpression', |
| 2232 UnaryExpression: 'UnaryExpression' |
| 2233 }; |
| 2234 |
| 2235 // Error messages should be identical to V8. |
| 2236 Messages = { |
| 2237 UnexpectedToken: 'Unexpected token %0', |
| 2238 UnknownLabel: 'Undefined label \'%0\'', |
| 2239 Redeclaration: '%0 \'%1\' has already been declared' |
| 2240 }; |
| 2241 |
| 2242 // Ensure the condition is true, otherwise throw an error. |
| 2243 // This is only to have a better contract semantic, i.e. another safety net |
| 2244 // to catch a logic error. The condition shall be fulfilled in normal case. |
| 2245 // Do NOT use this to enforce a certain condition on any user input. |
| 2246 |
| 2247 function assert(condition, message) { |
| 2248 if (!condition) { |
| 2249 throw new Error('ASSERT: ' + message); |
| 2250 } |
| 2251 } |
| 2252 |
| 2253 function isDecimalDigit(ch) { |
| 2254 return (ch >= 48 && ch <= 57); // 0..9 |
| 2255 } |
| 2256 |
| 2257 |
| 2258 // 7.2 White Space |
| 2259 |
| 2260 function isWhiteSpace(ch) { |
| 2261 return (ch === 32) || // space |
| 2262 (ch === 9) || // tab |
| 2263 (ch === 0xB) || |
| 2264 (ch === 0xC) || |
| 2265 (ch === 0xA0) || |
| 2266 (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u
2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCod
e(ch)) > 0); |
| 2267 } |
| 2268 |
| 2269 // 7.3 Line Terminators |
| 2270 |
| 2271 function isLineTerminator(ch) { |
| 2272 return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); |
| 2273 } |
| 2274 |
| 2275 // 7.6 Identifier Names and Identifiers |
| 2276 |
| 2277 function isIdentifierStart(ch) { |
| 2278 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
| 2279 (ch >= 65 && ch <= 90) || // A..Z |
| 2280 (ch >= 97 && ch <= 122); // a..z |
| 2281 } |
| 2282 |
| 2283 function isIdentifierPart(ch) { |
| 2284 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
| 2285 (ch >= 65 && ch <= 90) || // A..Z |
| 2286 (ch >= 97 && ch <= 122) || // a..z |
| 2287 (ch >= 48 && ch <= 57); // 0..9 |
| 2288 } |
| 2289 |
| 2290 // 7.6.1.1 Keywords |
| 2291 |
| 2292 function isKeyword(id) { |
| 2293 return (id === 'this') |
| 2294 } |
| 2295 |
| 2296 // 7.4 Comments |
| 2297 |
| 2298 function skipWhitespace() { |
| 2299 while (index < length && isWhiteSpace(source.charCodeAt(index))) { |
| 2300 ++index; |
| 2301 } |
| 2302 } |
| 2303 |
| 2304 function getIdentifier() { |
| 2305 var start, ch; |
| 2306 |
| 2307 start = index++; |
| 2308 while (index < length) { |
| 2309 ch = source.charCodeAt(index); |
| 2310 if (isIdentifierPart(ch)) { |
| 2311 ++index; |
| 2312 } else { |
| 2313 break; |
| 2314 } |
| 2315 } |
| 2316 |
| 2317 return source.slice(start, index); |
| 2318 } |
| 2319 |
| 2320 function scanIdentifier() { |
| 2321 var start, id, type; |
| 2322 |
| 2323 start = index; |
| 2324 |
| 2325 id = getIdentifier(); |
| 2326 |
| 2327 // There is no keyword or literal with only one character. |
| 2328 // Thus, it must be an identifier. |
| 2329 if (id.length === 1) { |
| 2330 type = Token.Identifier; |
| 2331 } else if (isKeyword(id)) { |
| 2332 type = Token.Keyword; |
| 2333 } else if (id === 'null') { |
| 2334 type = Token.NullLiteral; |
| 2335 } else if (id === 'true' || id === 'false') { |
| 2336 type = Token.BooleanLiteral; |
| 2337 } else { |
| 2338 type = Token.Identifier; |
| 2339 } |
| 2340 |
| 2341 return { |
| 2342 type: type, |
| 2343 value: id, |
| 2344 range: [start, index] |
| 2345 }; |
| 2346 } |
| 2347 |
| 2348 |
| 2349 // 7.7 Punctuators |
| 2350 |
| 2351 function scanPunctuator() { |
| 2352 var start = index, |
| 2353 code = source.charCodeAt(index), |
| 2354 code2, |
| 2355 ch1 = source[index], |
| 2356 ch2; |
| 2357 |
| 2358 switch (code) { |
| 2359 |
| 2360 // Check for most common single-character punctuators. |
| 2361 case 46: // . dot |
| 2362 case 40: // ( open bracket |
| 2363 case 41: // ) close bracket |
| 2364 case 59: // ; semicolon |
| 2365 case 44: // , comma |
| 2366 case 123: // { open curly brace |
| 2367 case 125: // } close curly brace |
| 2368 case 91: // [ |
| 2369 case 93: // ] |
| 2370 case 58: // : |
| 2371 case 63: // ? |
| 2372 ++index; |
| 2373 return { |
| 2374 type: Token.Punctuator, |
| 2375 value: String.fromCharCode(code), |
| 2376 range: [start, index] |
| 2377 }; |
| 2378 |
| 2379 default: |
| 2380 code2 = source.charCodeAt(index + 1); |
| 2381 |
| 2382 // '=' (char #61) marks an assignment or comparison operator. |
| 2383 if (code2 === 61) { |
| 2384 switch (code) { |
| 2385 case 37: // % |
| 2386 case 38: // & |
| 2387 case 42: // *: |
| 2388 case 43: // + |
| 2389 case 45: // - |
| 2390 case 47: // / |
| 2391 case 60: // < |
| 2392 case 62: // > |
| 2393 case 124: // | |
| 2394 index += 2; |
| 2395 return { |
| 2396 type: Token.Punctuator, |
| 2397 value: String.fromCharCode(code) + String.fromCharCode(c
ode2), |
| 2398 range: [start, index] |
| 2399 }; |
| 2400 |
| 2401 case 33: // ! |
| 2402 case 61: // = |
| 2403 index += 2; |
| 2404 |
| 2405 // !== and === |
| 2406 if (source.charCodeAt(index) === 61) { |
| 2407 ++index; |
| 2408 } |
| 2409 return { |
| 2410 type: Token.Punctuator, |
| 2411 value: source.slice(start, index), |
| 2412 range: [start, index] |
| 2413 }; |
| 2414 default: |
| 2415 break; |
| 2416 } |
| 2417 } |
| 2418 break; |
| 2419 } |
| 2420 |
| 2421 // Peek more characters. |
| 2422 |
| 2423 ch2 = source[index + 1]; |
| 2424 |
| 2425 // Other 2-character punctuators: && || |
| 2426 |
| 2427 if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { |
| 2428 index += 2; |
| 2429 return { |
| 2430 type: Token.Punctuator, |
| 2431 value: ch1 + ch2, |
| 2432 range: [start, index] |
| 2433 }; |
| 2434 } |
| 2435 |
| 2436 if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { |
| 2437 ++index; |
| 2438 return { |
| 2439 type: Token.Punctuator, |
| 2440 value: ch1, |
| 2441 range: [start, index] |
| 2442 }; |
| 2443 } |
| 2444 |
| 2445 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 2446 } |
| 2447 |
| 2448 // 7.8.3 Numeric Literals |
| 2449 function scanNumericLiteral() { |
| 2450 var number, start, ch; |
| 2451 |
| 2452 ch = source[index]; |
| 2453 assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), |
| 2454 'Numeric literal must start with a decimal digit or a decimal point'
); |
| 2455 |
| 2456 start = index; |
| 2457 number = ''; |
| 2458 if (ch !== '.') { |
| 2459 number = source[index++]; |
| 2460 ch = source[index]; |
| 2461 |
| 2462 // Hex number starts with '0x'. |
| 2463 // Octal number starts with '0'. |
| 2464 if (number === '0') { |
| 2465 // decimal number starts with '0' such as '09' is illegal. |
| 2466 if (ch && isDecimalDigit(ch.charCodeAt(0))) { |
| 2467 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 2468 } |
| 2469 } |
| 2470 |
| 2471 while (isDecimalDigit(source.charCodeAt(index))) { |
| 2472 number += source[index++]; |
| 2473 } |
| 2474 ch = source[index]; |
| 2475 } |
| 2476 |
| 2477 if (ch === '.') { |
| 2478 number += source[index++]; |
| 2479 while (isDecimalDigit(source.charCodeAt(index))) { |
| 2480 number += source[index++]; |
| 2481 } |
| 2482 ch = source[index]; |
| 2483 } |
| 2484 |
| 2485 if (ch === 'e' || ch === 'E') { |
| 2486 number += source[index++]; |
| 2487 |
| 2488 ch = source[index]; |
| 2489 if (ch === '+' || ch === '-') { |
| 2490 number += source[index++]; |
| 2491 } |
| 2492 if (isDecimalDigit(source.charCodeAt(index))) { |
| 2493 while (isDecimalDigit(source.charCodeAt(index))) { |
| 2494 number += source[index++]; |
| 2495 } |
| 2496 } else { |
| 2497 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 2498 } |
| 2499 } |
| 2500 |
| 2501 if (isIdentifierStart(source.charCodeAt(index))) { |
| 2502 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 2503 } |
| 2504 |
| 2505 return { |
| 2506 type: Token.NumericLiteral, |
| 2507 value: parseFloat(number), |
| 2508 range: [start, index] |
| 2509 }; |
| 2510 } |
| 2511 |
| 2512 // 7.8.4 String Literals |
| 2513 |
| 2514 function scanStringLiteral() { |
| 2515 var str = '', quote, start, ch, octal = false; |
| 2516 |
| 2517 quote = source[index]; |
| 2518 assert((quote === '\'' || quote === '"'), |
| 2519 'String literal must starts with a quote'); |
| 2520 |
| 2521 start = index; |
| 2522 ++index; |
| 2523 |
| 2524 while (index < length) { |
| 2525 ch = source[index++]; |
| 2526 |
| 2527 if (ch === quote) { |
| 2528 quote = ''; |
| 2529 break; |
| 2530 } else if (ch === '\\') { |
| 2531 ch = source[index++]; |
| 2532 if (!ch || !isLineTerminator(ch.charCodeAt(0))) { |
| 2533 switch (ch) { |
| 2534 case 'n': |
| 2535 str += '\n'; |
| 2536 break; |
| 2537 case 'r': |
| 2538 str += '\r'; |
| 2539 break; |
| 2540 case 't': |
| 2541 str += '\t'; |
| 2542 break; |
| 2543 case 'b': |
| 2544 str += '\b'; |
| 2545 break; |
| 2546 case 'f': |
| 2547 str += '\f'; |
| 2548 break; |
| 2549 case 'v': |
| 2550 str += '\x0B'; |
| 2551 break; |
| 2552 |
| 2553 default: |
| 2554 str += ch; |
| 2555 break; |
| 2556 } |
| 2557 } else { |
| 2558 if (ch === '\r' && source[index] === '\n') { |
| 2559 ++index; |
| 2560 } |
| 2561 } |
| 2562 } else if (isLineTerminator(ch.charCodeAt(0))) { |
| 2563 break; |
| 2564 } else { |
| 2565 str += ch; |
| 2566 } |
| 2567 } |
| 2568 |
| 2569 if (quote !== '') { |
| 2570 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 2571 } |
| 2572 |
| 2573 return { |
| 2574 type: Token.StringLiteral, |
| 2575 value: str, |
| 2576 octal: octal, |
| 2577 range: [start, index] |
| 2578 }; |
| 2579 } |
| 2580 |
| 2581 function isIdentifierName(token) { |
| 2582 return token.type === Token.Identifier || |
| 2583 token.type === Token.Keyword || |
| 2584 token.type === Token.BooleanLiteral || |
| 2585 token.type === Token.NullLiteral; |
| 2586 } |
| 2587 |
| 2588 function advance() { |
| 2589 var ch; |
| 2590 |
| 2591 skipWhitespace(); |
| 2592 |
| 2593 if (index >= length) { |
| 2594 return { |
| 2595 type: Token.EOF, |
| 2596 range: [index, index] |
| 2597 }; |
| 2598 } |
| 2599 |
| 2600 ch = source.charCodeAt(index); |
| 2601 |
| 2602 // Very common: ( and ) and ; |
| 2603 if (ch === 40 || ch === 41 || ch === 58) { |
| 2604 return scanPunctuator(); |
| 2605 } |
| 2606 |
| 2607 // String literal starts with single quote (#39) or double quote (#34). |
| 2608 if (ch === 39 || ch === 34) { |
| 2609 return scanStringLiteral(); |
| 2610 } |
| 2611 |
| 2612 if (isIdentifierStart(ch)) { |
| 2613 return scanIdentifier(); |
| 2614 } |
| 2615 |
| 2616 // Dot (.) char #46 can also start a floating-point number, hence the ne
ed |
| 2617 // to check the next character. |
| 2618 if (ch === 46) { |
| 2619 if (isDecimalDigit(source.charCodeAt(index + 1))) { |
| 2620 return scanNumericLiteral(); |
| 2621 } |
| 2622 return scanPunctuator(); |
| 2623 } |
| 2624 |
| 2625 if (isDecimalDigit(ch)) { |
| 2626 return scanNumericLiteral(); |
| 2627 } |
| 2628 |
| 2629 return scanPunctuator(); |
| 2630 } |
| 2631 |
| 2632 function lex() { |
| 2633 var token; |
| 2634 |
| 2635 token = lookahead; |
| 2636 index = token.range[1]; |
| 2637 |
| 2638 lookahead = advance(); |
| 2639 |
| 2640 index = token.range[1]; |
| 2641 |
| 2642 return token; |
| 2643 } |
| 2644 |
| 2645 function peek() { |
| 2646 var pos; |
| 2647 |
| 2648 pos = index; |
| 2649 lookahead = advance(); |
| 2650 index = pos; |
| 2651 } |
| 2652 |
| 2653 // Throw an exception |
| 2654 |
| 2655 function throwError(token, messageFormat) { |
| 2656 var error, |
| 2657 args = Array.prototype.slice.call(arguments, 2), |
| 2658 msg = messageFormat.replace( |
| 2659 /%(\d)/g, |
| 2660 function (whole, index) { |
| 2661 assert(index < args.length, 'Message reference must be in ra
nge'); |
| 2662 return args[index]; |
| 2663 } |
| 2664 ); |
| 2665 |
| 2666 error = new Error(msg); |
| 2667 error.index = index; |
| 2668 error.description = msg; |
| 2669 throw error; |
| 2670 } |
| 2671 |
| 2672 // Throw an exception because of the token. |
| 2673 |
| 2674 function throwUnexpected(token) { |
| 2675 throwError(token, Messages.UnexpectedToken, token.value); |
| 2676 } |
| 2677 |
| 2678 // Expect the next token to match the specified punctuator. |
| 2679 // If not, an exception will be thrown. |
| 2680 |
| 2681 function expect(value) { |
| 2682 var token = lex(); |
| 2683 if (token.type !== Token.Punctuator || token.value !== value) { |
| 2684 throwUnexpected(token); |
| 2685 } |
| 2686 } |
| 2687 |
| 2688 // Return true if the next token matches the specified punctuator. |
| 2689 |
| 2690 function match(value) { |
| 2691 return lookahead.type === Token.Punctuator && lookahead.value === value; |
| 2692 } |
| 2693 |
| 2694 // Return true if the next token matches the specified keyword |
| 2695 |
| 2696 function matchKeyword(keyword) { |
| 2697 return lookahead.type === Token.Keyword && lookahead.value === keyword; |
| 2698 } |
| 2699 |
| 2700 function consumeSemicolon() { |
| 2701 // Catch the very common case first: immediately a semicolon (char #59). |
| 2702 if (source.charCodeAt(index) === 59) { |
| 2703 lex(); |
| 2704 return; |
| 2705 } |
| 2706 |
| 2707 skipWhitespace(); |
| 2708 |
| 2709 if (match(';')) { |
| 2710 lex(); |
| 2711 return; |
| 2712 } |
| 2713 |
| 2714 if (lookahead.type !== Token.EOF && !match('}')) { |
| 2715 throwUnexpected(lookahead); |
| 2716 } |
| 2717 } |
| 2718 |
| 2719 // 11.1.4 Array Initialiser |
| 2720 |
| 2721 function parseArrayInitialiser() { |
| 2722 var elements = []; |
| 2723 |
| 2724 expect('['); |
| 2725 |
| 2726 while (!match(']')) { |
| 2727 if (match(',')) { |
| 2728 lex(); |
| 2729 elements.push(null); |
| 2730 } else { |
| 2731 elements.push(parseExpression()); |
| 2732 |
| 2733 if (!match(']')) { |
| 2734 expect(','); |
| 2735 } |
| 2736 } |
| 2737 } |
| 2738 |
| 2739 expect(']'); |
| 2740 |
| 2741 return delegate.createArrayExpression(elements); |
| 2742 } |
| 2743 |
| 2744 // 11.1.5 Object Initialiser |
| 2745 |
| 2746 function parseObjectPropertyKey() { |
| 2747 var token; |
| 2748 |
| 2749 skipWhitespace(); |
| 2750 token = lex(); |
| 2751 |
| 2752 // Note: This function is called only from parseObjectProperty(), where |
| 2753 // EOF and Punctuator tokens are already filtered out. |
| 2754 if (token.type === Token.StringLiteral || token.type === Token.NumericLi
teral) { |
| 2755 return delegate.createLiteral(token); |
| 2756 } |
| 2757 |
| 2758 return delegate.createIdentifier(token.value); |
| 2759 } |
| 2760 |
| 2761 function parseObjectProperty() { |
| 2762 var token, key; |
| 2763 |
| 2764 token = lookahead; |
| 2765 skipWhitespace(); |
| 2766 |
| 2767 if (token.type === Token.EOF || token.type === Token.Punctuator) { |
| 2768 throwUnexpected(token); |
| 2769 } |
| 2770 |
| 2771 key = parseObjectPropertyKey(); |
| 2772 expect(':'); |
| 2773 return delegate.createProperty('init', key, parseExpression()); |
| 2774 } |
| 2775 |
| 2776 function parseObjectInitialiser() { |
| 2777 var properties = []; |
| 2778 |
| 2779 expect('{'); |
| 2780 |
| 2781 while (!match('}')) { |
| 2782 properties.push(parseObjectProperty()); |
| 2783 |
| 2784 if (!match('}')) { |
| 2785 expect(','); |
| 2786 } |
| 2787 } |
| 2788 |
| 2789 expect('}'); |
| 2790 |
| 2791 return delegate.createObjectExpression(properties); |
| 2792 } |
| 2793 |
| 2794 // 11.1.6 The Grouping Operator |
| 2795 |
| 2796 function parseGroupExpression() { |
| 2797 var expr; |
| 2798 |
| 2799 expect('('); |
| 2800 |
| 2801 expr = parseExpression(); |
| 2802 |
| 2803 expect(')'); |
| 2804 |
| 2805 return expr; |
| 2806 } |
| 2807 |
| 2808 |
| 2809 // 11.1 Primary Expressions |
| 2810 |
| 2811 function parsePrimaryExpression() { |
| 2812 var type, token, expr; |
| 2813 |
| 2814 if (match('(')) { |
| 2815 return parseGroupExpression(); |
| 2816 } |
| 2817 |
| 2818 type = lookahead.type; |
| 2819 |
| 2820 if (type === Token.Identifier) { |
| 2821 expr = delegate.createIdentifier(lex().value); |
| 2822 } else if (type === Token.StringLiteral || type === Token.NumericLiteral
) { |
| 2823 expr = delegate.createLiteral(lex()); |
| 2824 } else if (type === Token.Keyword) { |
| 2825 if (matchKeyword('this')) { |
| 2826 lex(); |
| 2827 expr = delegate.createThisExpression(); |
| 2828 } |
| 2829 } else if (type === Token.BooleanLiteral) { |
| 2830 token = lex(); |
| 2831 token.value = (token.value === 'true'); |
| 2832 expr = delegate.createLiteral(token); |
| 2833 } else if (type === Token.NullLiteral) { |
| 2834 token = lex(); |
| 2835 token.value = null; |
| 2836 expr = delegate.createLiteral(token); |
| 2837 } else if (match('[')) { |
| 2838 expr = parseArrayInitialiser(); |
| 2839 } else if (match('{')) { |
| 2840 expr = parseObjectInitialiser(); |
| 2841 } |
| 2842 |
| 2843 if (expr) { |
| 2844 return expr; |
| 2845 } |
| 2846 |
| 2847 throwUnexpected(lex()); |
| 2848 } |
| 2849 |
| 2850 // 11.2 Left-Hand-Side Expressions |
| 2851 |
| 2852 function parseArguments() { |
| 2853 var args = []; |
| 2854 |
| 2855 expect('('); |
| 2856 |
| 2857 if (!match(')')) { |
| 2858 while (index < length) { |
| 2859 args.push(parseExpression()); |
| 2860 if (match(')')) { |
| 2861 break; |
| 2862 } |
| 2863 expect(','); |
| 2864 } |
| 2865 } |
| 2866 |
| 2867 expect(')'); |
| 2868 |
| 2869 return args; |
| 2870 } |
| 2871 |
| 2872 function parseNonComputedProperty() { |
| 2873 var token; |
| 2874 |
| 2875 token = lex(); |
| 2876 |
| 2877 if (!isIdentifierName(token)) { |
| 2878 throwUnexpected(token); |
| 2879 } |
| 2880 |
| 2881 return delegate.createIdentifier(token.value); |
| 2882 } |
| 2883 |
| 2884 function parseNonComputedMember() { |
| 2885 expect('.'); |
| 2886 |
| 2887 return parseNonComputedProperty(); |
| 2888 } |
| 2889 |
| 2890 function parseComputedMember() { |
| 2891 var expr; |
| 2892 |
| 2893 expect('['); |
| 2894 |
| 2895 expr = parseExpression(); |
| 2896 |
| 2897 expect(']'); |
| 2898 |
| 2899 return expr; |
| 2900 } |
| 2901 |
| 2902 function parseLeftHandSideExpression() { |
| 2903 var expr, args, property; |
| 2904 |
| 2905 expr = parsePrimaryExpression(); |
| 2906 |
| 2907 while (true) { |
| 2908 if (match('[')) { |
| 2909 property = parseComputedMember(); |
| 2910 expr = delegate.createMemberExpression('[', expr, property); |
| 2911 } else if (match('.')) { |
| 2912 property = parseNonComputedMember(); |
| 2913 expr = delegate.createMemberExpression('.', expr, property); |
| 2914 } else if (match('(')) { |
| 2915 args = parseArguments(); |
| 2916 expr = delegate.createCallExpression(expr, args); |
| 2917 } else { |
| 2918 break; |
| 2919 } |
| 2920 } |
| 2921 |
| 2922 return expr; |
| 2923 } |
| 2924 |
| 2925 // 11.3 Postfix Expressions |
| 2926 |
| 2927 var parsePostfixExpression = parseLeftHandSideExpression; |
| 2928 |
| 2929 // 11.4 Unary Operators |
| 2930 |
| 2931 function parseUnaryExpression() { |
| 2932 var token, expr; |
| 2933 |
| 2934 if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyw
ord) { |
| 2935 expr = parsePostfixExpression(); |
| 2936 } else if (match('+') || match('-') || match('!')) { |
| 2937 token = lex(); |
| 2938 expr = parseUnaryExpression(); |
| 2939 expr = delegate.createUnaryExpression(token.value, expr); |
| 2940 } else if (matchKeyword('delete') || matchKeyword('void') || matchKeywor
d('typeof')) { |
| 2941 throwError({}, Messages.UnexpectedToken); |
| 2942 } else { |
| 2943 expr = parsePostfixExpression(); |
| 2944 } |
| 2945 |
| 2946 return expr; |
| 2947 } |
| 2948 |
| 2949 function binaryPrecedence(token) { |
| 2950 var prec = 0; |
| 2951 |
| 2952 if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { |
| 2953 return 0; |
| 2954 } |
| 2955 |
| 2956 switch (token.value) { |
| 2957 case '||': |
| 2958 prec = 1; |
| 2959 break; |
| 2960 |
| 2961 case '&&': |
| 2962 prec = 2; |
| 2963 break; |
| 2964 |
| 2965 case '==': |
| 2966 case '!=': |
| 2967 case '===': |
| 2968 case '!==': |
| 2969 prec = 6; |
| 2970 break; |
| 2971 |
| 2972 case '<': |
| 2973 case '>': |
| 2974 case '<=': |
| 2975 case '>=': |
| 2976 case 'instanceof': |
| 2977 prec = 7; |
| 2978 break; |
| 2979 |
| 2980 case 'in': |
| 2981 prec = 7; |
| 2982 break; |
| 2983 |
| 2984 case '+': |
| 2985 case '-': |
| 2986 prec = 9; |
| 2987 break; |
| 2988 |
| 2989 case '*': |
| 2990 case '/': |
| 2991 case '%': |
| 2992 prec = 11; |
| 2993 break; |
| 2994 |
| 2995 default: |
| 2996 break; |
| 2997 } |
| 2998 |
| 2999 return prec; |
| 3000 } |
| 3001 |
| 3002 // 11.5 Multiplicative Operators |
| 3003 // 11.6 Additive Operators |
| 3004 // 11.7 Bitwise Shift Operators |
| 3005 // 11.8 Relational Operators |
| 3006 // 11.9 Equality Operators |
| 3007 // 11.10 Binary Bitwise Operators |
| 3008 // 11.11 Binary Logical Operators |
| 3009 |
| 3010 function parseBinaryExpression() { |
| 3011 var expr, token, prec, stack, right, operator, left, i; |
| 3012 |
| 3013 left = parseUnaryExpression(); |
| 3014 |
| 3015 token = lookahead; |
| 3016 prec = binaryPrecedence(token); |
| 3017 if (prec === 0) { |
| 3018 return left; |
| 3019 } |
| 3020 token.prec = prec; |
| 3021 lex(); |
| 3022 |
| 3023 right = parseUnaryExpression(); |
| 3024 |
| 3025 stack = [left, token, right]; |
| 3026 |
| 3027 while ((prec = binaryPrecedence(lookahead)) > 0) { |
| 3028 |
| 3029 // Reduce: make a binary expression from the three topmost entries. |
| 3030 while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec))
{ |
| 3031 right = stack.pop(); |
| 3032 operator = stack.pop().value; |
| 3033 left = stack.pop(); |
| 3034 expr = delegate.createBinaryExpression(operator, left, right); |
| 3035 stack.push(expr); |
| 3036 } |
| 3037 |
| 3038 // Shift. |
| 3039 token = lex(); |
| 3040 token.prec = prec; |
| 3041 stack.push(token); |
| 3042 expr = parseUnaryExpression(); |
| 3043 stack.push(expr); |
| 3044 } |
| 3045 |
| 3046 // Final reduce to clean-up the stack. |
| 3047 i = stack.length - 1; |
| 3048 expr = stack[i]; |
| 3049 while (i > 1) { |
| 3050 expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i -
2], expr); |
| 3051 i -= 2; |
| 3052 } |
| 3053 |
| 3054 return expr; |
| 3055 } |
| 3056 |
| 3057 |
| 3058 // 11.12 Conditional Operator |
| 3059 |
| 3060 function parseConditionalExpression() { |
| 3061 var expr, consequent, alternate; |
| 3062 |
| 3063 expr = parseBinaryExpression(); |
| 3064 |
| 3065 if (match('?')) { |
| 3066 lex(); |
| 3067 consequent = parseConditionalExpression(); |
| 3068 expect(':'); |
| 3069 alternate = parseConditionalExpression(); |
| 3070 |
| 3071 expr = delegate.createConditionalExpression(expr, consequent, altern
ate); |
| 3072 } |
| 3073 |
| 3074 return expr; |
| 3075 } |
| 3076 |
| 3077 // Simplification since we do not support AssignmentExpression. |
| 3078 var parseExpression = parseConditionalExpression; |
| 3079 |
| 3080 // Polymer Syntax extensions |
| 3081 |
| 3082 // Filter :: |
| 3083 // Identifier |
| 3084 // Identifier "(" ")" |
| 3085 // Identifier "(" FilterArguments ")" |
| 3086 |
| 3087 function parseFilter() { |
| 3088 var identifier, args; |
| 3089 |
| 3090 identifier = lex(); |
| 3091 |
| 3092 if (identifier.type !== Token.Identifier) { |
| 3093 throwUnexpected(identifier); |
| 3094 } |
| 3095 |
| 3096 args = match('(') ? parseArguments() : []; |
| 3097 |
| 3098 return delegate.createFilter(identifier.value, args); |
| 3099 } |
| 3100 |
| 3101 // Filters :: |
| 3102 // "|" Filter |
| 3103 // Filters "|" Filter |
| 3104 |
| 3105 function parseFilters() { |
| 3106 while (match('|')) { |
| 3107 lex(); |
| 3108 parseFilter(); |
| 3109 } |
| 3110 } |
| 3111 |
| 3112 // TopLevel :: |
| 3113 // LabelledExpressions |
| 3114 // AsExpression |
| 3115 // InExpression |
| 3116 // FilterExpression |
| 3117 |
| 3118 // AsExpression :: |
| 3119 // FilterExpression as Identifier |
| 3120 |
| 3121 // InExpression :: |
| 3122 // Identifier, Identifier in FilterExpression |
| 3123 // Identifier in FilterExpression |
| 3124 |
| 3125 // FilterExpression :: |
| 3126 // Expression |
| 3127 // Expression Filters |
| 3128 |
| 3129 function parseTopLevel() { |
| 3130 skipWhitespace(); |
| 3131 peek(); |
| 3132 |
| 3133 var expr = parseExpression(); |
| 3134 if (expr) { |
| 3135 if (lookahead.value === ',' || lookahead.value == 'in' && |
| 3136 expr.type === Syntax.Identifier) { |
| 3137 parseInExpression(expr); |
| 3138 } else { |
| 3139 parseFilters(); |
| 3140 if (lookahead.value === 'as') { |
| 3141 parseAsExpression(expr); |
| 3142 } else { |
| 3143 delegate.createTopLevel(expr); |
| 3144 } |
| 3145 } |
| 3146 } |
| 3147 |
| 3148 if (lookahead.type !== Token.EOF) { |
| 3149 throwUnexpected(lookahead); |
| 3150 } |
| 3151 } |
| 3152 |
| 3153 function parseAsExpression(expr) { |
| 3154 lex(); // as |
| 3155 var identifier = lex().value; |
| 3156 delegate.createAsExpression(expr, identifier); |
| 3157 } |
| 3158 |
| 3159 function parseInExpression(identifier) { |
| 3160 var indexName; |
| 3161 if (lookahead.value === ',') { |
| 3162 lex(); |
| 3163 if (lookahead.type !== Token.Identifier) |
| 3164 throwUnexpected(lookahead); |
| 3165 indexName = lex().value; |
| 3166 } |
| 3167 |
| 3168 lex(); // in |
| 3169 var expr = parseExpression(); |
| 3170 parseFilters(); |
| 3171 delegate.createInExpression(identifier.name, indexName, expr); |
| 3172 } |
| 3173 |
| 3174 function parse(code, inDelegate) { |
| 3175 delegate = inDelegate; |
| 3176 source = code; |
| 3177 index = 0; |
| 3178 length = source.length; |
| 3179 lookahead = null; |
| 3180 state = { |
| 3181 labelSet: {} |
| 3182 }; |
| 3183 |
| 3184 return parseTopLevel(); |
| 3185 } |
| 3186 |
| 3187 global.esprima = { |
| 3188 parse: parse |
| 3189 }; |
| 3190 })(this); |
| 3191 |
| 3192 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 3193 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 3194 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 3195 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 3196 // Code distributed by Google as part of the polymer project is also |
| 3197 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 3198 |
| 3199 (function (global) { |
| 3200 'use strict'; |
| 3201 |
| 3202 function prepareBinding(expressionText, name, node, filterRegistry) { |
| 3203 var expression; |
| 3204 try { |
| 3205 expression = getExpression(expressionText); |
| 3206 if (expression.scopeIdent && |
| 3207 (node.nodeType !== Node.ELEMENT_NODE || |
| 3208 node.tagName !== 'TEMPLATE' || |
| 3209 (name !== 'bind' && name !== 'repeat'))) { |
| 3210 throw Error('as and in can only be used within <template bind/repeat>'); |
| 3211 } |
| 3212 } catch (ex) { |
| 3213 console.error('Invalid expression syntax: ' + expressionText, ex); |
| 3214 return; |
| 3215 } |
| 3216 |
| 3217 return function(model, node, oneTime) { |
| 3218 var binding = expression.getBinding(model, filterRegistry, oneTime); |
| 3219 if (expression.scopeIdent && binding) { |
| 3220 node.polymerExpressionScopeIdent_ = expression.scopeIdent; |
| 3221 if (expression.indexIdent) |
| 3222 node.polymerExpressionIndexIdent_ = expression.indexIdent; |
| 3223 } |
| 3224 |
| 3225 return binding; |
| 3226 } |
| 3227 } |
| 3228 |
| 3229 // TODO(rafaelw): Implement simple LRU. |
| 3230 var expressionParseCache = Object.create(null); |
| 3231 |
| 3232 function getExpression(expressionText) { |
| 3233 var expression = expressionParseCache[expressionText]; |
| 3234 if (!expression) { |
| 3235 var delegate = new ASTDelegate(); |
| 3236 esprima.parse(expressionText, delegate); |
| 3237 expression = new Expression(delegate); |
| 3238 expressionParseCache[expressionText] = expression; |
| 3239 } |
| 3240 return expression; |
| 3241 } |
| 3242 |
| 3243 function Literal(value) { |
| 3244 this.value = value; |
| 3245 this.valueFn_ = undefined; |
| 3246 } |
| 3247 |
| 3248 Literal.prototype = { |
| 3249 valueFn: function() { |
| 3250 if (!this.valueFn_) { |
| 3251 var value = this.value; |
| 3252 this.valueFn_ = function() { |
| 3253 return value; |
| 3254 } |
| 3255 } |
| 3256 |
| 3257 return this.valueFn_; |
| 3258 } |
| 3259 } |
| 3260 |
| 3261 function IdentPath(name) { |
| 3262 this.name = name; |
| 3263 this.path = Path.get(name); |
| 3264 } |
| 3265 |
| 3266 IdentPath.prototype = { |
| 3267 valueFn: function() { |
| 3268 if (!this.valueFn_) { |
| 3269 var name = this.name; |
| 3270 var path = this.path; |
| 3271 this.valueFn_ = function(model, observer) { |
| 3272 if (observer) |
| 3273 observer.addPath(model, path); |
| 3274 |
| 3275 return path.getValueFrom(model); |
| 3276 } |
| 3277 } |
| 3278 |
| 3279 return this.valueFn_; |
| 3280 }, |
| 3281 |
| 3282 setValue: function(model, newValue) { |
| 3283 if (this.path.length == 1) |
| 3284 model = findScope(model, this.path[0]); |
| 3285 |
| 3286 return this.path.setValueFrom(model, newValue); |
| 3287 } |
| 3288 }; |
| 3289 |
| 3290 function MemberExpression(object, property, accessor) { |
| 3291 this.computed = accessor == '['; |
| 3292 |
| 3293 this.dynamicDeps = typeof object == 'function' || |
| 3294 object.dynamicDeps || |
| 3295 (this.computed && !(property instanceof Literal)); |
| 3296 |
| 3297 this.simplePath = |
| 3298 !this.dynamicDeps && |
| 3299 (property instanceof IdentPath || property instanceof Literal) && |
| 3300 (object instanceof MemberExpression || object instanceof IdentPath); |
| 3301 |
| 3302 this.object = this.simplePath ? object : getFn(object); |
| 3303 this.property = !this.computed || this.simplePath ? |
| 3304 property : getFn(property); |
| 3305 } |
| 3306 |
| 3307 MemberExpression.prototype = { |
| 3308 get fullPath() { |
| 3309 if (!this.fullPath_) { |
| 3310 |
| 3311 var parts = this.object instanceof MemberExpression ? |
| 3312 this.object.fullPath.slice() : [this.object.name]; |
| 3313 parts.push(this.property instanceof IdentPath ? |
| 3314 this.property.name : this.property.value); |
| 3315 this.fullPath_ = Path.get(parts); |
| 3316 } |
| 3317 |
| 3318 return this.fullPath_; |
| 3319 }, |
| 3320 |
| 3321 valueFn: function() { |
| 3322 if (!this.valueFn_) { |
| 3323 var object = this.object; |
| 3324 |
| 3325 if (this.simplePath) { |
| 3326 var path = this.fullPath; |
| 3327 |
| 3328 this.valueFn_ = function(model, observer) { |
| 3329 if (observer) |
| 3330 observer.addPath(model, path); |
| 3331 |
| 3332 return path.getValueFrom(model); |
| 3333 }; |
| 3334 } else if (!this.computed) { |
| 3335 var path = Path.get(this.property.name); |
| 3336 |
| 3337 this.valueFn_ = function(model, observer, filterRegistry) { |
| 3338 var context = object(model, observer, filterRegistry); |
| 3339 |
| 3340 if (observer) |
| 3341 observer.addPath(context, path); |
| 3342 |
| 3343 return path.getValueFrom(context); |
| 3344 } |
| 3345 } else { |
| 3346 // Computed property. |
| 3347 var property = this.property; |
| 3348 |
| 3349 this.valueFn_ = function(model, observer, filterRegistry) { |
| 3350 var context = object(model, observer, filterRegistry); |
| 3351 var propName = property(model, observer, filterRegistry); |
| 3352 if (observer) |
| 3353 observer.addPath(context, [propName]); |
| 3354 |
| 3355 return context ? context[propName] : undefined; |
| 3356 }; |
| 3357 } |
| 3358 } |
| 3359 return this.valueFn_; |
| 3360 }, |
| 3361 |
| 3362 setValue: function(model, newValue) { |
| 3363 if (this.simplePath) { |
| 3364 this.fullPath.setValueFrom(model, newValue); |
| 3365 return newValue; |
| 3366 } |
| 3367 |
| 3368 var object = this.object(model); |
| 3369 var propName = this.property instanceof IdentPath ? this.property.name : |
| 3370 this.property(model); |
| 3371 return object[propName] = newValue; |
| 3372 } |
| 3373 }; |
| 3374 |
| 3375 function Filter(name, args) { |
| 3376 this.name = name; |
| 3377 this.args = []; |
| 3378 for (var i = 0; i < args.length; i++) { |
| 3379 this.args[i] = getFn(args[i]); |
| 3380 } |
| 3381 } |
| 3382 |
| 3383 Filter.prototype = { |
| 3384 transform: function(model, observer, filterRegistry, toModelDirection, |
| 3385 initialArgs) { |
| 3386 var fn = filterRegistry[this.name]; |
| 3387 var context = model; |
| 3388 if (fn) { |
| 3389 context = undefined; |
| 3390 } else { |
| 3391 fn = context[this.name]; |
| 3392 if (!fn) { |
| 3393 console.error('Cannot find function or filter: ' + this.name); |
| 3394 return; |
| 3395 } |
| 3396 } |
| 3397 |
| 3398 // If toModelDirection is falsey, then the "normal" (dom-bound) direction |
| 3399 // is used. Otherwise, it looks for a 'toModel' property function on the |
| 3400 // object. |
| 3401 if (toModelDirection) { |
| 3402 fn = fn.toModel; |
| 3403 } else if (typeof fn.toDOM == 'function') { |
| 3404 fn = fn.toDOM; |
| 3405 } |
| 3406 |
| 3407 if (typeof fn != 'function') { |
| 3408 console.error('Cannot find function or filter: ' + this.name); |
| 3409 return; |
| 3410 } |
| 3411 |
| 3412 var args = initialArgs || []; |
| 3413 for (var i = 0; i < this.args.length; i++) { |
| 3414 args.push(getFn(this.args[i])(model, observer, filterRegistry)); |
| 3415 } |
| 3416 |
| 3417 return fn.apply(context, args); |
| 3418 } |
| 3419 }; |
| 3420 |
| 3421 function notImplemented() { throw Error('Not Implemented'); } |
| 3422 |
| 3423 var unaryOperators = { |
| 3424 '+': function(v) { return +v; }, |
| 3425 '-': function(v) { return -v; }, |
| 3426 '!': function(v) { return !v; } |
| 3427 }; |
| 3428 |
| 3429 var binaryOperators = { |
| 3430 '+': function(l, r) { return l+r; }, |
| 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 }; |
| 3446 |
| 3447 function getFn(arg) { |
| 3448 return typeof arg == 'function' ? arg : arg.valueFn(); |
| 3449 } |
| 3450 |
| 3451 function ASTDelegate() { |
| 3452 this.expression = null; |
| 3453 this.filters = []; |
| 3454 this.deps = {}; |
| 3455 this.currentPath = undefined; |
| 3456 this.scopeIdent = undefined; |
| 3457 this.indexIdent = undefined; |
| 3458 this.dynamicDeps = false; |
| 3459 } |
| 3460 |
| 3461 ASTDelegate.prototype = { |
| 3462 createUnaryExpression: function(op, argument) { |
| 3463 if (!unaryOperators[op]) |
| 3464 throw Error('Disallowed operator: ' + op); |
| 3465 |
| 3466 argument = getFn(argument); |
| 3467 |
| 3468 return function(model, observer, filterRegistry) { |
| 3469 return unaryOperators[op](argument(model, observer, filterRegistry)); |
| 3470 }; |
| 3471 }, |
| 3472 |
| 3473 createBinaryExpression: function(op, left, right) { |
| 3474 if (!binaryOperators[op]) |
| 3475 throw Error('Disallowed operator: ' + op); |
| 3476 |
| 3477 left = getFn(left); |
| 3478 right = getFn(right); |
| 3479 |
| 3480 switch (op) { |
| 3481 case '||': |
| 3482 this.dynamicDeps = true; |
| 3483 return function(model, observer, filterRegistry) { |
| 3484 return left(model, observer, filterRegistry) || |
| 3485 right(model, observer, filterRegistry); |
| 3486 }; |
| 3487 case '&&': |
| 3488 this.dynamicDeps = true; |
| 3489 return function(model, observer, filterRegistry) { |
| 3490 return left(model, observer, filterRegistry) && |
| 3491 right(model, observer, filterRegistry); |
| 3492 }; |
| 3493 } |
| 3494 |
| 3495 return function(model, observer, filterRegistry) { |
| 3496 return binaryOperators[op](left(model, observer, filterRegistry), |
| 3497 right(model, observer, filterRegistry)); |
| 3498 }; |
| 3499 }, |
| 3500 |
| 3501 createConditionalExpression: function(test, consequent, alternate) { |
| 3502 test = getFn(test); |
| 3503 consequent = getFn(consequent); |
| 3504 alternate = getFn(alternate); |
| 3505 |
| 3506 this.dynamicDeps = true; |
| 3507 |
| 3508 return function(model, observer, filterRegistry) { |
| 3509 return test(model, observer, filterRegistry) ? |
| 3510 consequent(model, observer, filterRegistry) : |
| 3511 alternate(model, observer, filterRegistry); |
| 3512 } |
| 3513 }, |
| 3514 |
| 3515 createIdentifier: function(name) { |
| 3516 var ident = new IdentPath(name); |
| 3517 ident.type = 'Identifier'; |
| 3518 return ident; |
| 3519 }, |
| 3520 |
| 3521 createMemberExpression: function(accessor, object, property) { |
| 3522 var ex = new MemberExpression(object, property, accessor); |
| 3523 if (ex.dynamicDeps) |
| 3524 this.dynamicDeps = true; |
| 3525 return ex; |
| 3526 }, |
| 3527 |
| 3528 createCallExpression: function(expression, args) { |
| 3529 if (!(expression instanceof IdentPath)) |
| 3530 throw Error('Only identifier function invocations are allowed'); |
| 3531 |
| 3532 var filter = new Filter(expression.name, args); |
| 3533 |
| 3534 return function(model, observer, filterRegistry) { |
| 3535 return filter.transform(model, observer, filterRegistry, false); |
| 3536 }; |
| 3537 }, |
| 3538 |
| 3539 createLiteral: function(token) { |
| 3540 return new Literal(token.value); |
| 3541 }, |
| 3542 |
| 3543 createArrayExpression: function(elements) { |
| 3544 for (var i = 0; i < elements.length; i++) |
| 3545 elements[i] = getFn(elements[i]); |
| 3546 |
| 3547 return function(model, observer, filterRegistry) { |
| 3548 var arr = [] |
| 3549 for (var i = 0; i < elements.length; i++) |
| 3550 arr.push(elements[i](model, observer, filterRegistry)); |
| 3551 return arr; |
| 3552 } |
| 3553 }, |
| 3554 |
| 3555 createProperty: function(kind, key, value) { |
| 3556 return { |
| 3557 key: key instanceof IdentPath ? key.name : key.value, |
| 3558 value: value |
| 3559 }; |
| 3560 }, |
| 3561 |
| 3562 createObjectExpression: function(properties) { |
| 3563 for (var i = 0; i < properties.length; i++) |
| 3564 properties[i].value = getFn(properties[i].value); |
| 3565 |
| 3566 return function(model, observer, filterRegistry) { |
| 3567 var obj = {}; |
| 3568 for (var i = 0; i < properties.length; i++) |
| 3569 obj[properties[i].key] = |
| 3570 properties[i].value(model, observer, filterRegistry); |
| 3571 return obj; |
| 3572 } |
| 3573 }, |
| 3574 |
| 3575 createFilter: function(name, args) { |
| 3576 this.filters.push(new Filter(name, args)); |
| 3577 }, |
| 3578 |
| 3579 createAsExpression: function(expression, scopeIdent) { |
| 3580 this.expression = expression; |
| 3581 this.scopeIdent = scopeIdent; |
| 3582 }, |
| 3583 |
| 3584 createInExpression: function(scopeIdent, indexIdent, expression) { |
| 3585 this.expression = expression; |
| 3586 this.scopeIdent = scopeIdent; |
| 3587 this.indexIdent = indexIdent; |
| 3588 }, |
| 3589 |
| 3590 createTopLevel: function(expression) { |
| 3591 this.expression = expression; |
| 3592 }, |
| 3593 |
| 3594 createThisExpression: notImplemented |
| 3595 } |
| 3596 |
| 3597 function ConstantObservable(value) { |
| 3598 this.value_ = value; |
| 3599 } |
| 3600 |
| 3601 ConstantObservable.prototype = { |
| 3602 open: function() { return this.value_; }, |
| 3603 discardChanges: function() { return this.value_; }, |
| 3604 deliver: function() {}, |
| 3605 close: function() {}, |
| 3606 } |
| 3607 |
| 3608 function Expression(delegate) { |
| 3609 this.scopeIdent = delegate.scopeIdent; |
| 3610 this.indexIdent = delegate.indexIdent; |
| 3611 |
| 3612 if (!delegate.expression) |
| 3613 throw Error('No expression found.'); |
| 3614 |
| 3615 this.expression = delegate.expression; |
| 3616 getFn(this.expression); // forces enumeration of path dependencies |
| 3617 |
| 3618 this.filters = delegate.filters; |
| 3619 this.dynamicDeps = delegate.dynamicDeps; |
| 3620 } |
| 3621 |
| 3622 Expression.prototype = { |
| 3623 getBinding: function(model, filterRegistry, oneTime) { |
| 3624 if (oneTime) |
| 3625 return this.getValue(model, undefined, filterRegistry); |
| 3626 |
| 3627 var observer = new CompoundObserver(); |
| 3628 // captures deps. |
| 3629 var firstValue = this.getValue(model, observer, filterRegistry); |
| 3630 var firstTime = true; |
| 3631 var self = this; |
| 3632 |
| 3633 function valueFn() { |
| 3634 // deps cannot have changed on first value retrieval. |
| 3635 if (firstTime) { |
| 3636 firstTime = false; |
| 3637 return firstValue; |
| 3638 } |
| 3639 |
| 3640 if (self.dynamicDeps) |
| 3641 observer.startReset(); |
| 3642 |
| 3643 var value = self.getValue(model, |
| 3644 self.dynamicDeps ? observer : undefined, |
| 3645 filterRegistry); |
| 3646 if (self.dynamicDeps) |
| 3647 observer.finishReset(); |
| 3648 |
| 3649 return value; |
| 3650 } |
| 3651 |
| 3652 function setValueFn(newValue) { |
| 3653 self.setValue(model, newValue, filterRegistry); |
| 3654 return newValue; |
| 3655 } |
| 3656 |
| 3657 return new ObserverTransform(observer, valueFn, setValueFn, true); |
| 3658 }, |
| 3659 |
| 3660 getValue: function(model, observer, filterRegistry) { |
| 3661 var value = getFn(this.expression)(model, observer, filterRegistry); |
| 3662 for (var i = 0; i < this.filters.length; i++) { |
| 3663 value = this.filters[i].transform(model, observer, filterRegistry, |
| 3664 false, [value]); |
| 3665 } |
| 3666 |
| 3667 return value; |
| 3668 }, |
| 3669 |
| 3670 setValue: function(model, newValue, filterRegistry) { |
| 3671 var count = this.filters ? this.filters.length : 0; |
| 3672 while (count-- > 0) { |
| 3673 newValue = this.filters[count].transform(model, undefined, |
| 3674 filterRegistry, true, [newValue]); |
| 3675 } |
| 3676 |
| 3677 if (this.expression.setValue) |
| 3678 return this.expression.setValue(model, newValue); |
| 3679 } |
| 3680 } |
| 3681 |
| 3682 /** |
| 3683 * Converts a style property name to a css property name. For example: |
| 3684 * "WebkitUserSelect" to "-webkit-user-select" |
| 3685 */ |
| 3686 function convertStylePropertyName(name) { |
| 3687 return String(name).replace(/[A-Z]/g, function(c) { |
| 3688 return '-' + c.toLowerCase(); |
| 3689 }); |
| 3690 } |
| 3691 |
| 3692 var parentScopeName = '@' + Math.random().toString(36).slice(2); |
| 3693 |
| 3694 // Single ident paths must bind directly to the appropriate scope object. |
| 3695 // I.e. Pushed values in two-bindings need to be assigned to the actual model |
| 3696 // object. |
| 3697 function findScope(model, prop) { |
| 3698 while (model[parentScopeName] && |
| 3699 !Object.prototype.hasOwnProperty.call(model, prop)) { |
| 3700 model = model[parentScopeName]; |
| 3701 } |
| 3702 |
| 3703 return model; |
| 3704 } |
| 3705 |
| 3706 function isLiteralExpression(pathString) { |
| 3707 switch (pathString) { |
| 3708 case '': |
| 3709 return false; |
| 3710 |
| 3711 case 'false': |
| 3712 case 'null': |
| 3713 case 'true': |
| 3714 return true; |
| 3715 } |
| 3716 |
| 3717 if (!isNaN(Number(pathString))) |
| 3718 return true; |
| 3719 |
| 3720 return false; |
| 3721 }; |
| 3722 |
| 3723 function PolymerExpressions() {} |
| 3724 |
| 3725 PolymerExpressions.prototype = { |
| 3726 // "built-in" filters |
| 3727 styleObject: function(value) { |
| 3728 var parts = []; |
| 3729 for (var key in value) { |
| 3730 parts.push(convertStylePropertyName(key) + ': ' + value[key]); |
| 3731 } |
| 3732 return parts.join('; '); |
| 3733 }, |
| 3734 |
| 3735 tokenList: function(value) { |
| 3736 var tokens = []; |
| 3737 for (var key in value) { |
| 3738 if (value[key]) |
| 3739 tokens.push(key); |
| 3740 } |
| 3741 return tokens.join(' '); |
| 3742 }, |
| 3743 |
| 3744 // binding delegate API |
| 3745 prepareInstancePositionChanged: function(template) { |
| 3746 var indexIdent = template.polymerExpressionIndexIdent_; |
| 3747 if (!indexIdent) |
| 3748 return; |
| 3749 |
| 3750 return function(templateInstance, index) { |
| 3751 templateInstance.model[indexIdent] = index; |
| 3752 }; |
| 3753 }, |
| 3754 |
| 3755 prepareBinding: function(pathString, name, node) { |
| 3756 var path = Path.get(pathString); |
| 3757 |
| 3758 if (!isLiteralExpression(pathString) && path.valid) { |
| 3759 if (path.length == 1) { |
| 3760 return function(model, node, oneTime) { |
| 3761 if (oneTime) |
| 3762 return path.getValueFrom(model); |
| 3763 |
| 3764 var scope = findScope(model, path[0]); |
| 3765 return new PathObserver(scope, path); |
| 3766 }; |
| 3767 } |
| 3768 return; // bail out early if pathString is simple path. |
| 3769 } |
| 3770 |
| 3771 return prepareBinding(pathString, name, node, this); |
| 3772 }, |
| 3773 |
| 3774 prepareInstanceModel: function(template) { |
| 3775 var scopeName = template.polymerExpressionScopeIdent_; |
| 3776 if (!scopeName) |
| 3777 return; |
| 3778 |
| 3779 var parentScope = template.templateInstance ? |
| 3780 template.templateInstance.model : |
| 3781 template.model; |
| 3782 |
| 3783 var indexName = template.polymerExpressionIndexIdent_; |
| 3784 |
| 3785 return function(model) { |
| 3786 return createScopeObject(parentScope, model, scopeName, indexName); |
| 3787 }; |
| 3788 } |
| 3789 }; |
| 3790 |
| 3791 var createScopeObject = ('__proto__' in {}) ? |
| 3792 function(parentScope, model, scopeName, indexName) { |
| 3793 var scope = {}; |
| 3794 scope[scopeName] = model; |
| 3795 scope[indexName] = undefined; |
| 3796 scope[parentScopeName] = parentScope; |
| 3797 scope.__proto__ = parentScope; |
| 3798 return scope; |
| 3799 } : |
| 3800 function(parentScope, model, scopeName, indexName) { |
| 3801 var scope = Object.create(parentScope); |
| 3802 Object.defineProperty(scope, scopeName, |
| 3803 { value: model, configurable: true, writable: true }); |
| 3804 Object.defineProperty(scope, indexName, |
| 3805 { value: undefined, configurable: true, writable: true }); |
| 3806 Object.defineProperty(scope, parentScopeName, |
| 3807 { value: parentScope, configurable: true, writable: true }); |
| 3808 return scope; |
| 3809 }; |
| 3810 |
| 3811 global.PolymerExpressions = PolymerExpressions; |
| 3812 PolymerExpressions.getExpression = getExpression; |
| 3813 })(this); |
| 3814 |
| 3815 Polymer = { |
| 3816 version: '0.5.1' |
| 3817 }; |
| 3818 |
| 3819 // TODO(sorvell): this ensures Polymer is an object and not a function |
| 3820 // Platform is currently defining it as a function to allow for async loading |
| 3821 // of polymer; once we refine the loading process this likely goes away. |
| 3822 if (typeof window.Polymer === 'function') { |
| 3823 Polymer = {}; |
| 3824 } |
| 3825 |
| 3826 |
| 3827 (function(scope) { |
| 3828 |
| 3829 function withDependencies(task, depends) { |
| 3830 depends = depends || []; |
| 3831 if (!depends.map) { |
| 3832 depends = [depends]; |
| 3833 } |
| 3834 return task.apply(this, depends.map(marshal)); |
| 3835 } |
| 3836 |
| 3837 function module(name, dependsOrFactory, moduleFactory) { |
| 3838 var module; |
| 3839 switch (arguments.length) { |
| 3840 case 0: |
| 3841 return; |
| 3842 case 1: |
| 3843 module = null; |
| 3844 break; |
| 3845 case 2: |
| 3846 // dependsOrFactory is `factory` in this case |
| 3847 module = dependsOrFactory.apply(this); |
| 3848 break; |
| 3849 default: |
| 3850 // dependsOrFactory is `depends` in this case |
| 3851 module = withDependencies(moduleFactory, dependsOrFactory); |
| 3852 break; |
| 3853 } |
| 3854 modules[name] = module; |
| 3855 }; |
| 3856 |
| 3857 function marshal(name) { |
| 3858 return modules[name]; |
| 3859 } |
| 3860 |
| 3861 var modules = {}; |
| 3862 |
| 3863 function using(depends, task) { |
| 3864 HTMLImports.whenImportsReady(function() { |
| 3865 withDependencies(task, depends); |
| 3866 }); |
| 3867 }; |
| 3868 |
| 3869 // exports |
| 3870 |
| 3871 scope.marshal = marshal; |
| 3872 // `module` confuses commonjs detectors |
| 3873 scope.modularize = module; |
| 3874 scope.using = using; |
| 3875 |
| 3876 })(window); |
| 3877 |
| 3878 /* |
| 3879 Build only script. |
| 3880 |
| 3881 Ensures scripts needed for basic x-platform compatibility |
| 3882 will be run when platform.js is not loaded. |
| 3883 */ |
| 3884 if (!window.WebComponents) { |
| 3885 |
| 3886 /* |
| 3887 On supported platforms, platform.js is not needed. To retain compatibili
ty |
| 3888 with the polyfills, we stub out minimal functionality. |
| 3889 */ |
| 3890 if (!window.WebComponents) { |
| 3891 |
| 3892 WebComponents = { |
| 3893 flush: function() {}, |
| 3894 flags: {log: {}} |
| 3895 }; |
| 3896 |
| 3897 Platform = WebComponents; |
| 3898 |
| 3899 CustomElements = { |
| 3900 useNative: true, |
| 3901 ready: true, |
| 3902 takeRecords: function() {}, |
| 3903 instanceof: function(obj, base) { |
| 3904 return obj instanceof base; |
| 3905 } |
| 3906 }; |
| 3907 |
| 3908 HTMLImports = { |
| 3909 useNative: true |
| 3910 }; |
| 3911 |
| 3912 |
| 3913 addEventListener('HTMLImportsLoaded', function() { |
| 3914 document.dispatchEvent( |
| 3915 new CustomEvent('WebComponentsReady', {bubbles: true}) |
| 3916 ); |
| 3917 }); |
| 3918 |
| 3919 |
| 3920 // ShadowDOM |
| 3921 ShadowDOMPolyfill = null; |
| 3922 wrap = unwrap = function(n){ |
| 3923 return n; |
| 3924 }; |
| 3925 |
| 3926 } |
| 3927 |
| 3928 /* |
| 3929 Create polyfill scope and feature detect native support. |
| 3930 */ |
| 3931 window.HTMLImports = window.HTMLImports || {flags:{}}; |
| 3932 |
| 3933 (function(scope) { |
| 3934 |
| 3935 /** |
| 3936 Basic setup and simple module executer. We collect modules and then execute |
| 3937 the code later, only if it's necessary for polyfilling. |
| 3938 */ |
| 3939 var IMPORT_LINK_TYPE = 'import'; |
| 3940 var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement('link')); |
| 3941 |
| 3942 /** |
| 3943 Support `currentScript` on all browsers as `document._currentScript.` |
| 3944 |
| 3945 NOTE: We cannot polyfill `document.currentScript` because it's not possible |
| 3946 both to override and maintain the ability to capture the native value. |
| 3947 Therefore we choose to expose `_currentScript` both when native imports |
| 3948 and the polyfill are in use. |
| 3949 */ |
| 3950 // NOTE: ShadowDOMPolyfill intrusion. |
| 3951 var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill); |
| 3952 var wrap = function(node) { |
| 3953 return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node; |
| 3954 }; |
| 3955 var rootDocument = wrap(document); |
| 3956 |
| 3957 var currentScriptDescriptor = { |
| 3958 get: function() { |
| 3959 var script = HTMLImports.currentScript || document.currentScript || |
| 3960 // NOTE: only works when called in synchronously executing code. |
| 3961 // readyState should check if `loading` but IE10 is |
| 3962 // interactive when scripts run so we cheat. |
| 3963 (document.readyState !== 'complete' ? |
| 3964 document.scripts[document.scripts.length - 1] : null); |
| 3965 return wrap(script); |
| 3966 }, |
| 3967 configurable: true |
| 3968 }; |
| 3969 |
| 3970 Object.defineProperty(document, '_currentScript', currentScriptDescriptor); |
| 3971 Object.defineProperty(rootDocument, '_currentScript', currentScriptDescriptor); |
| 3972 |
| 3973 /** |
| 3974 Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady` |
| 3975 method. This api is necessary because unlike the native implementation, |
| 3976 script elements do not force imports to resolve. Instead, users should wrap |
| 3977 code in either an `HTMLImportsLoaded` hander or after load time in an |
| 3978 `HTMLImports.whenReady(callback)` call. |
| 3979 |
| 3980 NOTE: This module also supports these apis under the native implementation. |
| 3981 Therefore, if this file is loaded, the same code can be used under both |
| 3982 the polyfill and native implementation. |
| 3983 */ |
| 3984 |
| 3985 var isIE = /Trident/.test(navigator.userAgent); |
| 3986 |
| 3987 // call a callback when all HTMLImports in the document at call time |
| 3988 // (or at least document ready) have loaded. |
| 3989 // 1. ensure the document is in a ready state (has dom), then |
| 3990 // 2. watch for loading of imports and call callback when done |
| 3991 function whenReady(callback, doc) { |
| 3992 doc = doc || rootDocument; |
| 3993 // if document is loading, wait and try again |
| 3994 whenDocumentReady(function() { |
| 3995 watchImportsLoad(callback, doc); |
| 3996 }, doc); |
| 3997 } |
| 3998 |
| 3999 // call the callback when the document is in a ready state (has dom) |
| 4000 var requiredReadyState = isIE ? 'complete' : 'interactive'; |
| 4001 var READY_EVENT = 'readystatechange'; |
| 4002 function isDocumentReady(doc) { |
| 4003 return (doc.readyState === 'complete' || |
| 4004 doc.readyState === requiredReadyState); |
| 4005 } |
| 4006 |
| 4007 // call <callback> when we ensure the document is in a ready state |
| 4008 function whenDocumentReady(callback, doc) { |
| 4009 if (!isDocumentReady(doc)) { |
| 4010 var checkReady = function() { |
| 4011 if (doc.readyState === 'complete' || |
| 4012 doc.readyState === requiredReadyState) { |
| 4013 doc.removeEventListener(READY_EVENT, checkReady); |
| 4014 whenDocumentReady(callback, doc); |
| 4015 } |
| 4016 }; |
| 4017 doc.addEventListener(READY_EVENT, checkReady); |
| 4018 } else if (callback) { |
| 4019 callback(); |
| 4020 } |
| 4021 } |
| 4022 |
| 4023 function markTargetLoaded(event) { |
| 4024 event.target.__loaded = true; |
| 4025 } |
| 4026 |
| 4027 // call <callback> when we ensure all imports have loaded |
| 4028 function watchImportsLoad(callback, doc) { |
| 4029 var imports = doc.querySelectorAll('link[rel=import]'); |
| 4030 var loaded = 0, l = imports.length; |
| 4031 function checkDone(d) { |
| 4032 if ((loaded == l) && callback) { |
| 4033 callback(); |
| 4034 } |
| 4035 } |
| 4036 function loadedImport(e) { |
| 4037 markTargetLoaded(e); |
| 4038 loaded++; |
| 4039 checkDone(); |
| 4040 } |
| 4041 if (l) { |
| 4042 for (var i=0, imp; (i<l) && (imp=imports[i]); i++) { |
| 4043 if (isImportLoaded(imp)) { |
| 4044 loadedImport.call(imp, {target: imp}); |
| 4045 } else { |
| 4046 imp.addEventListener('load', loadedImport); |
| 4047 imp.addEventListener('error', loadedImport); |
| 4048 } |
| 4049 } |
| 4050 } else { |
| 4051 checkDone(); |
| 4052 } |
| 4053 } |
| 4054 |
| 4055 // NOTE: test for native imports loading is based on explicitly watching |
| 4056 // all imports (see below). |
| 4057 // However, we cannot rely on this entirely without watching the entire document |
| 4058 // for import links. For perf reasons, currently only head is watched. |
| 4059 // Instead, we fallback to checking if the import property is available |
| 4060 // and the document is not itself loading. |
| 4061 function isImportLoaded(link) { |
| 4062 return useNative ? link.__loaded || |
| 4063 (link.import && link.import.readyState !== 'loading') : |
| 4064 link.__importParsed; |
| 4065 } |
| 4066 |
| 4067 // TODO(sorvell): Workaround for |
| 4068 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007, should be removed when |
| 4069 // this bug is addressed. |
| 4070 // (1) Install a mutation observer to see when HTMLImports have loaded |
| 4071 // (2) if this script is run during document load it will watch any existing |
| 4072 // imports for loading. |
| 4073 // |
| 4074 // NOTE: The workaround has restricted functionality: (1) it's only compatible |
| 4075 // with imports that are added to document.head since the mutation observer |
| 4076 // watches only head for perf reasons, (2) it requires this script |
| 4077 // to run before any imports have completed loading. |
| 4078 if (useNative) { |
| 4079 new MutationObserver(function(mxns) { |
| 4080 for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) { |
| 4081 if (m.addedNodes) { |
| 4082 handleImports(m.addedNodes); |
| 4083 } |
| 4084 } |
| 4085 }).observe(document.head, {childList: true}); |
| 4086 |
| 4087 function handleImports(nodes) { |
| 4088 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { |
| 4089 if (isImport(n)) { |
| 4090 handleImport(n); |
| 4091 } |
| 4092 } |
| 4093 } |
| 4094 |
| 4095 function isImport(element) { |
| 4096 return element.localName === 'link' && element.rel === 'import'; |
| 4097 } |
| 4098 |
| 4099 function handleImport(element) { |
| 4100 var loaded = element.import; |
| 4101 if (loaded) { |
| 4102 markTargetLoaded({target: element}); |
| 4103 } else { |
| 4104 element.addEventListener('load', markTargetLoaded); |
| 4105 element.addEventListener('error', markTargetLoaded); |
| 4106 } |
| 4107 } |
| 4108 |
| 4109 // make sure to catch any imports that are in the process of loading |
| 4110 // when this script is run. |
| 4111 (function() { |
| 4112 if (document.readyState === 'loading') { |
| 4113 var imports = document.querySelectorAll('link[rel=import]'); |
| 4114 for (var i=0, l=imports.length, imp; (i<l) && (imp=imports[i]); i++) { |
| 4115 handleImport(imp); |
| 4116 } |
| 4117 } |
| 4118 })(); |
| 4119 |
| 4120 } |
| 4121 |
| 4122 // Fire the 'HTMLImportsLoaded' event when imports in document at load time |
| 4123 // have loaded. This event is required to simulate the script blocking |
| 4124 // behavior of native imports. A main document script that needs to be sure |
| 4125 // imports have loaded should wait for this event. |
| 4126 whenReady(function() { |
| 4127 HTMLImports.ready = true; |
| 4128 HTMLImports.readyTime = new Date().getTime(); |
| 4129 rootDocument.dispatchEvent( |
| 4130 new CustomEvent('HTMLImportsLoaded', {bubbles: true}) |
| 4131 ); |
| 4132 }); |
| 4133 |
| 4134 // exports |
| 4135 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; |
| 4136 scope.useNative = useNative; |
| 4137 scope.rootDocument = rootDocument; |
| 4138 scope.whenReady = whenReady; |
| 4139 scope.isIE = isIE; |
| 4140 |
| 4141 })(HTMLImports); |
| 4142 |
| 4143 (function(scope) { |
| 4144 |
| 4145 // TODO(sorvell): It's desireable to provide a default stylesheet |
| 4146 // that's convenient for styling unresolved elements, but |
| 4147 // it's cumbersome to have to include this manually in every page. |
| 4148 // It would make sense to put inside some HTMLImport but |
| 4149 // the HTMLImports polyfill does not allow loading of stylesheets |
| 4150 // that block rendering. Therefore this injection is tolerated here. |
| 4151 var style = document.createElement('style'); |
| 4152 style.textContent = '' |
| 4153 + 'body {' |
| 4154 + 'transition: opacity ease-in 0.2s;' |
| 4155 + ' } \n' |
| 4156 + 'body[unresolved] {' |
| 4157 + 'opacity: 0; display: block; overflow: hidden;' |
| 4158 + ' } \n' |
| 4159 ; |
| 4160 var head = document.querySelector('head'); |
| 4161 head.insertBefore(style, head.firstChild); |
| 4162 |
| 4163 })(Platform); |
| 4164 |
| 4165 /* |
| 4166 Build only script. |
| 4167 |
| 4168 Ensures scripts needed for basic x-platform compatibility |
| 4169 will be run when platform.js is not loaded. |
| 4170 */ |
| 4171 } |
| 4172 (function(global) { |
| 4173 'use strict'; |
| 4174 |
| 4175 var testingExposeCycleCount = global.testingExposeCycleCount; |
| 4176 |
| 4177 // Detect and do basic sanity checking on Object/Array.observe. |
| 4178 function detectObjectObserve() { |
| 4179 if (typeof Object.observe !== 'function' || |
| 4180 typeof Array.observe !== 'function') { |
| 4181 return false; |
| 4182 } |
| 4183 |
| 4184 var records = []; |
| 4185 |
| 4186 function callback(recs) { |
| 4187 records = recs; |
| 4188 } |
| 4189 |
| 4190 var test = {}; |
| 4191 var arr = []; |
| 4192 Object.observe(test, callback); |
| 4193 Array.observe(arr, callback); |
| 4194 test.id = 1; |
| 4195 test.id = 2; |
| 4196 delete test.id; |
| 4197 arr.push(1, 2); |
| 4198 arr.length = 0; |
| 4199 |
| 4200 Object.deliverChangeRecords(callback); |
| 4201 if (records.length !== 5) |
| 4202 return false; |
| 4203 |
| 4204 if (records[0].type != 'add' || |
| 4205 records[1].type != 'update' || |
| 4206 records[2].type != 'delete' || |
| 4207 records[3].type != 'splice' || |
| 4208 records[4].type != 'splice') { |
| 4209 return false; |
| 4210 } |
| 4211 |
| 4212 Object.unobserve(test, callback); |
| 4213 Array.unobserve(arr, callback); |
| 4214 |
| 4215 return true; |
| 4216 } |
| 4217 |
| 4218 var hasObserve = detectObjectObserve(); |
| 4219 |
| 4220 function detectEval() { |
| 4221 // Don't test for eval if we're running in a Chrome App environment. |
| 4222 // We check for APIs set that only exist in a Chrome App context. |
| 4223 if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { |
| 4224 return false; |
| 4225 } |
| 4226 |
| 4227 // Firefox OS Apps do not allow eval. This feature detection is very hacky |
| 4228 // but even if some other platform adds support for this function this code |
| 4229 // will continue to work. |
| 4230 if (typeof navigator != 'undefined' && navigator.getDeviceStorage) { |
| 4231 return false; |
| 4232 } |
| 4233 |
| 4234 try { |
| 4235 var f = new Function('', 'return true;'); |
| 4236 return f(); |
| 4237 } catch (ex) { |
| 4238 return false; |
| 4239 } |
| 4240 } |
| 4241 |
| 4242 var hasEval = detectEval(); |
| 4243 |
| 4244 function isIndex(s) { |
| 4245 return +s === s >>> 0 && s !== ''; |
| 4246 } |
| 4247 |
| 4248 function toNumber(s) { |
| 4249 return +s; |
| 4250 } |
| 4251 |
| 4252 function isObject(obj) { |
| 4253 return obj === Object(obj); |
| 4254 } |
| 4255 |
| 4256 var numberIsNaN = global.Number.isNaN || function(value) { |
| 4257 return typeof value === 'number' && global.isNaN(value); |
| 4258 } |
| 4259 |
| 4260 function areSameValue(left, right) { |
| 4261 if (left === right) |
| 4262 return left !== 0 || 1 / left === 1 / right; |
| 4263 if (numberIsNaN(left) && numberIsNaN(right)) |
| 4264 return true; |
| 4265 |
| 4266 return left !== left && right !== right; |
| 4267 } |
| 4268 |
| 4269 var createObject = ('__proto__' in {}) ? |
| 4270 function(obj) { return obj; } : |
| 4271 function(obj) { |
| 4272 var proto = obj.__proto__; |
| 4273 if (!proto) |
| 4274 return obj; |
| 4275 var newObject = Object.create(proto); |
| 4276 Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 4277 Object.defineProperty(newObject, name, |
| 4278 Object.getOwnPropertyDescriptor(obj, name)); |
| 4279 }); |
| 4280 return newObject; |
| 4281 }; |
| 4282 |
| 4283 var identStart = '[\$_a-zA-Z]'; |
| 4284 var identPart = '[\$_a-zA-Z0-9]'; |
| 4285 var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$'); |
| 4286 |
| 4287 function getPathCharType(char) { |
| 4288 if (char === undefined) |
| 4289 return 'eof'; |
| 4290 |
| 4291 var code = char.charCodeAt(0); |
| 4292 |
| 4293 switch(code) { |
| 4294 case 0x5B: // [ |
| 4295 case 0x5D: // ] |
| 4296 case 0x2E: // . |
| 4297 case 0x22: // " |
| 4298 case 0x27: // ' |
| 4299 case 0x30: // 0 |
| 4300 return char; |
| 4301 |
| 4302 case 0x5F: // _ |
| 4303 case 0x24: // $ |
| 4304 return 'ident'; |
| 4305 |
| 4306 case 0x20: // Space |
| 4307 case 0x09: // Tab |
| 4308 case 0x0A: // Newline |
| 4309 case 0x0D: // Return |
| 4310 case 0xA0: // No-break space |
| 4311 case 0xFEFF: // Byte Order Mark |
| 4312 case 0x2028: // Line Separator |
| 4313 case 0x2029: // Paragraph Separator |
| 4314 return 'ws'; |
| 4315 } |
| 4316 |
| 4317 // a-z, A-Z |
| 4318 if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) |
| 4319 return 'ident'; |
| 4320 |
| 4321 // 1-9 |
| 4322 if (0x31 <= code && code <= 0x39) |
| 4323 return 'number'; |
| 4324 |
| 4325 return 'else'; |
| 4326 } |
| 4327 |
| 4328 var pathStateMachine = { |
| 4329 'beforePath': { |
| 4330 'ws': ['beforePath'], |
| 4331 'ident': ['inIdent', 'append'], |
| 4332 '[': ['beforeElement'], |
| 4333 'eof': ['afterPath'] |
| 4334 }, |
| 4335 |
| 4336 'inPath': { |
| 4337 'ws': ['inPath'], |
| 4338 '.': ['beforeIdent'], |
| 4339 '[': ['beforeElement'], |
| 4340 'eof': ['afterPath'] |
| 4341 }, |
| 4342 |
| 4343 'beforeIdent': { |
| 4344 'ws': ['beforeIdent'], |
| 4345 'ident': ['inIdent', 'append'] |
| 4346 }, |
| 4347 |
| 4348 'inIdent': { |
| 4349 'ident': ['inIdent', 'append'], |
| 4350 '0': ['inIdent', 'append'], |
| 4351 'number': ['inIdent', 'append'], |
| 4352 'ws': ['inPath', 'push'], |
| 4353 '.': ['beforeIdent', 'push'], |
| 4354 '[': ['beforeElement', 'push'], |
| 4355 'eof': ['afterPath', 'push'] |
| 4356 }, |
| 4357 |
| 4358 'beforeElement': { |
| 4359 'ws': ['beforeElement'], |
| 4360 '0': ['afterZero', 'append'], |
| 4361 'number': ['inIndex', 'append'], |
| 4362 "'": ['inSingleQuote', 'append', ''], |
| 4363 '"': ['inDoubleQuote', 'append', ''] |
| 4364 }, |
| 4365 |
| 4366 'afterZero': { |
| 4367 'ws': ['afterElement', 'push'], |
| 4368 ']': ['inPath', 'push'] |
| 4369 }, |
| 4370 |
| 4371 'inIndex': { |
| 4372 '0': ['inIndex', 'append'], |
| 4373 'number': ['inIndex', 'append'], |
| 4374 'ws': ['afterElement'], |
| 4375 ']': ['inPath', 'push'] |
| 4376 }, |
| 4377 |
| 4378 'inSingleQuote': { |
| 4379 "'": ['afterElement'], |
| 4380 'eof': ['error'], |
| 4381 'else': ['inSingleQuote', 'append'] |
| 4382 }, |
| 4383 |
| 4384 'inDoubleQuote': { |
| 4385 '"': ['afterElement'], |
| 4386 'eof': ['error'], |
| 4387 'else': ['inDoubleQuote', 'append'] |
| 4388 }, |
| 4389 |
| 4390 'afterElement': { |
| 4391 'ws': ['afterElement'], |
| 4392 ']': ['inPath', 'push'] |
| 4393 } |
| 4394 } |
| 4395 |
| 4396 function noop() {} |
| 4397 |
| 4398 function parsePath(path) { |
| 4399 var keys = []; |
| 4400 var index = -1; |
| 4401 var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath'; |
| 4402 |
| 4403 var actions = { |
| 4404 push: function() { |
| 4405 if (key === undefined) |
| 4406 return; |
| 4407 |
| 4408 keys.push(key); |
| 4409 key = undefined; |
| 4410 }, |
| 4411 |
| 4412 append: function() { |
| 4413 if (key === undefined) |
| 4414 key = newChar |
| 4415 else |
| 4416 key += newChar; |
| 4417 } |
| 4418 }; |
| 4419 |
| 4420 function maybeUnescapeQuote() { |
| 4421 if (index >= path.length) |
| 4422 return; |
| 4423 |
| 4424 var nextChar = path[index + 1]; |
| 4425 if ((mode == 'inSingleQuote' && nextChar == "'") || |
| 4426 (mode == 'inDoubleQuote' && nextChar == '"')) { |
| 4427 index++; |
| 4428 newChar = nextChar; |
| 4429 actions.append(); |
| 4430 return true; |
| 4431 } |
| 4432 } |
| 4433 |
| 4434 while (mode) { |
| 4435 index++; |
| 4436 c = path[index]; |
| 4437 |
| 4438 if (c == '\\' && maybeUnescapeQuote(mode)) |
| 4439 continue; |
| 4440 |
| 4441 type = getPathCharType(c); |
| 4442 typeMap = pathStateMachine[mode]; |
| 4443 transition = typeMap[type] || typeMap['else'] || 'error'; |
| 4444 |
| 4445 if (transition == 'error') |
| 4446 return; // parse error; |
| 4447 |
| 4448 mode = transition[0]; |
| 4449 action = actions[transition[1]] || noop; |
| 4450 newChar = transition[2] === undefined ? c : transition[2]; |
| 4451 action(); |
| 4452 |
| 4453 if (mode === 'afterPath') { |
| 4454 return keys; |
| 4455 } |
| 4456 } |
| 4457 |
| 4458 return; // parse error |
| 4459 } |
| 4460 |
| 4461 function isIdent(s) { |
| 4462 return identRegExp.test(s); |
| 4463 } |
| 4464 |
| 4465 var constructorIsPrivate = {}; |
| 4466 |
| 4467 function Path(parts, privateToken) { |
| 4468 if (privateToken !== constructorIsPrivate) |
| 4469 throw Error('Use Path.get to retrieve path objects'); |
| 4470 |
| 4471 for (var i = 0; i < parts.length; i++) { |
| 4472 this.push(String(parts[i])); |
| 4473 } |
| 4474 |
| 4475 if (hasEval && this.length) { |
| 4476 this.getValueFrom = this.compiledGetValueFromFn(); |
| 4477 } |
| 4478 } |
| 4479 |
| 4480 // TODO(rafaelw): Make simple LRU cache |
| 4481 var pathCache = {}; |
| 4482 |
| 4483 function getPath(pathString) { |
| 4484 if (pathString instanceof Path) |
| 4485 return pathString; |
| 4486 |
| 4487 if (pathString == null || pathString.length == 0) |
| 4488 pathString = ''; |
| 4489 |
| 4490 if (typeof pathString != 'string') { |
| 4491 if (isIndex(pathString.length)) { |
| 4492 // Constructed with array-like (pre-parsed) keys |
| 4493 return new Path(pathString, constructorIsPrivate); |
| 4494 } |
| 4495 |
| 4496 pathString = String(pathString); |
| 4497 } |
| 4498 |
| 4499 var path = pathCache[pathString]; |
| 4500 if (path) |
| 4501 return path; |
| 4502 |
| 4503 var parts = parsePath(pathString); |
| 4504 if (!parts) |
| 4505 return invalidPath; |
| 4506 |
| 4507 var path = new Path(parts, constructorIsPrivate); |
| 4508 pathCache[pathString] = path; |
| 4509 return path; |
| 4510 } |
| 4511 |
| 4512 Path.get = getPath; |
| 4513 |
| 4514 function formatAccessor(key) { |
| 4515 if (isIndex(key)) { |
| 4516 return '[' + key + ']'; |
| 4517 } else { |
| 4518 return '["' + key.replace(/"/g, '\\"') + '"]'; |
| 4519 } |
| 4520 } |
| 4521 |
| 4522 Path.prototype = createObject({ |
| 4523 __proto__: [], |
| 4524 valid: true, |
| 4525 |
| 4526 toString: function() { |
| 4527 var pathString = ''; |
| 4528 for (var i = 0; i < this.length; i++) { |
| 4529 var key = this[i]; |
| 4530 if (isIdent(key)) { |
| 4531 pathString += i ? '.' + key : key; |
| 4532 } else { |
| 4533 pathString += formatAccessor(key); |
| 4534 } |
| 4535 } |
| 4536 |
| 4537 return pathString; |
| 4538 }, |
| 4539 |
| 4540 getValueFrom: function(obj, directObserver) { |
| 4541 for (var i = 0; i < this.length; i++) { |
| 4542 if (obj == null) |
| 4543 return; |
| 4544 obj = obj[this[i]]; |
| 4545 } |
| 4546 return obj; |
| 4547 }, |
| 4548 |
| 4549 iterateObjects: function(obj, observe) { |
| 4550 for (var i = 0; i < this.length; i++) { |
| 4551 if (i) |
| 4552 obj = obj[this[i - 1]]; |
| 4553 if (!isObject(obj)) |
| 4554 return; |
| 4555 observe(obj, this[i]); |
| 4556 } |
| 4557 }, |
| 4558 |
| 4559 compiledGetValueFromFn: function() { |
| 4560 var str = ''; |
| 4561 var pathString = 'obj'; |
| 4562 str += 'if (obj != null'; |
| 4563 var i = 0; |
| 4564 var key; |
| 4565 for (; i < (this.length - 1); i++) { |
| 4566 key = this[i]; |
| 4567 pathString += isIdent(key) ? '.' + key : formatAccessor(key); |
| 4568 str += ' &&\n ' + pathString + ' != null'; |
| 4569 } |
| 4570 str += ')\n'; |
| 4571 |
| 4572 var key = this[i]; |
| 4573 pathString += isIdent(key) ? '.' + key : formatAccessor(key); |
| 4574 |
| 4575 str += ' return ' + pathString + ';\nelse\n return undefined;'; |
| 4576 return new Function('obj', str); |
| 4577 }, |
| 4578 |
| 4579 setValueFrom: function(obj, value) { |
| 4580 if (!this.length) |
| 4581 return false; |
| 4582 |
| 4583 for (var i = 0; i < this.length - 1; i++) { |
| 4584 if (!isObject(obj)) |
| 4585 return false; |
| 4586 obj = obj[this[i]]; |
| 4587 } |
| 4588 |
| 4589 if (!isObject(obj)) |
| 4590 return false; |
| 4591 |
| 4592 obj[this[i]] = value; |
| 4593 return true; |
| 4594 } |
| 4595 }); |
| 4596 |
| 4597 var invalidPath = new Path('', constructorIsPrivate); |
| 4598 invalidPath.valid = false; |
| 4599 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; |
| 4600 |
| 4601 var MAX_DIRTY_CHECK_CYCLES = 1000; |
| 4602 |
| 4603 function dirtyCheck(observer) { |
| 4604 var cycles = 0; |
| 4605 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { |
| 4606 cycles++; |
| 4607 } |
| 4608 if (testingExposeCycleCount) |
| 4609 global.dirtyCheckCycleCount = cycles; |
| 4610 |
| 4611 return cycles > 0; |
| 4612 } |
| 4613 |
| 4614 function objectIsEmpty(object) { |
| 4615 for (var prop in object) |
| 4616 return false; |
| 4617 return true; |
| 4618 } |
| 4619 |
| 4620 function diffIsEmpty(diff) { |
| 4621 return objectIsEmpty(diff.added) && |
| 4622 objectIsEmpty(diff.removed) && |
| 4623 objectIsEmpty(diff.changed); |
| 4624 } |
| 4625 |
| 4626 function diffObjectFromOldObject(object, oldObject) { |
| 4627 var added = {}; |
| 4628 var removed = {}; |
| 4629 var changed = {}; |
| 4630 |
| 4631 for (var prop in oldObject) { |
| 4632 var newValue = object[prop]; |
| 4633 |
| 4634 if (newValue !== undefined && newValue === oldObject[prop]) |
| 4635 continue; |
| 4636 |
| 4637 if (!(prop in object)) { |
| 4638 removed[prop] = undefined; |
| 4639 continue; |
| 4640 } |
| 4641 |
| 4642 if (newValue !== oldObject[prop]) |
| 4643 changed[prop] = newValue; |
| 4644 } |
| 4645 |
| 4646 for (var prop in object) { |
| 4647 if (prop in oldObject) |
| 4648 continue; |
| 4649 |
| 4650 added[prop] = object[prop]; |
| 4651 } |
| 4652 |
| 4653 if (Array.isArray(object) && object.length !== oldObject.length) |
| 4654 changed.length = object.length; |
| 4655 |
| 4656 return { |
| 4657 added: added, |
| 4658 removed: removed, |
| 4659 changed: changed |
| 4660 }; |
| 4661 } |
| 4662 |
| 4663 var eomTasks = []; |
| 4664 function runEOMTasks() { |
| 4665 if (!eomTasks.length) |
| 4666 return false; |
| 4667 |
| 4668 for (var i = 0; i < eomTasks.length; i++) { |
| 4669 eomTasks[i](); |
| 4670 } |
| 4671 eomTasks.length = 0; |
| 4672 return true; |
| 4673 } |
| 4674 |
| 4675 var runEOM = hasObserve ? (function(){ |
| 4676 return function(fn) { |
| 4677 return Promise.resolve().then(fn); |
| 4678 } |
| 4679 })() : |
| 4680 (function() { |
| 4681 return function(fn) { |
| 4682 eomTasks.push(fn); |
| 4683 }; |
| 4684 })(); |
| 4685 |
| 4686 var observedObjectCache = []; |
| 4687 |
| 4688 function newObservedObject() { |
| 4689 var observer; |
| 4690 var object; |
| 4691 var discardRecords = false; |
| 4692 var first = true; |
| 4693 |
| 4694 function callback(records) { |
| 4695 if (observer && observer.state_ === OPENED && !discardRecords) |
| 4696 observer.check_(records); |
| 4697 } |
| 4698 |
| 4699 return { |
| 4700 open: function(obs) { |
| 4701 if (observer) |
| 4702 throw Error('ObservedObject in use'); |
| 4703 |
| 4704 if (!first) |
| 4705 Object.deliverChangeRecords(callback); |
| 4706 |
| 4707 observer = obs; |
| 4708 first = false; |
| 4709 }, |
| 4710 observe: function(obj, arrayObserve) { |
| 4711 object = obj; |
| 4712 if (arrayObserve) |
| 4713 Array.observe(object, callback); |
| 4714 else |
| 4715 Object.observe(object, callback); |
| 4716 }, |
| 4717 deliver: function(discard) { |
| 4718 discardRecords = discard; |
| 4719 Object.deliverChangeRecords(callback); |
| 4720 discardRecords = false; |
| 4721 }, |
| 4722 close: function() { |
| 4723 observer = undefined; |
| 4724 Object.unobserve(object, callback); |
| 4725 observedObjectCache.push(this); |
| 4726 } |
| 4727 }; |
| 4728 } |
| 4729 |
| 4730 /* |
| 4731 * The observedSet abstraction is a perf optimization which reduces the total |
| 4732 * number of Object.observe observations of a set of objects. The idea is that |
| 4733 * groups of Observers will have some object dependencies in common and this |
| 4734 * observed set ensures that each object in the transitive closure of |
| 4735 * dependencies is only observed once. The observedSet acts as a write barrier |
| 4736 * such that whenever any change comes through, all Observers are checked for |
| 4737 * changed values. |
| 4738 * |
| 4739 * Note that this optimization is explicitly moving work from setup-time to |
| 4740 * change-time. |
| 4741 * |
| 4742 * TODO(rafaelw): Implement "garbage collection". In order to move work off |
| 4743 * the critical path, when Observers are closed, their observed objects are |
| 4744 * not Object.unobserve(d). As a result, it's possible that if the observedSet |
| 4745 * is kept open, but some Observers have been closed, it could cause "leaks" |
| 4746 * (prevent otherwise collectable objects from being collected). At some |
| 4747 * point, we should implement incremental "gc" which keeps a list of |
| 4748 * observedSets which may need clean-up and does small amounts of cleanup on a |
| 4749 * timeout until all is clean. |
| 4750 */ |
| 4751 |
| 4752 function getObservedObject(observer, object, arrayObserve) { |
| 4753 var dir = observedObjectCache.pop() || newObservedObject(); |
| 4754 dir.open(observer); |
| 4755 dir.observe(object, arrayObserve); |
| 4756 return dir; |
| 4757 } |
| 4758 |
| 4759 var observedSetCache = []; |
| 4760 |
| 4761 function newObservedSet() { |
| 4762 var observerCount = 0; |
| 4763 var observers = []; |
| 4764 var objects = []; |
| 4765 var rootObj; |
| 4766 var rootObjProps; |
| 4767 |
| 4768 function observe(obj, prop) { |
| 4769 if (!obj) |
| 4770 return; |
| 4771 |
| 4772 if (obj === rootObj) |
| 4773 rootObjProps[prop] = true; |
| 4774 |
| 4775 if (objects.indexOf(obj) < 0) { |
| 4776 objects.push(obj); |
| 4777 Object.observe(obj, callback); |
| 4778 } |
| 4779 |
| 4780 observe(Object.getPrototypeOf(obj), prop); |
| 4781 } |
| 4782 |
| 4783 function allRootObjNonObservedProps(recs) { |
| 4784 for (var i = 0; i < recs.length; i++) { |
| 4785 var rec = recs[i]; |
| 4786 if (rec.object !== rootObj || |
| 4787 rootObjProps[rec.name] || |
| 4788 rec.type === 'setPrototype') { |
| 4789 return false; |
| 4790 } |
| 4791 } |
| 4792 return true; |
| 4793 } |
| 4794 |
| 4795 function callback(recs) { |
| 4796 if (allRootObjNonObservedProps(recs)) |
| 4797 return; |
| 4798 |
| 4799 var observer; |
| 4800 for (var i = 0; i < observers.length; i++) { |
| 4801 observer = observers[i]; |
| 4802 if (observer.state_ == OPENED) { |
| 4803 observer.iterateObjects_(observe); |
| 4804 } |
| 4805 } |
| 4806 |
| 4807 for (var i = 0; i < observers.length; i++) { |
| 4808 observer = observers[i]; |
| 4809 if (observer.state_ == OPENED) { |
| 4810 observer.check_(); |
| 4811 } |
| 4812 } |
| 4813 } |
| 4814 |
| 4815 var record = { |
| 4816 objects: objects, |
| 4817 get rootObject() { return rootObj; }, |
| 4818 set rootObject(value) { |
| 4819 rootObj = value; |
| 4820 rootObjProps = {}; |
| 4821 }, |
| 4822 open: function(obs, object) { |
| 4823 observers.push(obs); |
| 4824 observerCount++; |
| 4825 obs.iterateObjects_(observe); |
| 4826 }, |
| 4827 close: function(obs) { |
| 4828 observerCount--; |
| 4829 if (observerCount > 0) { |
| 4830 return; |
| 4831 } |
| 4832 |
| 4833 for (var i = 0; i < objects.length; i++) { |
| 4834 Object.unobserve(objects[i], callback); |
| 4835 Observer.unobservedCount++; |
| 4836 } |
| 4837 |
| 4838 observers.length = 0; |
| 4839 objects.length = 0; |
| 4840 rootObj = undefined; |
| 4841 rootObjProps = undefined; |
| 4842 observedSetCache.push(this); |
| 4843 if (lastObservedSet === this) |
| 4844 lastObservedSet = null; |
| 4845 }, |
| 4846 }; |
| 4847 |
| 4848 return record; |
| 4849 } |
| 4850 |
| 4851 var lastObservedSet; |
| 4852 |
| 4853 function getObservedSet(observer, obj) { |
| 4854 if (!lastObservedSet || lastObservedSet.rootObject !== obj) { |
| 4855 lastObservedSet = observedSetCache.pop() || newObservedSet(); |
| 4856 lastObservedSet.rootObject = obj; |
| 4857 } |
| 4858 lastObservedSet.open(observer, obj); |
| 4859 return lastObservedSet; |
| 4860 } |
| 4861 |
| 4862 var UNOPENED = 0; |
| 4863 var OPENED = 1; |
| 4864 var CLOSED = 2; |
| 4865 var RESETTING = 3; |
| 4866 |
| 4867 var nextObserverId = 1; |
| 4868 |
| 4869 function Observer() { |
| 4870 this.state_ = UNOPENED; |
| 4871 this.callback_ = undefined; |
| 4872 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef |
| 4873 this.directObserver_ = undefined; |
| 4874 this.value_ = undefined; |
| 4875 this.id_ = nextObserverId++; |
| 4876 } |
| 4877 |
| 4878 Observer.prototype = { |
| 4879 open: function(callback, target) { |
| 4880 if (this.state_ != UNOPENED) |
| 4881 throw Error('Observer has already been opened.'); |
| 4882 |
| 4883 addToAll(this); |
| 4884 this.callback_ = callback; |
| 4885 this.target_ = target; |
| 4886 this.connect_(); |
| 4887 this.state_ = OPENED; |
| 4888 return this.value_; |
| 4889 }, |
| 4890 |
| 4891 close: function() { |
| 4892 if (this.state_ != OPENED) |
| 4893 return; |
| 4894 |
| 4895 removeFromAll(this); |
| 4896 this.disconnect_(); |
| 4897 this.value_ = undefined; |
| 4898 this.callback_ = undefined; |
| 4899 this.target_ = undefined; |
| 4900 this.state_ = CLOSED; |
| 4901 }, |
| 4902 |
| 4903 deliver: function() { |
| 4904 if (this.state_ != OPENED) |
| 4905 return; |
| 4906 |
| 4907 dirtyCheck(this); |
| 4908 }, |
| 4909 |
| 4910 report_: function(changes) { |
| 4911 try { |
| 4912 this.callback_.apply(this.target_, changes); |
| 4913 } catch (ex) { |
| 4914 Observer._errorThrownDuringCallback = true; |
| 4915 console.error('Exception caught during observer callback: ' + |
| 4916 (ex.stack || ex)); |
| 4917 } |
| 4918 }, |
| 4919 |
| 4920 discardChanges: function() { |
| 4921 this.check_(undefined, true); |
| 4922 return this.value_; |
| 4923 } |
| 4924 } |
| 4925 |
| 4926 var collectObservers = !hasObserve; |
| 4927 var allObservers; |
| 4928 Observer._allObserversCount = 0; |
| 4929 |
| 4930 if (collectObservers) { |
| 4931 allObservers = []; |
| 4932 } |
| 4933 |
| 4934 function addToAll(observer) { |
| 4935 Observer._allObserversCount++; |
| 4936 if (!collectObservers) |
| 4937 return; |
| 4938 |
| 4939 allObservers.push(observer); |
| 4940 } |
| 4941 |
| 4942 function removeFromAll(observer) { |
| 4943 Observer._allObserversCount--; |
| 4944 } |
| 4945 |
| 4946 var runningMicrotaskCheckpoint = false; |
| 4947 |
| 4948 global.Platform = global.Platform || {}; |
| 4949 |
| 4950 global.Platform.performMicrotaskCheckpoint = function() { |
| 4951 if (runningMicrotaskCheckpoint) |
| 4952 return; |
| 4953 |
| 4954 if (!collectObservers) |
| 4955 return; |
| 4956 |
| 4957 runningMicrotaskCheckpoint = true; |
| 4958 |
| 4959 var cycles = 0; |
| 4960 var anyChanged, toCheck; |
| 4961 |
| 4962 do { |
| 4963 cycles++; |
| 4964 toCheck = allObservers; |
| 4965 allObservers = []; |
| 4966 anyChanged = false; |
| 4967 |
| 4968 for (var i = 0; i < toCheck.length; i++) { |
| 4969 var observer = toCheck[i]; |
| 4970 if (observer.state_ != OPENED) |
| 4971 continue; |
| 4972 |
| 4973 if (observer.check_()) |
| 4974 anyChanged = true; |
| 4975 |
| 4976 allObservers.push(observer); |
| 4977 } |
| 4978 if (runEOMTasks()) |
| 4979 anyChanged = true; |
| 4980 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); |
| 4981 |
| 4982 if (testingExposeCycleCount) |
| 4983 global.dirtyCheckCycleCount = cycles; |
| 4984 |
| 4985 runningMicrotaskCheckpoint = false; |
| 4986 }; |
| 4987 |
| 4988 if (collectObservers) { |
| 4989 global.Platform.clearObservers = function() { |
| 4990 allObservers = []; |
| 4991 }; |
| 4992 } |
| 4993 |
| 4994 function ObjectObserver(object) { |
| 4995 Observer.call(this); |
| 4996 this.value_ = object; |
| 4997 this.oldObject_ = undefined; |
| 4998 } |
| 4999 |
| 5000 ObjectObserver.prototype = createObject({ |
| 5001 __proto__: Observer.prototype, |
| 5002 |
| 5003 arrayObserve: false, |
| 5004 |
| 5005 connect_: function(callback, target) { |
| 5006 if (hasObserve) { |
| 5007 this.directObserver_ = getObservedObject(this, this.value_, |
| 5008 this.arrayObserve); |
| 5009 } else { |
| 5010 this.oldObject_ = this.copyObject(this.value_); |
| 5011 } |
| 5012 |
| 5013 }, |
| 5014 |
| 5015 copyObject: function(object) { |
| 5016 var copy = Array.isArray(object) ? [] : {}; |
| 5017 for (var prop in object) { |
| 5018 copy[prop] = object[prop]; |
| 5019 }; |
| 5020 if (Array.isArray(object)) |
| 5021 copy.length = object.length; |
| 5022 return copy; |
| 5023 }, |
| 5024 |
| 5025 check_: function(changeRecords, skipChanges) { |
| 5026 var diff; |
| 5027 var oldValues; |
| 5028 if (hasObserve) { |
| 5029 if (!changeRecords) |
| 5030 return false; |
| 5031 |
| 5032 oldValues = {}; |
| 5033 diff = diffObjectFromChangeRecords(this.value_, changeRecords, |
| 5034 oldValues); |
| 5035 } else { |
| 5036 oldValues = this.oldObject_; |
| 5037 diff = diffObjectFromOldObject(this.value_, this.oldObject_); |
| 5038 } |
| 5039 |
| 5040 if (diffIsEmpty(diff)) |
| 5041 return false; |
| 5042 |
| 5043 if (!hasObserve) |
| 5044 this.oldObject_ = this.copyObject(this.value_); |
| 5045 |
| 5046 this.report_([ |
| 5047 diff.added || {}, |
| 5048 diff.removed || {}, |
| 5049 diff.changed || {}, |
| 5050 function(property) { |
| 5051 return oldValues[property]; |
| 5052 } |
| 5053 ]); |
| 5054 |
| 5055 return true; |
| 5056 }, |
| 5057 |
| 5058 disconnect_: function() { |
| 5059 if (hasObserve) { |
| 5060 this.directObserver_.close(); |
| 5061 this.directObserver_ = undefined; |
| 5062 } else { |
| 5063 this.oldObject_ = undefined; |
| 5064 } |
| 5065 }, |
| 5066 |
| 5067 deliver: function() { |
| 5068 if (this.state_ != OPENED) |
| 5069 return; |
| 5070 |
| 5071 if (hasObserve) |
| 5072 this.directObserver_.deliver(false); |
| 5073 else |
| 5074 dirtyCheck(this); |
| 5075 }, |
| 5076 |
| 5077 discardChanges: function() { |
| 5078 if (this.directObserver_) |
| 5079 this.directObserver_.deliver(true); |
| 5080 else |
| 5081 this.oldObject_ = this.copyObject(this.value_); |
| 5082 |
| 5083 return this.value_; |
| 5084 } |
| 5085 }); |
| 5086 |
| 5087 function ArrayObserver(array) { |
| 5088 if (!Array.isArray(array)) |
| 5089 throw Error('Provided object is not an Array'); |
| 5090 ObjectObserver.call(this, array); |
| 5091 } |
| 5092 |
| 5093 ArrayObserver.prototype = createObject({ |
| 5094 |
| 5095 __proto__: ObjectObserver.prototype, |
| 5096 |
| 5097 arrayObserve: true, |
| 5098 |
| 5099 copyObject: function(arr) { |
| 5100 return arr.slice(); |
| 5101 }, |
| 5102 |
| 5103 check_: function(changeRecords) { |
| 5104 var splices; |
| 5105 if (hasObserve) { |
| 5106 if (!changeRecords) |
| 5107 return false; |
| 5108 splices = projectArraySplices(this.value_, changeRecords); |
| 5109 } else { |
| 5110 splices = calcSplices(this.value_, 0, this.value_.length, |
| 5111 this.oldObject_, 0, this.oldObject_.length); |
| 5112 } |
| 5113 |
| 5114 if (!splices || !splices.length) |
| 5115 return false; |
| 5116 |
| 5117 if (!hasObserve) |
| 5118 this.oldObject_ = this.copyObject(this.value_); |
| 5119 |
| 5120 this.report_([splices]); |
| 5121 return true; |
| 5122 } |
| 5123 }); |
| 5124 |
| 5125 ArrayObserver.applySplices = function(previous, current, splices) { |
| 5126 splices.forEach(function(splice) { |
| 5127 var spliceArgs = [splice.index, splice.removed.length]; |
| 5128 var addIndex = splice.index; |
| 5129 while (addIndex < splice.index + splice.addedCount) { |
| 5130 spliceArgs.push(current[addIndex]); |
| 5131 addIndex++; |
| 5132 } |
| 5133 |
| 5134 Array.prototype.splice.apply(previous, spliceArgs); |
| 5135 }); |
| 5136 }; |
| 5137 |
| 5138 function PathObserver(object, path) { |
| 5139 Observer.call(this); |
| 5140 |
| 5141 this.object_ = object; |
| 5142 this.path_ = getPath(path); |
| 5143 this.directObserver_ = undefined; |
| 5144 } |
| 5145 |
| 5146 PathObserver.prototype = createObject({ |
| 5147 __proto__: Observer.prototype, |
| 5148 |
| 5149 get path() { |
| 5150 return this.path_; |
| 5151 }, |
| 5152 |
| 5153 connect_: function() { |
| 5154 if (hasObserve) |
| 5155 this.directObserver_ = getObservedSet(this, this.object_); |
| 5156 |
| 5157 this.check_(undefined, true); |
| 5158 }, |
| 5159 |
| 5160 disconnect_: function() { |
| 5161 this.value_ = undefined; |
| 5162 |
| 5163 if (this.directObserver_) { |
| 5164 this.directObserver_.close(this); |
| 5165 this.directObserver_ = undefined; |
| 5166 } |
| 5167 }, |
| 5168 |
| 5169 iterateObjects_: function(observe) { |
| 5170 this.path_.iterateObjects(this.object_, observe); |
| 5171 }, |
| 5172 |
| 5173 check_: function(changeRecords, skipChanges) { |
| 5174 var oldValue = this.value_; |
| 5175 this.value_ = this.path_.getValueFrom(this.object_); |
| 5176 if (skipChanges || areSameValue(this.value_, oldValue)) |
| 5177 return false; |
| 5178 |
| 5179 this.report_([this.value_, oldValue, this]); |
| 5180 return true; |
| 5181 }, |
| 5182 |
| 5183 setValue: function(newValue) { |
| 5184 if (this.path_) |
| 5185 this.path_.setValueFrom(this.object_, newValue); |
| 5186 } |
| 5187 }); |
| 5188 |
| 5189 function CompoundObserver(reportChangesOnOpen) { |
| 5190 Observer.call(this); |
| 5191 |
| 5192 this.reportChangesOnOpen_ = reportChangesOnOpen; |
| 5193 this.value_ = []; |
| 5194 this.directObserver_ = undefined; |
| 5195 this.observed_ = []; |
| 5196 } |
| 5197 |
| 5198 var observerSentinel = {}; |
| 5199 |
| 5200 CompoundObserver.prototype = createObject({ |
| 5201 __proto__: Observer.prototype, |
| 5202 |
| 5203 connect_: function() { |
| 5204 if (hasObserve) { |
| 5205 var object; |
| 5206 var needsDirectObserver = false; |
| 5207 for (var i = 0; i < this.observed_.length; i += 2) { |
| 5208 object = this.observed_[i] |
| 5209 if (object !== observerSentinel) { |
| 5210 needsDirectObserver = true; |
| 5211 break; |
| 5212 } |
| 5213 } |
| 5214 |
| 5215 if (needsDirectObserver) |
| 5216 this.directObserver_ = getObservedSet(this, object); |
| 5217 } |
| 5218 |
| 5219 this.check_(undefined, !this.reportChangesOnOpen_); |
| 5220 }, |
| 5221 |
| 5222 disconnect_: function() { |
| 5223 for (var i = 0; i < this.observed_.length; i += 2) { |
| 5224 if (this.observed_[i] === observerSentinel) |
| 5225 this.observed_[i + 1].close(); |
| 5226 } |
| 5227 this.observed_.length = 0; |
| 5228 this.value_.length = 0; |
| 5229 |
| 5230 if (this.directObserver_) { |
| 5231 this.directObserver_.close(this); |
| 5232 this.directObserver_ = undefined; |
| 5233 } |
| 5234 }, |
| 5235 |
| 5236 addPath: function(object, path) { |
| 5237 if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| 5238 throw Error('Cannot add paths once started.'); |
| 5239 |
| 5240 var path = getPath(path); |
| 5241 this.observed_.push(object, path); |
| 5242 if (!this.reportChangesOnOpen_) |
| 5243 return; |
| 5244 var index = this.observed_.length / 2 - 1; |
| 5245 this.value_[index] = path.getValueFrom(object); |
| 5246 }, |
| 5247 |
| 5248 addObserver: function(observer) { |
| 5249 if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| 5250 throw Error('Cannot add observers once started.'); |
| 5251 |
| 5252 this.observed_.push(observerSentinel, observer); |
| 5253 if (!this.reportChangesOnOpen_) |
| 5254 return; |
| 5255 var index = this.observed_.length / 2 - 1; |
| 5256 this.value_[index] = observer.open(this.deliver, this); |
| 5257 }, |
| 5258 |
| 5259 startReset: function() { |
| 5260 if (this.state_ != OPENED) |
| 5261 throw Error('Can only reset while open'); |
| 5262 |
| 5263 this.state_ = RESETTING; |
| 5264 this.disconnect_(); |
| 5265 }, |
| 5266 |
| 5267 finishReset: function() { |
| 5268 if (this.state_ != RESETTING) |
| 5269 throw Error('Can only finishReset after startReset'); |
| 5270 this.state_ = OPENED; |
| 5271 this.connect_(); |
| 5272 |
| 5273 return this.value_; |
| 5274 }, |
| 5275 |
| 5276 iterateObjects_: function(observe) { |
| 5277 var object; |
| 5278 for (var i = 0; i < this.observed_.length; i += 2) { |
| 5279 object = this.observed_[i] |
| 5280 if (object !== observerSentinel) |
| 5281 this.observed_[i + 1].iterateObjects(object, observe) |
| 5282 } |
| 5283 }, |
| 5284 |
| 5285 check_: function(changeRecords, skipChanges) { |
| 5286 var oldValues; |
| 5287 for (var i = 0; i < this.observed_.length; i += 2) { |
| 5288 var object = this.observed_[i]; |
| 5289 var path = this.observed_[i+1]; |
| 5290 var value; |
| 5291 if (object === observerSentinel) { |
| 5292 var observable = path; |
| 5293 value = this.state_ === UNOPENED ? |
| 5294 observable.open(this.deliver, this) : |
| 5295 observable.discardChanges(); |
| 5296 } else { |
| 5297 value = path.getValueFrom(object); |
| 5298 } |
| 5299 |
| 5300 if (skipChanges) { |
| 5301 this.value_[i / 2] = value; |
| 5302 continue; |
| 5303 } |
| 5304 |
| 5305 if (areSameValue(value, this.value_[i / 2])) |
| 5306 continue; |
| 5307 |
| 5308 oldValues = oldValues || []; |
| 5309 oldValues[i / 2] = this.value_[i / 2]; |
| 5310 this.value_[i / 2] = value; |
| 5311 } |
| 5312 |
| 5313 if (!oldValues) |
| 5314 return false; |
| 5315 |
| 5316 // TODO(rafaelw): Having observed_ as the third callback arg here is |
| 5317 // pretty lame API. Fix. |
| 5318 this.report_([this.value_, oldValues, this.observed_]); |
| 5319 return true; |
| 5320 } |
| 5321 }); |
| 5322 |
| 5323 function identFn(value) { return value; } |
| 5324 |
| 5325 function ObserverTransform(observable, getValueFn, setValueFn, |
| 5326 dontPassThroughSet) { |
| 5327 this.callback_ = undefined; |
| 5328 this.target_ = undefined; |
| 5329 this.value_ = undefined; |
| 5330 this.observable_ = observable; |
| 5331 this.getValueFn_ = getValueFn || identFn; |
| 5332 this.setValueFn_ = setValueFn || identFn; |
| 5333 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this |
| 5334 // at the moment because of a bug in it's dependency tracking. |
| 5335 this.dontPassThroughSet_ = dontPassThroughSet; |
| 5336 } |
| 5337 |
| 5338 ObserverTransform.prototype = { |
| 5339 open: function(callback, target) { |
| 5340 this.callback_ = callback; |
| 5341 this.target_ = target; |
| 5342 this.value_ = |
| 5343 this.getValueFn_(this.observable_.open(this.observedCallback_, this)); |
| 5344 return this.value_; |
| 5345 }, |
| 5346 |
| 5347 observedCallback_: function(value) { |
| 5348 value = this.getValueFn_(value); |
| 5349 if (areSameValue(value, this.value_)) |
| 5350 return; |
| 5351 var oldValue = this.value_; |
| 5352 this.value_ = value; |
| 5353 this.callback_.call(this.target_, this.value_, oldValue); |
| 5354 }, |
| 5355 |
| 5356 discardChanges: function() { |
| 5357 this.value_ = this.getValueFn_(this.observable_.discardChanges()); |
| 5358 return this.value_; |
| 5359 }, |
| 5360 |
| 5361 deliver: function() { |
| 5362 return this.observable_.deliver(); |
| 5363 }, |
| 5364 |
| 5365 setValue: function(value) { |
| 5366 value = this.setValueFn_(value); |
| 5367 if (!this.dontPassThroughSet_ && this.observable_.setValue) |
| 5368 return this.observable_.setValue(value); |
| 5369 }, |
| 5370 |
| 5371 close: function() { |
| 5372 if (this.observable_) |
| 5373 this.observable_.close(); |
| 5374 this.callback_ = undefined; |
| 5375 this.target_ = undefined; |
| 5376 this.observable_ = undefined; |
| 5377 this.value_ = undefined; |
| 5378 this.getValueFn_ = undefined; |
| 5379 this.setValueFn_ = undefined; |
| 5380 } |
| 5381 } |
| 5382 |
| 5383 var expectedRecordTypes = { |
| 5384 add: true, |
| 5385 update: true, |
| 5386 delete: true |
| 5387 }; |
| 5388 |
| 5389 function diffObjectFromChangeRecords(object, changeRecords, oldValues) { |
| 5390 var added = {}; |
| 5391 var removed = {}; |
| 5392 |
| 5393 for (var i = 0; i < changeRecords.length; i++) { |
| 5394 var record = changeRecords[i]; |
| 5395 if (!expectedRecordTypes[record.type]) { |
| 5396 console.error('Unknown changeRecord type: ' + record.type); |
| 5397 console.error(record); |
| 5398 continue; |
| 5399 } |
| 5400 |
| 5401 if (!(record.name in oldValues)) |
| 5402 oldValues[record.name] = record.oldValue; |
| 5403 |
| 5404 if (record.type == 'update') |
| 5405 continue; |
| 5406 |
| 5407 if (record.type == 'add') { |
| 5408 if (record.name in removed) |
| 5409 delete removed[record.name]; |
| 5410 else |
| 5411 added[record.name] = true; |
| 5412 |
| 5413 continue; |
| 5414 } |
| 5415 |
| 5416 // type = 'delete' |
| 5417 if (record.name in added) { |
| 5418 delete added[record.name]; |
| 5419 delete oldValues[record.name]; |
| 5420 } else { |
| 5421 removed[record.name] = true; |
| 5422 } |
| 5423 } |
| 5424 |
| 5425 for (var prop in added) |
| 5426 added[prop] = object[prop]; |
| 5427 |
| 5428 for (var prop in removed) |
| 5429 removed[prop] = undefined; |
| 5430 |
| 5431 var changed = {}; |
| 5432 for (var prop in oldValues) { |
| 5433 if (prop in added || prop in removed) |
| 5434 continue; |
| 5435 |
| 5436 var newValue = object[prop]; |
| 5437 if (oldValues[prop] !== newValue) |
| 5438 changed[prop] = newValue; |
| 5439 } |
| 5440 |
| 5441 return { |
| 5442 added: added, |
| 5443 removed: removed, |
| 5444 changed: changed |
| 5445 }; |
| 5446 } |
| 5447 |
| 5448 function newSplice(index, removed, addedCount) { |
| 5449 return { |
| 5450 index: index, |
| 5451 removed: removed, |
| 5452 addedCount: addedCount |
| 5453 }; |
| 5454 } |
| 5455 |
| 5456 var EDIT_LEAVE = 0; |
| 5457 var EDIT_UPDATE = 1; |
| 5458 var EDIT_ADD = 2; |
| 5459 var EDIT_DELETE = 3; |
| 5460 |
| 5461 function ArraySplice() {} |
| 5462 |
| 5463 ArraySplice.prototype = { |
| 5464 |
| 5465 // Note: This function is *based* on the computation of the Levenshtein |
| 5466 // "edit" distance. The one change is that "updates" are treated as two |
| 5467 // edits - not one. With Array splices, an update is really a delete |
| 5468 // followed by an add. By retaining this, we optimize for "keeping" the |
| 5469 // maximum array items in the original array. For example: |
| 5470 // |
| 5471 // 'xxxx123' -> '123yyyy' |
| 5472 // |
| 5473 // With 1-edit updates, the shortest path would be just to update all seven |
| 5474 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This |
| 5475 // leaves the substring '123' intact. |
| 5476 calcEditDistances: function(current, currentStart, currentEnd, |
| 5477 old, oldStart, oldEnd) { |
| 5478 // "Deletion" columns |
| 5479 var rowCount = oldEnd - oldStart + 1; |
| 5480 var columnCount = currentEnd - currentStart + 1; |
| 5481 var distances = new Array(rowCount); |
| 5482 |
| 5483 // "Addition" rows. Initialize null column. |
| 5484 for (var i = 0; i < rowCount; i++) { |
| 5485 distances[i] = new Array(columnCount); |
| 5486 distances[i][0] = i; |
| 5487 } |
| 5488 |
| 5489 // Initialize null row |
| 5490 for (var j = 0; j < columnCount; j++) |
| 5491 distances[0][j] = j; |
| 5492 |
| 5493 for (var i = 1; i < rowCount; i++) { |
| 5494 for (var j = 1; j < columnCount; j++) { |
| 5495 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) |
| 5496 distances[i][j] = distances[i - 1][j - 1]; |
| 5497 else { |
| 5498 var north = distances[i - 1][j] + 1; |
| 5499 var west = distances[i][j - 1] + 1; |
| 5500 distances[i][j] = north < west ? north : west; |
| 5501 } |
| 5502 } |
| 5503 } |
| 5504 |
| 5505 return distances; |
| 5506 }, |
| 5507 |
| 5508 // This starts at the final weight, and walks "backward" by finding |
| 5509 // the minimum previous weight recursively until the origin of the weight |
| 5510 // matrix. |
| 5511 spliceOperationsFromEditDistances: function(distances) { |
| 5512 var i = distances.length - 1; |
| 5513 var j = distances[0].length - 1; |
| 5514 var current = distances[i][j]; |
| 5515 var edits = []; |
| 5516 while (i > 0 || j > 0) { |
| 5517 if (i == 0) { |
| 5518 edits.push(EDIT_ADD); |
| 5519 j--; |
| 5520 continue; |
| 5521 } |
| 5522 if (j == 0) { |
| 5523 edits.push(EDIT_DELETE); |
| 5524 i--; |
| 5525 continue; |
| 5526 } |
| 5527 var northWest = distances[i - 1][j - 1]; |
| 5528 var west = distances[i - 1][j]; |
| 5529 var north = distances[i][j - 1]; |
| 5530 |
| 5531 var min; |
| 5532 if (west < north) |
| 5533 min = west < northWest ? west : northWest; |
| 5534 else |
| 5535 min = north < northWest ? north : northWest; |
| 5536 |
| 5537 if (min == northWest) { |
| 5538 if (northWest == current) { |
| 5539 edits.push(EDIT_LEAVE); |
| 5540 } else { |
| 5541 edits.push(EDIT_UPDATE); |
| 5542 current = northWest; |
| 5543 } |
| 5544 i--; |
| 5545 j--; |
| 5546 } else if (min == west) { |
| 5547 edits.push(EDIT_DELETE); |
| 5548 i--; |
| 5549 current = west; |
| 5550 } else { |
| 5551 edits.push(EDIT_ADD); |
| 5552 j--; |
| 5553 current = north; |
| 5554 } |
| 5555 } |
| 5556 |
| 5557 edits.reverse(); |
| 5558 return edits; |
| 5559 }, |
| 5560 |
| 5561 /** |
| 5562 * Splice Projection functions: |
| 5563 * |
| 5564 * A splice map is a representation of how a previous array of items |
| 5565 * was transformed into a new array of items. Conceptually it is a list of |
| 5566 * tuples of |
| 5567 * |
| 5568 * <index, removed, addedCount> |
| 5569 * |
| 5570 * which are kept in ascending index order of. The tuple represents that at |
| 5571 * the |index|, |removed| sequence of items were removed, and counting forwa
rd |
| 5572 * from |index|, |addedCount| items were added. |
| 5573 */ |
| 5574 |
| 5575 /** |
| 5576 * Lacking individual splice mutation information, the minimal set of |
| 5577 * splices can be synthesized given the previous state and final state of an |
| 5578 * array. The basic approach is to calculate the edit distance matrix and |
| 5579 * choose the shortest path through it. |
| 5580 * |
| 5581 * Complexity: O(l * p) |
| 5582 * l: The length of the current array |
| 5583 * p: The length of the old array |
| 5584 */ |
| 5585 calcSplices: function(current, currentStart, currentEnd, |
| 5586 old, oldStart, oldEnd) { |
| 5587 var prefixCount = 0; |
| 5588 var suffixCount = 0; |
| 5589 |
| 5590 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); |
| 5591 if (currentStart == 0 && oldStart == 0) |
| 5592 prefixCount = this.sharedPrefix(current, old, minLength); |
| 5593 |
| 5594 if (currentEnd == current.length && oldEnd == old.length) |
| 5595 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); |
| 5596 |
| 5597 currentStart += prefixCount; |
| 5598 oldStart += prefixCount; |
| 5599 currentEnd -= suffixCount; |
| 5600 oldEnd -= suffixCount; |
| 5601 |
| 5602 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) |
| 5603 return []; |
| 5604 |
| 5605 if (currentStart == currentEnd) { |
| 5606 var splice = newSplice(currentStart, [], 0); |
| 5607 while (oldStart < oldEnd) |
| 5608 splice.removed.push(old[oldStart++]); |
| 5609 |
| 5610 return [ splice ]; |
| 5611 } else if (oldStart == oldEnd) |
| 5612 return [ newSplice(currentStart, [], currentEnd - currentStart) ]; |
| 5613 |
| 5614 var ops = this.spliceOperationsFromEditDistances( |
| 5615 this.calcEditDistances(current, currentStart, currentEnd, |
| 5616 old, oldStart, oldEnd)); |
| 5617 |
| 5618 var splice = undefined; |
| 5619 var splices = []; |
| 5620 var index = currentStart; |
| 5621 var oldIndex = oldStart; |
| 5622 for (var i = 0; i < ops.length; i++) { |
| 5623 switch(ops[i]) { |
| 5624 case EDIT_LEAVE: |
| 5625 if (splice) { |
| 5626 splices.push(splice); |
| 5627 splice = undefined; |
| 5628 } |
| 5629 |
| 5630 index++; |
| 5631 oldIndex++; |
| 5632 break; |
| 5633 case EDIT_UPDATE: |
| 5634 if (!splice) |
| 5635 splice = newSplice(index, [], 0); |
| 5636 |
| 5637 splice.addedCount++; |
| 5638 index++; |
| 5639 |
| 5640 splice.removed.push(old[oldIndex]); |
| 5641 oldIndex++; |
| 5642 break; |
| 5643 case EDIT_ADD: |
| 5644 if (!splice) |
| 5645 splice = newSplice(index, [], 0); |
| 5646 |
| 5647 splice.addedCount++; |
| 5648 index++; |
| 5649 break; |
| 5650 case EDIT_DELETE: |
| 5651 if (!splice) |
| 5652 splice = newSplice(index, [], 0); |
| 5653 |
| 5654 splice.removed.push(old[oldIndex]); |
| 5655 oldIndex++; |
| 5656 break; |
| 5657 } |
| 5658 } |
| 5659 |
| 5660 if (splice) { |
| 5661 splices.push(splice); |
| 5662 } |
| 5663 return splices; |
| 5664 }, |
| 5665 |
| 5666 sharedPrefix: function(current, old, searchLength) { |
| 5667 for (var i = 0; i < searchLength; i++) |
| 5668 if (!this.equals(current[i], old[i])) |
| 5669 return i; |
| 5670 return searchLength; |
| 5671 }, |
| 5672 |
| 5673 sharedSuffix: function(current, old, searchLength) { |
| 5674 var index1 = current.length; |
| 5675 var index2 = old.length; |
| 5676 var count = 0; |
| 5677 while (count < searchLength && this.equals(current[--index1], old[--index2
])) |
| 5678 count++; |
| 5679 |
| 5680 return count; |
| 5681 }, |
| 5682 |
| 5683 calculateSplices: function(current, previous) { |
| 5684 return this.calcSplices(current, 0, current.length, previous, 0, |
| 5685 previous.length); |
| 5686 }, |
| 5687 |
| 5688 equals: function(currentValue, previousValue) { |
| 5689 return currentValue === previousValue; |
| 5690 } |
| 5691 }; |
| 5692 |
| 5693 var arraySplice = new ArraySplice(); |
| 5694 |
| 5695 function calcSplices(current, currentStart, currentEnd, |
| 5696 old, oldStart, oldEnd) { |
| 5697 return arraySplice.calcSplices(current, currentStart, currentEnd, |
| 5698 old, oldStart, oldEnd); |
| 5699 } |
| 5700 |
| 5701 function intersect(start1, end1, start2, end2) { |
| 5702 // Disjoint |
| 5703 if (end1 < start2 || end2 < start1) |
| 5704 return -1; |
| 5705 |
| 5706 // Adjacent |
| 5707 if (end1 == start2 || end2 == start1) |
| 5708 return 0; |
| 5709 |
| 5710 // Non-zero intersect, span1 first |
| 5711 if (start1 < start2) { |
| 5712 if (end1 < end2) |
| 5713 return end1 - start2; // Overlap |
| 5714 else |
| 5715 return end2 - start2; // Contained |
| 5716 } else { |
| 5717 // Non-zero intersect, span2 first |
| 5718 if (end2 < end1) |
| 5719 return end2 - start1; // Overlap |
| 5720 else |
| 5721 return end1 - start1; // Contained |
| 5722 } |
| 5723 } |
| 5724 |
| 5725 function mergeSplice(splices, index, removed, addedCount) { |
| 5726 |
| 5727 var splice = newSplice(index, removed, addedCount); |
| 5728 |
| 5729 var inserted = false; |
| 5730 var insertionOffset = 0; |
| 5731 |
| 5732 for (var i = 0; i < splices.length; i++) { |
| 5733 var current = splices[i]; |
| 5734 current.index += insertionOffset; |
| 5735 |
| 5736 if (inserted) |
| 5737 continue; |
| 5738 |
| 5739 var intersectCount = intersect(splice.index, |
| 5740 splice.index + splice.removed.length, |
| 5741 current.index, |
| 5742 current.index + current.addedCount); |
| 5743 |
| 5744 if (intersectCount >= 0) { |
| 5745 // Merge the two splices |
| 5746 |
| 5747 splices.splice(i, 1); |
| 5748 i--; |
| 5749 |
| 5750 insertionOffset -= current.addedCount - current.removed.length; |
| 5751 |
| 5752 splice.addedCount += current.addedCount - intersectCount; |
| 5753 var deleteCount = splice.removed.length + |
| 5754 current.removed.length - intersectCount; |
| 5755 |
| 5756 if (!splice.addedCount && !deleteCount) { |
| 5757 // merged splice is a noop. discard. |
| 5758 inserted = true; |
| 5759 } else { |
| 5760 var removed = current.removed; |
| 5761 |
| 5762 if (splice.index < current.index) { |
| 5763 // some prefix of splice.removed is prepended to current.removed. |
| 5764 var prepend = splice.removed.slice(0, current.index - splice.index); |
| 5765 Array.prototype.push.apply(prepend, removed); |
| 5766 removed = prepend; |
| 5767 } |
| 5768 |
| 5769 if (splice.index + splice.removed.length > current.index + current.add
edCount) { |
| 5770 // some suffix of splice.removed is appended to current.removed. |
| 5771 var append = splice.removed.slice(current.index + current.addedCount
- splice.index); |
| 5772 Array.prototype.push.apply(removed, append); |
| 5773 } |
| 5774 |
| 5775 splice.removed = removed; |
| 5776 if (current.index < splice.index) { |
| 5777 splice.index = current.index; |
| 5778 } |
| 5779 } |
| 5780 } else if (splice.index < current.index) { |
| 5781 // Insert splice here. |
| 5782 |
| 5783 inserted = true; |
| 5784 |
| 5785 splices.splice(i, 0, splice); |
| 5786 i++; |
| 5787 |
| 5788 var offset = splice.addedCount - splice.removed.length |
| 5789 current.index += offset; |
| 5790 insertionOffset += offset; |
| 5791 } |
| 5792 } |
| 5793 |
| 5794 if (!inserted) |
| 5795 splices.push(splice); |
| 5796 } |
| 5797 |
| 5798 function createInitialSplices(array, changeRecords) { |
| 5799 var splices = []; |
| 5800 |
| 5801 for (var i = 0; i < changeRecords.length; i++) { |
| 5802 var record = changeRecords[i]; |
| 5803 switch(record.type) { |
| 5804 case 'splice': |
| 5805 mergeSplice(splices, record.index, record.removed.slice(), record.adde
dCount); |
| 5806 break; |
| 5807 case 'add': |
| 5808 case 'update': |
| 5809 case 'delete': |
| 5810 if (!isIndex(record.name)) |
| 5811 continue; |
| 5812 var index = toNumber(record.name); |
| 5813 if (index < 0) |
| 5814 continue; |
| 5815 mergeSplice(splices, index, [record.oldValue], 1); |
| 5816 break; |
| 5817 default: |
| 5818 console.error('Unexpected record type: ' + JSON.stringify(record)); |
| 5819 break; |
| 5820 } |
| 5821 } |
| 5822 |
| 5823 return splices; |
| 5824 } |
| 5825 |
| 5826 function projectArraySplices(array, changeRecords) { |
| 5827 var splices = []; |
| 5828 |
| 5829 createInitialSplices(array, changeRecords).forEach(function(splice) { |
| 5830 if (splice.addedCount == 1 && splice.removed.length == 1) { |
| 5831 if (splice.removed[0] !== array[splice.index]) |
| 5832 splices.push(splice); |
| 5833 |
| 5834 return |
| 5835 }; |
| 5836 |
| 5837 splices = splices.concat(calcSplices(array, splice.index, splice.index + s
plice.addedCount, |
| 5838 splice.removed, 0, splice.removed.len
gth)); |
| 5839 }); |
| 5840 |
| 5841 return splices; |
| 5842 } |
| 5843 |
| 5844 // Export the observe-js object for **Node.js**, with |
| 5845 // backwards-compatibility for the old `require()` API. If we're in |
| 5846 // the browser, export as a global object. |
| 5847 |
| 5848 var expose = global; |
| 5849 |
| 5850 if (typeof exports !== 'undefined') { |
| 5851 if (typeof module !== 'undefined' && module.exports) { |
| 5852 expose = exports = module.exports; |
| 5853 } |
| 5854 expose = exports; |
| 5855 } |
| 5856 |
| 5857 expose.Observer = Observer; |
| 5858 expose.Observer.runEOM_ = runEOM; |
| 5859 expose.Observer.observerSentinel_ = observerSentinel; // for testing. |
| 5860 expose.Observer.hasObjectObserve = hasObserve; |
| 5861 expose.ArrayObserver = ArrayObserver; |
| 5862 expose.ArrayObserver.calculateSplices = function(current, previous) { |
| 5863 return arraySplice.calculateSplices(current, previous); |
| 5864 }; |
| 5865 |
| 5866 expose.ArraySplice = ArraySplice; |
| 5867 expose.ObjectObserver = ObjectObserver; |
| 5868 expose.PathObserver = PathObserver; |
| 5869 expose.CompoundObserver = CompoundObserver; |
| 5870 expose.Path = Path; |
| 5871 expose.ObserverTransform = ObserverTransform; |
| 5872 |
| 5873 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m
odule ? global : this || window); |
| 5874 |
| 5875 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 5876 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 5877 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 5878 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 5879 // Code distributed by Google as part of the polymer project is also |
| 5880 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 5881 |
| 5882 (function(global) { |
| 5883 'use strict'; |
| 5884 |
| 5885 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); |
| 5886 |
| 5887 function getTreeScope(node) { |
| 5888 while (node.parentNode) { |
| 5889 node = node.parentNode; |
| 5890 } |
| 5891 |
| 5892 return typeof node.getElementById === 'function' ? node : null; |
| 5893 } |
| 5894 |
| 5895 Node.prototype.bind = function(name, observable) { |
| 5896 console.error('Unhandled binding to Node: ', this, name, observable); |
| 5897 }; |
| 5898 |
| 5899 Node.prototype.bindFinished = function() {}; |
| 5900 |
| 5901 function updateBindings(node, name, binding) { |
| 5902 var bindings = node.bindings_; |
| 5903 if (!bindings) |
| 5904 bindings = node.bindings_ = {}; |
| 5905 |
| 5906 if (bindings[name]) |
| 5907 binding[name].close(); |
| 5908 |
| 5909 return bindings[name] = binding; |
| 5910 } |
| 5911 |
| 5912 function returnBinding(node, name, binding) { |
| 5913 return binding; |
| 5914 } |
| 5915 |
| 5916 function sanitizeValue(value) { |
| 5917 return value == null ? '' : value; |
| 5918 } |
| 5919 |
| 5920 function updateText(node, value) { |
| 5921 node.data = sanitizeValue(value); |
| 5922 } |
| 5923 |
| 5924 function textBinding(node) { |
| 5925 return function(value) { |
| 5926 return updateText(node, value); |
| 5927 }; |
| 5928 } |
| 5929 |
| 5930 var maybeUpdateBindings = returnBinding; |
| 5931 |
| 5932 Object.defineProperty(Platform, 'enableBindingsReflection', { |
| 5933 get: function() { |
| 5934 return maybeUpdateBindings === updateBindings; |
| 5935 }, |
| 5936 set: function(enable) { |
| 5937 maybeUpdateBindings = enable ? updateBindings : returnBinding; |
| 5938 return enable; |
| 5939 }, |
| 5940 configurable: true |
| 5941 }); |
| 5942 |
| 5943 Text.prototype.bind = function(name, value, oneTime) { |
| 5944 if (name !== 'textContent') |
| 5945 return Node.prototype.bind.call(this, name, value, oneTime); |
| 5946 |
| 5947 if (oneTime) |
| 5948 return updateText(this, value); |
| 5949 |
| 5950 var observable = value; |
| 5951 updateText(this, observable.open(textBinding(this))); |
| 5952 return maybeUpdateBindings(this, name, observable); |
| 5953 } |
| 5954 |
| 5955 function updateAttribute(el, name, conditional, value) { |
| 5956 if (conditional) { |
| 5957 if (value) |
| 5958 el.setAttribute(name, ''); |
| 5959 else |
| 5960 el.removeAttribute(name); |
| 5961 return; |
| 5962 } |
| 5963 |
| 5964 el.setAttribute(name, sanitizeValue(value)); |
| 5965 } |
| 5966 |
| 5967 function attributeBinding(el, name, conditional) { |
| 5968 return function(value) { |
| 5969 updateAttribute(el, name, conditional, value); |
| 5970 }; |
| 5971 } |
| 5972 |
| 5973 Element.prototype.bind = function(name, value, oneTime) { |
| 5974 var conditional = name[name.length - 1] == '?'; |
| 5975 if (conditional) { |
| 5976 this.removeAttribute(name); |
| 5977 name = name.slice(0, -1); |
| 5978 } |
| 5979 |
| 5980 if (oneTime) |
| 5981 return updateAttribute(this, name, conditional, value); |
| 5982 |
| 5983 |
| 5984 var observable = value; |
| 5985 updateAttribute(this, name, conditional, |
| 5986 observable.open(attributeBinding(this, name, conditional))); |
| 5987 |
| 5988 return maybeUpdateBindings(this, name, observable); |
| 5989 }; |
| 5990 |
| 5991 var checkboxEventType; |
| 5992 (function() { |
| 5993 // Attempt to feature-detect which event (change or click) is fired first |
| 5994 // for checkboxes. |
| 5995 var div = document.createElement('div'); |
| 5996 var checkbox = div.appendChild(document.createElement('input')); |
| 5997 checkbox.setAttribute('type', 'checkbox'); |
| 5998 var first; |
| 5999 var count = 0; |
| 6000 checkbox.addEventListener('click', function(e) { |
| 6001 count++; |
| 6002 first = first || 'click'; |
| 6003 }); |
| 6004 checkbox.addEventListener('change', function() { |
| 6005 count++; |
| 6006 first = first || 'change'; |
| 6007 }); |
| 6008 |
| 6009 var event = document.createEvent('MouseEvent'); |
| 6010 event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, |
| 6011 false, false, false, 0, null); |
| 6012 checkbox.dispatchEvent(event); |
| 6013 // WebKit/Blink don't fire the change event if the element is outside the |
| 6014 // document, so assume 'change' for that case. |
| 6015 checkboxEventType = count == 1 ? 'change' : first; |
| 6016 })(); |
| 6017 |
| 6018 function getEventForInputType(element) { |
| 6019 switch (element.type) { |
| 6020 case 'checkbox': |
| 6021 return checkboxEventType; |
| 6022 case 'radio': |
| 6023 case 'select-multiple': |
| 6024 case 'select-one': |
| 6025 return 'change'; |
| 6026 case 'range': |
| 6027 if (/Trident|MSIE/.test(navigator.userAgent)) |
| 6028 return 'change'; |
| 6029 default: |
| 6030 return 'input'; |
| 6031 } |
| 6032 } |
| 6033 |
| 6034 function updateInput(input, property, value, santizeFn) { |
| 6035 input[property] = (santizeFn || sanitizeValue)(value); |
| 6036 } |
| 6037 |
| 6038 function inputBinding(input, property, santizeFn) { |
| 6039 return function(value) { |
| 6040 return updateInput(input, property, value, santizeFn); |
| 6041 } |
| 6042 } |
| 6043 |
| 6044 function noop() {} |
| 6045 |
| 6046 function bindInputEvent(input, property, observable, postEventFn) { |
| 6047 var eventType = getEventForInputType(input); |
| 6048 |
| 6049 function eventHandler() { |
| 6050 observable.setValue(input[property]); |
| 6051 observable.discardChanges(); |
| 6052 (postEventFn || noop)(input); |
| 6053 Platform.performMicrotaskCheckpoint(); |
| 6054 } |
| 6055 input.addEventListener(eventType, eventHandler); |
| 6056 |
| 6057 return { |
| 6058 close: function() { |
| 6059 input.removeEventListener(eventType, eventHandler); |
| 6060 observable.close(); |
| 6061 }, |
| 6062 |
| 6063 observable_: observable |
| 6064 } |
| 6065 } |
| 6066 |
| 6067 function booleanSanitize(value) { |
| 6068 return Boolean(value); |
| 6069 } |
| 6070 |
| 6071 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. |
| 6072 // Returns an array containing all radio buttons other than |element| that |
| 6073 // have the same |name|, either in the form that |element| belongs to or, |
| 6074 // if no form, in the document tree to which |element| belongs. |
| 6075 // |
| 6076 // This implementation is based upon the HTML spec definition of a |
| 6077 // "radio button group": |
| 6078 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group |
| 6079 // |
| 6080 function getAssociatedRadioButtons(element) { |
| 6081 if (element.form) { |
| 6082 return filter(element.form.elements, function(el) { |
| 6083 return el != element && |
| 6084 el.tagName == 'INPUT' && |
| 6085 el.type == 'radio' && |
| 6086 el.name == element.name; |
| 6087 }); |
| 6088 } else { |
| 6089 var treeScope = getTreeScope(element); |
| 6090 if (!treeScope) |
| 6091 return []; |
| 6092 var radios = treeScope.querySelectorAll( |
| 6093 'input[type="radio"][name="' + element.name + '"]'); |
| 6094 return filter(radios, function(el) { |
| 6095 return el != element && !el.form; |
| 6096 }); |
| 6097 } |
| 6098 } |
| 6099 |
| 6100 function checkedPostEvent(input) { |
| 6101 // Only the radio button that is getting checked gets an event. We |
| 6102 // therefore find all the associated radio buttons and update their |
| 6103 // check binding manually. |
| 6104 if (input.tagName === 'INPUT' && |
| 6105 input.type === 'radio') { |
| 6106 getAssociatedRadioButtons(input).forEach(function(radio) { |
| 6107 var checkedBinding = radio.bindings_.checked; |
| 6108 if (checkedBinding) { |
| 6109 // Set the value directly to avoid an infinite call stack. |
| 6110 checkedBinding.observable_.setValue(false); |
| 6111 } |
| 6112 }); |
| 6113 } |
| 6114 } |
| 6115 |
| 6116 HTMLInputElement.prototype.bind = function(name, value, oneTime) { |
| 6117 if (name !== 'value' && name !== 'checked') |
| 6118 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 6119 |
| 6120 this.removeAttribute(name); |
| 6121 var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; |
| 6122 var postEventFn = name == 'checked' ? checkedPostEvent : noop; |
| 6123 |
| 6124 if (oneTime) |
| 6125 return updateInput(this, name, value, sanitizeFn); |
| 6126 |
| 6127 |
| 6128 var observable = value; |
| 6129 var binding = bindInputEvent(this, name, observable, postEventFn); |
| 6130 updateInput(this, name, |
| 6131 observable.open(inputBinding(this, name, sanitizeFn)), |
| 6132 sanitizeFn); |
| 6133 |
| 6134 // Checkboxes may need to update bindings of other checkboxes. |
| 6135 return updateBindings(this, name, binding); |
| 6136 } |
| 6137 |
| 6138 HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { |
| 6139 if (name !== 'value') |
| 6140 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 6141 |
| 6142 this.removeAttribute('value'); |
| 6143 |
| 6144 if (oneTime) |
| 6145 return updateInput(this, 'value', value); |
| 6146 |
| 6147 var observable = value; |
| 6148 var binding = bindInputEvent(this, 'value', observable); |
| 6149 updateInput(this, 'value', |
| 6150 observable.open(inputBinding(this, 'value', sanitizeValue))); |
| 6151 return maybeUpdateBindings(this, name, binding); |
| 6152 } |
| 6153 |
| 6154 function updateOption(option, value) { |
| 6155 var parentNode = option.parentNode;; |
| 6156 var select; |
| 6157 var selectBinding; |
| 6158 var oldValue; |
| 6159 if (parentNode instanceof HTMLSelectElement && |
| 6160 parentNode.bindings_ && |
| 6161 parentNode.bindings_.value) { |
| 6162 select = parentNode; |
| 6163 selectBinding = select.bindings_.value; |
| 6164 oldValue = select.value; |
| 6165 } |
| 6166 |
| 6167 option.value = sanitizeValue(value); |
| 6168 |
| 6169 if (select && select.value != oldValue) { |
| 6170 selectBinding.observable_.setValue(select.value); |
| 6171 selectBinding.observable_.discardChanges(); |
| 6172 Platform.performMicrotaskCheckpoint(); |
| 6173 } |
| 6174 } |
| 6175 |
| 6176 function optionBinding(option) { |
| 6177 return function(value) { |
| 6178 updateOption(option, value); |
| 6179 } |
| 6180 } |
| 6181 |
| 6182 HTMLOptionElement.prototype.bind = function(name, value, oneTime) { |
| 6183 if (name !== 'value') |
| 6184 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 6185 |
| 6186 this.removeAttribute('value'); |
| 6187 |
| 6188 if (oneTime) |
| 6189 return updateOption(this, value); |
| 6190 |
| 6191 var observable = value; |
| 6192 var binding = bindInputEvent(this, 'value', observable); |
| 6193 updateOption(this, observable.open(optionBinding(this))); |
| 6194 return maybeUpdateBindings(this, name, binding); |
| 6195 } |
| 6196 |
| 6197 HTMLSelectElement.prototype.bind = function(name, value, oneTime) { |
| 6198 if (name === 'selectedindex') |
| 6199 name = 'selectedIndex'; |
| 6200 |
| 6201 if (name !== 'selectedIndex' && name !== 'value') |
| 6202 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 6203 |
| 6204 this.removeAttribute(name); |
| 6205 |
| 6206 if (oneTime) |
| 6207 return updateInput(this, name, value); |
| 6208 |
| 6209 var observable = value; |
| 6210 var binding = bindInputEvent(this, name, observable); |
| 6211 updateInput(this, name, |
| 6212 observable.open(inputBinding(this, name))); |
| 6213 |
| 6214 // Option update events may need to access select bindings. |
| 6215 return updateBindings(this, name, binding); |
| 6216 } |
| 6217 })(this); |
| 6218 |
| 6219 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 6220 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 6221 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 6222 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 6223 // Code distributed by Google as part of the polymer project is also |
| 6224 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 6225 |
| 6226 (function(global) { |
| 6227 'use strict'; |
| 6228 |
| 6229 function assert(v) { |
| 6230 if (!v) |
| 6231 throw new Error('Assertion failed'); |
| 6232 } |
| 6233 |
| 6234 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
| 6235 |
| 6236 function getFragmentRoot(node) { |
| 6237 var p; |
| 6238 while (p = node.parentNode) { |
| 6239 node = p; |
| 6240 } |
| 6241 |
| 6242 return node; |
| 6243 } |
| 6244 |
| 6245 function searchRefId(node, id) { |
| 6246 if (!id) |
| 6247 return; |
| 6248 |
| 6249 var ref; |
| 6250 var selector = '#' + id; |
| 6251 while (!ref) { |
| 6252 node = getFragmentRoot(node); |
| 6253 |
| 6254 if (node.protoContent_) |
| 6255 ref = node.protoContent_.querySelector(selector); |
| 6256 else if (node.getElementById) |
| 6257 ref = node.getElementById(id); |
| 6258 |
| 6259 if (ref || !node.templateCreator_) |
| 6260 break |
| 6261 |
| 6262 node = node.templateCreator_; |
| 6263 } |
| 6264 |
| 6265 return ref; |
| 6266 } |
| 6267 |
| 6268 function getInstanceRoot(node) { |
| 6269 while (node.parentNode) { |
| 6270 node = node.parentNode; |
| 6271 } |
| 6272 return node.templateCreator_ ? node : null; |
| 6273 } |
| 6274 |
| 6275 var Map; |
| 6276 if (global.Map && typeof global.Map.prototype.forEach === 'function') { |
| 6277 Map = global.Map; |
| 6278 } else { |
| 6279 Map = function() { |
| 6280 this.keys = []; |
| 6281 this.values = []; |
| 6282 }; |
| 6283 |
| 6284 Map.prototype = { |
| 6285 set: function(key, value) { |
| 6286 var index = this.keys.indexOf(key); |
| 6287 if (index < 0) { |
| 6288 this.keys.push(key); |
| 6289 this.values.push(value); |
| 6290 } else { |
| 6291 this.values[index] = value; |
| 6292 } |
| 6293 }, |
| 6294 |
| 6295 get: function(key) { |
| 6296 var index = this.keys.indexOf(key); |
| 6297 if (index < 0) |
| 6298 return; |
| 6299 |
| 6300 return this.values[index]; |
| 6301 }, |
| 6302 |
| 6303 delete: function(key, value) { |
| 6304 var index = this.keys.indexOf(key); |
| 6305 if (index < 0) |
| 6306 return false; |
| 6307 |
| 6308 this.keys.splice(index, 1); |
| 6309 this.values.splice(index, 1); |
| 6310 return true; |
| 6311 }, |
| 6312 |
| 6313 forEach: function(f, opt_this) { |
| 6314 for (var i = 0; i < this.keys.length; i++) |
| 6315 f.call(opt_this || this, this.values[i], this.keys[i], this); |
| 6316 } |
| 6317 }; |
| 6318 } |
| 6319 |
| 6320 // JScript does not have __proto__. We wrap all object literals with |
| 6321 // createObject which uses Object.create, Object.defineProperty and |
| 6322 // Object.getOwnPropertyDescriptor to create a new object that does the exact |
| 6323 // same thing. The main downside to this solution is that we have to extract |
| 6324 // all those property descriptors for IE. |
| 6325 var createObject = ('__proto__' in {}) ? |
| 6326 function(obj) { return obj; } : |
| 6327 function(obj) { |
| 6328 var proto = obj.__proto__; |
| 6329 if (!proto) |
| 6330 return obj; |
| 6331 var newObject = Object.create(proto); |
| 6332 Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 6333 Object.defineProperty(newObject, name, |
| 6334 Object.getOwnPropertyDescriptor(obj, name)); |
| 6335 }); |
| 6336 return newObject; |
| 6337 }; |
| 6338 |
| 6339 // IE does not support have Document.prototype.contains. |
| 6340 if (typeof document.contains != 'function') { |
| 6341 Document.prototype.contains = function(node) { |
| 6342 if (node === this || node.parentNode === this) |
| 6343 return true; |
| 6344 return this.documentElement.contains(node); |
| 6345 } |
| 6346 } |
| 6347 |
| 6348 var BIND = 'bind'; |
| 6349 var REPEAT = 'repeat'; |
| 6350 var IF = 'if'; |
| 6351 |
| 6352 var templateAttributeDirectives = { |
| 6353 'template': true, |
| 6354 'repeat': true, |
| 6355 'bind': true, |
| 6356 'ref': true |
| 6357 }; |
| 6358 |
| 6359 var semanticTemplateElements = { |
| 6360 'THEAD': true, |
| 6361 'TBODY': true, |
| 6362 'TFOOT': true, |
| 6363 'TH': true, |
| 6364 'TR': true, |
| 6365 'TD': true, |
| 6366 'COLGROUP': true, |
| 6367 'COL': true, |
| 6368 'CAPTION': true, |
| 6369 'OPTION': true, |
| 6370 'OPTGROUP': true |
| 6371 }; |
| 6372 |
| 6373 var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined'; |
| 6374 if (hasTemplateElement) { |
| 6375 // TODO(rafaelw): Remove when fix for |
| 6376 // https://codereview.chromium.org/164803002/ |
| 6377 // makes it to Chrome release. |
| 6378 (function() { |
| 6379 var t = document.createElement('template'); |
| 6380 var d = t.content.ownerDocument; |
| 6381 var html = d.appendChild(d.createElement('html')); |
| 6382 var head = html.appendChild(d.createElement('head')); |
| 6383 var base = d.createElement('base'); |
| 6384 base.href = document.baseURI; |
| 6385 head.appendChild(base); |
| 6386 })(); |
| 6387 } |
| 6388 |
| 6389 var allTemplatesSelectors = 'template, ' + |
| 6390 Object.keys(semanticTemplateElements).map(function(tagName) { |
| 6391 return tagName.toLowerCase() + '[template]'; |
| 6392 }).join(', '); |
| 6393 |
| 6394 function isSVGTemplate(el) { |
| 6395 return el.tagName == 'template' && |
| 6396 el.namespaceURI == 'http://www.w3.org/2000/svg'; |
| 6397 } |
| 6398 |
| 6399 function isHTMLTemplate(el) { |
| 6400 return el.tagName == 'TEMPLATE' && |
| 6401 el.namespaceURI == 'http://www.w3.org/1999/xhtml'; |
| 6402 } |
| 6403 |
| 6404 function isAttributeTemplate(el) { |
| 6405 return Boolean(semanticTemplateElements[el.tagName] && |
| 6406 el.hasAttribute('template')); |
| 6407 } |
| 6408 |
| 6409 function isTemplate(el) { |
| 6410 if (el.isTemplate_ === undefined) |
| 6411 el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el); |
| 6412 |
| 6413 return el.isTemplate_; |
| 6414 } |
| 6415 |
| 6416 // FIXME: Observe templates being added/removed from documents |
| 6417 // FIXME: Expose imperative API to decorate and observe templates in |
| 6418 // "disconnected tress" (e.g. ShadowRoot) |
| 6419 document.addEventListener('DOMContentLoaded', function(e) { |
| 6420 bootstrapTemplatesRecursivelyFrom(document); |
| 6421 // FIXME: Is this needed? Seems like it shouldn't be. |
| 6422 Platform.performMicrotaskCheckpoint(); |
| 6423 }, false); |
| 6424 |
| 6425 function forAllTemplatesFrom(node, fn) { |
| 6426 var subTemplates = node.querySelectorAll(allTemplatesSelectors); |
| 6427 |
| 6428 if (isTemplate(node)) |
| 6429 fn(node) |
| 6430 forEach(subTemplates, fn); |
| 6431 } |
| 6432 |
| 6433 function bootstrapTemplatesRecursivelyFrom(node) { |
| 6434 function bootstrap(template) { |
| 6435 if (!HTMLTemplateElement.decorate(template)) |
| 6436 bootstrapTemplatesRecursivelyFrom(template.content); |
| 6437 } |
| 6438 |
| 6439 forAllTemplatesFrom(node, bootstrap); |
| 6440 } |
| 6441 |
| 6442 if (!hasTemplateElement) { |
| 6443 /** |
| 6444 * This represents a <template> element. |
| 6445 * @constructor |
| 6446 * @extends {HTMLElement} |
| 6447 */ |
| 6448 global.HTMLTemplateElement = function() { |
| 6449 throw TypeError('Illegal constructor'); |
| 6450 }; |
| 6451 } |
| 6452 |
| 6453 var hasProto = '__proto__' in {}; |
| 6454 |
| 6455 function mixin(to, from) { |
| 6456 Object.getOwnPropertyNames(from).forEach(function(name) { |
| 6457 Object.defineProperty(to, name, |
| 6458 Object.getOwnPropertyDescriptor(from, name)); |
| 6459 }); |
| 6460 } |
| 6461 |
| 6462 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner |
| 6463 function getOrCreateTemplateContentsOwner(template) { |
| 6464 var doc = template.ownerDocument |
| 6465 if (!doc.defaultView) |
| 6466 return doc; |
| 6467 var d = doc.templateContentsOwner_; |
| 6468 if (!d) { |
| 6469 // TODO(arv): This should either be a Document or HTMLDocument depending |
| 6470 // on doc. |
| 6471 d = doc.implementation.createHTMLDocument(''); |
| 6472 while (d.lastChild) { |
| 6473 d.removeChild(d.lastChild); |
| 6474 } |
| 6475 doc.templateContentsOwner_ = d; |
| 6476 } |
| 6477 return d; |
| 6478 } |
| 6479 |
| 6480 function getTemplateStagingDocument(template) { |
| 6481 if (!template.stagingDocument_) { |
| 6482 var owner = template.ownerDocument; |
| 6483 if (!owner.stagingDocument_) { |
| 6484 owner.stagingDocument_ = owner.implementation.createHTMLDocument(''); |
| 6485 owner.stagingDocument_.isStagingDocument = true; |
| 6486 // TODO(rafaelw): Remove when fix for |
| 6487 // https://codereview.chromium.org/164803002/ |
| 6488 // makes it to Chrome release. |
| 6489 var base = owner.stagingDocument_.createElement('base'); |
| 6490 base.href = document.baseURI; |
| 6491 owner.stagingDocument_.head.appendChild(base); |
| 6492 |
| 6493 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_; |
| 6494 } |
| 6495 |
| 6496 template.stagingDocument_ = owner.stagingDocument_; |
| 6497 } |
| 6498 |
| 6499 return template.stagingDocument_; |
| 6500 } |
| 6501 |
| 6502 // For non-template browsers, the parser will disallow <template> in certain |
| 6503 // locations, so we allow "attribute templates" which combine the template |
| 6504 // element with the top-level container node of the content, e.g. |
| 6505 // |
| 6506 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr> |
| 6507 // |
| 6508 // becomes |
| 6509 // |
| 6510 // <template repeat="{{ foo }}"> |
| 6511 // + #document-fragment |
| 6512 // + <tr class="bar"> |
| 6513 // + <td>Bar</td> |
| 6514 // |
| 6515 function extractTemplateFromAttributeTemplate(el) { |
| 6516 var template = el.ownerDocument.createElement('template'); |
| 6517 el.parentNode.insertBefore(template, el); |
| 6518 |
| 6519 var attribs = el.attributes; |
| 6520 var count = attribs.length; |
| 6521 while (count-- > 0) { |
| 6522 var attrib = attribs[count]; |
| 6523 if (templateAttributeDirectives[attrib.name]) { |
| 6524 if (attrib.name !== 'template') |
| 6525 template.setAttribute(attrib.name, attrib.value); |
| 6526 el.removeAttribute(attrib.name); |
| 6527 } |
| 6528 } |
| 6529 |
| 6530 return template; |
| 6531 } |
| 6532 |
| 6533 function extractTemplateFromSVGTemplate(el) { |
| 6534 var template = el.ownerDocument.createElement('template'); |
| 6535 el.parentNode.insertBefore(template, el); |
| 6536 |
| 6537 var attribs = el.attributes; |
| 6538 var count = attribs.length; |
| 6539 while (count-- > 0) { |
| 6540 var attrib = attribs[count]; |
| 6541 template.setAttribute(attrib.name, attrib.value); |
| 6542 el.removeAttribute(attrib.name); |
| 6543 } |
| 6544 |
| 6545 el.parentNode.removeChild(el); |
| 6546 return template; |
| 6547 } |
| 6548 |
| 6549 function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) { |
| 6550 var content = template.content; |
| 6551 if (useRoot) { |
| 6552 content.appendChild(el); |
| 6553 return; |
| 6554 } |
| 6555 |
| 6556 var child; |
| 6557 while (child = el.firstChild) { |
| 6558 content.appendChild(child); |
| 6559 } |
| 6560 } |
| 6561 |
| 6562 var templateObserver; |
| 6563 if (typeof MutationObserver == 'function') { |
| 6564 templateObserver = new MutationObserver(function(records) { |
| 6565 for (var i = 0; i < records.length; i++) { |
| 6566 records[i].target.refChanged_(); |
| 6567 } |
| 6568 }); |
| 6569 } |
| 6570 |
| 6571 /** |
| 6572 * Ensures proper API and content model for template elements. |
| 6573 * @param {HTMLTemplateElement} opt_instanceRef The template element which |
| 6574 * |el| template element will return as the value of its ref(), and whose |
| 6575 * content will be used as source when createInstance() is invoked. |
| 6576 */ |
| 6577 HTMLTemplateElement.decorate = function(el, opt_instanceRef) { |
| 6578 if (el.templateIsDecorated_) |
| 6579 return false; |
| 6580 |
| 6581 var templateElement = el; |
| 6582 templateElement.templateIsDecorated_ = true; |
| 6583 |
| 6584 var isNativeHTMLTemplate = isHTMLTemplate(templateElement) && |
| 6585 hasTemplateElement; |
| 6586 var bootstrapContents = isNativeHTMLTemplate; |
| 6587 var liftContents = !isNativeHTMLTemplate; |
| 6588 var liftRoot = false; |
| 6589 |
| 6590 if (!isNativeHTMLTemplate) { |
| 6591 if (isAttributeTemplate(templateElement)) { |
| 6592 assert(!opt_instanceRef); |
| 6593 templateElement = extractTemplateFromAttributeTemplate(el); |
| 6594 templateElement.templateIsDecorated_ = true; |
| 6595 isNativeHTMLTemplate = hasTemplateElement; |
| 6596 liftRoot = true; |
| 6597 } else if (isSVGTemplate(templateElement)) { |
| 6598 templateElement = extractTemplateFromSVGTemplate(el); |
| 6599 templateElement.templateIsDecorated_ = true; |
| 6600 isNativeHTMLTemplate = hasTemplateElement; |
| 6601 } |
| 6602 } |
| 6603 |
| 6604 if (!isNativeHTMLTemplate) { |
| 6605 fixTemplateElementPrototype(templateElement); |
| 6606 var doc = getOrCreateTemplateContentsOwner(templateElement); |
| 6607 templateElement.content_ = doc.createDocumentFragment(); |
| 6608 } |
| 6609 |
| 6610 if (opt_instanceRef) { |
| 6611 // template is contained within an instance, its direct content must be |
| 6612 // empty |
| 6613 templateElement.instanceRef_ = opt_instanceRef; |
| 6614 } else if (liftContents) { |
| 6615 liftNonNativeTemplateChildrenIntoContent(templateElement, |
| 6616 el, |
| 6617 liftRoot); |
| 6618 } else if (bootstrapContents) { |
| 6619 bootstrapTemplatesRecursivelyFrom(templateElement.content); |
| 6620 } |
| 6621 |
| 6622 return true; |
| 6623 }; |
| 6624 |
| 6625 // TODO(rafaelw): This used to decorate recursively all templates from a given |
| 6626 // node. This happens by default on 'DOMContentLoaded', but may be needed |
| 6627 // in subtrees not descendent from document (e.g. ShadowRoot). |
| 6628 // Review whether this is the right public API. |
| 6629 HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom; |
| 6630 |
| 6631 var htmlElement = global.HTMLUnknownElement || HTMLElement; |
| 6632 |
| 6633 var contentDescriptor = { |
| 6634 get: function() { |
| 6635 return this.content_; |
| 6636 }, |
| 6637 enumerable: true, |
| 6638 configurable: true |
| 6639 }; |
| 6640 |
| 6641 if (!hasTemplateElement) { |
| 6642 // Gecko is more picky with the prototype than WebKit. Make sure to use the |
| 6643 // same prototype as created in the constructor. |
| 6644 HTMLTemplateElement.prototype = Object.create(htmlElement.prototype); |
| 6645 |
| 6646 Object.defineProperty(HTMLTemplateElement.prototype, 'content', |
| 6647 contentDescriptor); |
| 6648 } |
| 6649 |
| 6650 function fixTemplateElementPrototype(el) { |
| 6651 if (hasProto) |
| 6652 el.__proto__ = HTMLTemplateElement.prototype; |
| 6653 else |
| 6654 mixin(el, HTMLTemplateElement.prototype); |
| 6655 } |
| 6656 |
| 6657 function ensureSetModelScheduled(template) { |
| 6658 if (!template.setModelFn_) { |
| 6659 template.setModelFn_ = function() { |
| 6660 template.setModelFnScheduled_ = false; |
| 6661 var map = getBindings(template, |
| 6662 template.delegate_ && template.delegate_.prepareBinding); |
| 6663 processBindings(template, map, template.model_); |
| 6664 }; |
| 6665 } |
| 6666 |
| 6667 if (!template.setModelFnScheduled_) { |
| 6668 template.setModelFnScheduled_ = true; |
| 6669 Observer.runEOM_(template.setModelFn_); |
| 6670 } |
| 6671 } |
| 6672 |
| 6673 mixin(HTMLTemplateElement.prototype, { |
| 6674 bind: function(name, value, oneTime) { |
| 6675 if (name != 'ref') |
| 6676 return Element.prototype.bind.call(this, name, value, oneTime); |
| 6677 |
| 6678 var self = this; |
| 6679 var ref = oneTime ? value : value.open(function(ref) { |
| 6680 self.setAttribute('ref', ref); |
| 6681 self.refChanged_(); |
| 6682 }); |
| 6683 |
| 6684 this.setAttribute('ref', ref); |
| 6685 this.refChanged_(); |
| 6686 if (oneTime) |
| 6687 return; |
| 6688 |
| 6689 if (!this.bindings_) { |
| 6690 this.bindings_ = { ref: value }; |
| 6691 } else { |
| 6692 this.bindings_.ref = value; |
| 6693 } |
| 6694 |
| 6695 return value; |
| 6696 }, |
| 6697 |
| 6698 processBindingDirectives_: function(directives) { |
| 6699 if (this.iterator_) |
| 6700 this.iterator_.closeDeps(); |
| 6701 |
| 6702 if (!directives.if && !directives.bind && !directives.repeat) { |
| 6703 if (this.iterator_) { |
| 6704 this.iterator_.close(); |
| 6705 this.iterator_ = undefined; |
| 6706 } |
| 6707 |
| 6708 return; |
| 6709 } |
| 6710 |
| 6711 if (!this.iterator_) { |
| 6712 this.iterator_ = new TemplateIterator(this); |
| 6713 } |
| 6714 |
| 6715 this.iterator_.updateDependencies(directives, this.model_); |
| 6716 |
| 6717 if (templateObserver) { |
| 6718 templateObserver.observe(this, { attributes: true, |
| 6719 attributeFilter: ['ref'] }); |
| 6720 } |
| 6721 |
| 6722 return this.iterator_; |
| 6723 }, |
| 6724 |
| 6725 createInstance: function(model, bindingDelegate, delegate_) { |
| 6726 if (bindingDelegate) |
| 6727 delegate_ = this.newDelegate_(bindingDelegate); |
| 6728 else if (!delegate_) |
| 6729 delegate_ = this.delegate_; |
| 6730 |
| 6731 if (!this.refContent_) |
| 6732 this.refContent_ = this.ref_.content; |
| 6733 var content = this.refContent_; |
| 6734 if (content.firstChild === null) |
| 6735 return emptyInstance; |
| 6736 |
| 6737 var map = getInstanceBindingMap(content, delegate_); |
| 6738 var stagingDocument = getTemplateStagingDocument(this); |
| 6739 var instance = stagingDocument.createDocumentFragment(); |
| 6740 instance.templateCreator_ = this; |
| 6741 instance.protoContent_ = content; |
| 6742 instance.bindings_ = []; |
| 6743 instance.terminator_ = null; |
| 6744 var instanceRecord = instance.templateInstance_ = { |
| 6745 firstNode: null, |
| 6746 lastNode: null, |
| 6747 model: model |
| 6748 }; |
| 6749 |
| 6750 var i = 0; |
| 6751 var collectTerminator = false; |
| 6752 for (var child = content.firstChild; child; child = child.nextSibling) { |
| 6753 // The terminator of the instance is the clone of the last child of the |
| 6754 // content. If the last child is an active template, it may produce |
| 6755 // instances as a result of production, so simply collecting the last |
| 6756 // child of the instance after it has finished producing may be wrong. |
| 6757 if (child.nextSibling === null) |
| 6758 collectTerminator = true; |
| 6759 |
| 6760 var clone = cloneAndBindInstance(child, instance, stagingDocument, |
| 6761 map.children[i++], |
| 6762 model, |
| 6763 delegate_, |
| 6764 instance.bindings_); |
| 6765 clone.templateInstance_ = instanceRecord; |
| 6766 if (collectTerminator) |
| 6767 instance.terminator_ = clone; |
| 6768 } |
| 6769 |
| 6770 instanceRecord.firstNode = instance.firstChild; |
| 6771 instanceRecord.lastNode = instance.lastChild; |
| 6772 instance.templateCreator_ = undefined; |
| 6773 instance.protoContent_ = undefined; |
| 6774 return instance; |
| 6775 }, |
| 6776 |
| 6777 get model() { |
| 6778 return this.model_; |
| 6779 }, |
| 6780 |
| 6781 set model(model) { |
| 6782 this.model_ = model; |
| 6783 ensureSetModelScheduled(this); |
| 6784 }, |
| 6785 |
| 6786 get bindingDelegate() { |
| 6787 return this.delegate_ && this.delegate_.raw; |
| 6788 }, |
| 6789 |
| 6790 refChanged_: function() { |
| 6791 if (!this.iterator_ || this.refContent_ === this.ref_.content) |
| 6792 return; |
| 6793 |
| 6794 this.refContent_ = undefined; |
| 6795 this.iterator_.valueChanged(); |
| 6796 this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue()); |
| 6797 }, |
| 6798 |
| 6799 clear: function() { |
| 6800 this.model_ = undefined; |
| 6801 this.delegate_ = undefined; |
| 6802 if (this.bindings_ && this.bindings_.ref) |
| 6803 this.bindings_.ref.close() |
| 6804 this.refContent_ = undefined; |
| 6805 if (!this.iterator_) |
| 6806 return; |
| 6807 this.iterator_.valueChanged(); |
| 6808 this.iterator_.close() |
| 6809 this.iterator_ = undefined; |
| 6810 }, |
| 6811 |
| 6812 setDelegate_: function(delegate) { |
| 6813 this.delegate_ = delegate; |
| 6814 this.bindingMap_ = undefined; |
| 6815 if (this.iterator_) { |
| 6816 this.iterator_.instancePositionChangedFn_ = undefined; |
| 6817 this.iterator_.instanceModelFn_ = undefined; |
| 6818 } |
| 6819 }, |
| 6820 |
| 6821 newDelegate_: function(bindingDelegate) { |
| 6822 if (!bindingDelegate) |
| 6823 return; |
| 6824 |
| 6825 function delegateFn(name) { |
| 6826 var fn = bindingDelegate && bindingDelegate[name]; |
| 6827 if (typeof fn != 'function') |
| 6828 return; |
| 6829 |
| 6830 return function() { |
| 6831 return fn.apply(bindingDelegate, arguments); |
| 6832 }; |
| 6833 } |
| 6834 |
| 6835 return { |
| 6836 bindingMaps: {}, |
| 6837 raw: bindingDelegate, |
| 6838 prepareBinding: delegateFn('prepareBinding'), |
| 6839 prepareInstanceModel: delegateFn('prepareInstanceModel'), |
| 6840 prepareInstancePositionChanged: |
| 6841 delegateFn('prepareInstancePositionChanged') |
| 6842 }; |
| 6843 }, |
| 6844 |
| 6845 set bindingDelegate(bindingDelegate) { |
| 6846 if (this.delegate_) { |
| 6847 throw Error('Template must be cleared before a new bindingDelegate ' + |
| 6848 'can be assigned'); |
| 6849 } |
| 6850 |
| 6851 this.setDelegate_(this.newDelegate_(bindingDelegate)); |
| 6852 }, |
| 6853 |
| 6854 get ref_() { |
| 6855 var ref = searchRefId(this, this.getAttribute('ref')); |
| 6856 if (!ref) |
| 6857 ref = this.instanceRef_; |
| 6858 |
| 6859 if (!ref) |
| 6860 return this; |
| 6861 |
| 6862 var nextRef = ref.ref_; |
| 6863 return nextRef ? nextRef : ref; |
| 6864 } |
| 6865 }); |
| 6866 |
| 6867 // Returns |
| 6868 // a) undefined if there are no mustaches. |
| 6869 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one
mustache. |
| 6870 function parseMustaches(s, name, node, prepareBindingFn) { |
| 6871 if (!s || !s.length) |
| 6872 return; |
| 6873 |
| 6874 var tokens; |
| 6875 var length = s.length; |
| 6876 var startIndex = 0, lastIndex = 0, endIndex = 0; |
| 6877 var onlyOneTime = true; |
| 6878 while (lastIndex < length) { |
| 6879 var startIndex = s.indexOf('{{', lastIndex); |
| 6880 var oneTimeStart = s.indexOf('[[', lastIndex); |
| 6881 var oneTime = false; |
| 6882 var terminator = '}}'; |
| 6883 |
| 6884 if (oneTimeStart >= 0 && |
| 6885 (startIndex < 0 || oneTimeStart < startIndex)) { |
| 6886 startIndex = oneTimeStart; |
| 6887 oneTime = true; |
| 6888 terminator = ']]'; |
| 6889 } |
| 6890 |
| 6891 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2); |
| 6892 |
| 6893 if (endIndex < 0) { |
| 6894 if (!tokens) |
| 6895 return; |
| 6896 |
| 6897 tokens.push(s.slice(lastIndex)); // TEXT |
| 6898 break; |
| 6899 } |
| 6900 |
| 6901 tokens = tokens || []; |
| 6902 tokens.push(s.slice(lastIndex, startIndex)); // TEXT |
| 6903 var pathString = s.slice(startIndex + 2, endIndex).trim(); |
| 6904 tokens.push(oneTime); // ONE_TIME? |
| 6905 onlyOneTime = onlyOneTime && oneTime; |
| 6906 var delegateFn = prepareBindingFn && |
| 6907 prepareBindingFn(pathString, name, node); |
| 6908 // Don't try to parse the expression if there's a prepareBinding function |
| 6909 if (delegateFn == null) { |
| 6910 tokens.push(Path.get(pathString)); // PATH |
| 6911 } else { |
| 6912 tokens.push(null); |
| 6913 } |
| 6914 tokens.push(delegateFn); // DELEGATE_FN |
| 6915 lastIndex = endIndex + 2; |
| 6916 } |
| 6917 |
| 6918 if (lastIndex === length) |
| 6919 tokens.push(''); // TEXT |
| 6920 |
| 6921 tokens.hasOnePath = tokens.length === 5; |
| 6922 tokens.isSimplePath = tokens.hasOnePath && |
| 6923 tokens[0] == '' && |
| 6924 tokens[4] == ''; |
| 6925 tokens.onlyOneTime = onlyOneTime; |
| 6926 |
| 6927 tokens.combinator = function(values) { |
| 6928 var newValue = tokens[0]; |
| 6929 |
| 6930 for (var i = 1; i < tokens.length; i += 4) { |
| 6931 var value = tokens.hasOnePath ? values : values[(i - 1) / 4]; |
| 6932 if (value !== undefined) |
| 6933 newValue += value; |
| 6934 newValue += tokens[i + 3]; |
| 6935 } |
| 6936 |
| 6937 return newValue; |
| 6938 } |
| 6939 |
| 6940 return tokens; |
| 6941 }; |
| 6942 |
| 6943 function processOneTimeBinding(name, tokens, node, model) { |
| 6944 if (tokens.hasOnePath) { |
| 6945 var delegateFn = tokens[3]; |
| 6946 var value = delegateFn ? delegateFn(model, node, true) : |
| 6947 tokens[2].getValueFrom(model); |
| 6948 return tokens.isSimplePath ? value : tokens.combinator(value); |
| 6949 } |
| 6950 |
| 6951 var values = []; |
| 6952 for (var i = 1; i < tokens.length; i += 4) { |
| 6953 var delegateFn = tokens[i + 2]; |
| 6954 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) : |
| 6955 tokens[i + 1].getValueFrom(model); |
| 6956 } |
| 6957 |
| 6958 return tokens.combinator(values); |
| 6959 } |
| 6960 |
| 6961 function processSinglePathBinding(name, tokens, node, model) { |
| 6962 var delegateFn = tokens[3]; |
| 6963 var observer = delegateFn ? delegateFn(model, node, false) : |
| 6964 new PathObserver(model, tokens[2]); |
| 6965 |
| 6966 return tokens.isSimplePath ? observer : |
| 6967 new ObserverTransform(observer, tokens.combinator); |
| 6968 } |
| 6969 |
| 6970 function processBinding(name, tokens, node, model) { |
| 6971 if (tokens.onlyOneTime) |
| 6972 return processOneTimeBinding(name, tokens, node, model); |
| 6973 |
| 6974 if (tokens.hasOnePath) |
| 6975 return processSinglePathBinding(name, tokens, node, model); |
| 6976 |
| 6977 var observer = new CompoundObserver(); |
| 6978 |
| 6979 for (var i = 1; i < tokens.length; i += 4) { |
| 6980 var oneTime = tokens[i]; |
| 6981 var delegateFn = tokens[i + 2]; |
| 6982 |
| 6983 if (delegateFn) { |
| 6984 var value = delegateFn(model, node, oneTime); |
| 6985 if (oneTime) |
| 6986 observer.addPath(value) |
| 6987 else |
| 6988 observer.addObserver(value); |
| 6989 continue; |
| 6990 } |
| 6991 |
| 6992 var path = tokens[i + 1]; |
| 6993 if (oneTime) |
| 6994 observer.addPath(path.getValueFrom(model)) |
| 6995 else |
| 6996 observer.addPath(model, path); |
| 6997 } |
| 6998 |
| 6999 return new ObserverTransform(observer, tokens.combinator); |
| 7000 } |
| 7001 |
| 7002 function processBindings(node, bindings, model, instanceBindings) { |
| 7003 for (var i = 0; i < bindings.length; i += 2) { |
| 7004 var name = bindings[i] |
| 7005 var tokens = bindings[i + 1]; |
| 7006 var value = processBinding(name, tokens, node, model); |
| 7007 var binding = node.bind(name, value, tokens.onlyOneTime); |
| 7008 if (binding && instanceBindings) |
| 7009 instanceBindings.push(binding); |
| 7010 } |
| 7011 |
| 7012 node.bindFinished(); |
| 7013 if (!bindings.isTemplate) |
| 7014 return; |
| 7015 |
| 7016 node.model_ = model; |
| 7017 var iter = node.processBindingDirectives_(bindings); |
| 7018 if (instanceBindings && iter) |
| 7019 instanceBindings.push(iter); |
| 7020 } |
| 7021 |
| 7022 function parseWithDefault(el, name, prepareBindingFn) { |
| 7023 var v = el.getAttribute(name); |
| 7024 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn); |
| 7025 } |
| 7026 |
| 7027 function parseAttributeBindings(element, prepareBindingFn) { |
| 7028 assert(element); |
| 7029 |
| 7030 var bindings = []; |
| 7031 var ifFound = false; |
| 7032 var bindFound = false; |
| 7033 |
| 7034 for (var i = 0; i < element.attributes.length; i++) { |
| 7035 var attr = element.attributes[i]; |
| 7036 var name = attr.name; |
| 7037 var value = attr.value; |
| 7038 |
| 7039 // Allow bindings expressed in attributes to be prefixed with underbars. |
| 7040 // We do this to allow correct semantics for browsers that don't implement |
| 7041 // <template> where certain attributes might trigger side-effects -- and |
| 7042 // for IE which sanitizes certain attributes, disallowing mustache |
| 7043 // replacements in their text. |
| 7044 while (name[0] === '_') { |
| 7045 name = name.substring(1); |
| 7046 } |
| 7047 |
| 7048 if (isTemplate(element) && |
| 7049 (name === IF || name === BIND || name === REPEAT)) { |
| 7050 continue; |
| 7051 } |
| 7052 |
| 7053 var tokens = parseMustaches(value, name, element, |
| 7054 prepareBindingFn); |
| 7055 if (!tokens) |
| 7056 continue; |
| 7057 |
| 7058 bindings.push(name, tokens); |
| 7059 } |
| 7060 |
| 7061 if (isTemplate(element)) { |
| 7062 bindings.isTemplate = true; |
| 7063 bindings.if = parseWithDefault(element, IF, prepareBindingFn); |
| 7064 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn); |
| 7065 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn); |
| 7066 |
| 7067 if (bindings.if && !bindings.bind && !bindings.repeat) |
| 7068 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn); |
| 7069 } |
| 7070 |
| 7071 return bindings; |
| 7072 } |
| 7073 |
| 7074 function getBindings(node, prepareBindingFn) { |
| 7075 if (node.nodeType === Node.ELEMENT_NODE) |
| 7076 return parseAttributeBindings(node, prepareBindingFn); |
| 7077 |
| 7078 if (node.nodeType === Node.TEXT_NODE) { |
| 7079 var tokens = parseMustaches(node.data, 'textContent', node, |
| 7080 prepareBindingFn); |
| 7081 if (tokens) |
| 7082 return ['textContent', tokens]; |
| 7083 } |
| 7084 |
| 7085 return []; |
| 7086 } |
| 7087 |
| 7088 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model, |
| 7089 delegate, |
| 7090 instanceBindings, |
| 7091 instanceRecord) { |
| 7092 var clone = parent.appendChild(stagingDocument.importNode(node, false)); |
| 7093 |
| 7094 var i = 0; |
| 7095 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 7096 cloneAndBindInstance(child, clone, stagingDocument, |
| 7097 bindings.children[i++], |
| 7098 model, |
| 7099 delegate, |
| 7100 instanceBindings); |
| 7101 } |
| 7102 |
| 7103 if (bindings.isTemplate) { |
| 7104 HTMLTemplateElement.decorate(clone, node); |
| 7105 if (delegate) |
| 7106 clone.setDelegate_(delegate); |
| 7107 } |
| 7108 |
| 7109 processBindings(clone, bindings, model, instanceBindings); |
| 7110 return clone; |
| 7111 } |
| 7112 |
| 7113 function createInstanceBindingMap(node, prepareBindingFn) { |
| 7114 var map = getBindings(node, prepareBindingFn); |
| 7115 map.children = {}; |
| 7116 var index = 0; |
| 7117 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 7118 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn); |
| 7119 } |
| 7120 |
| 7121 return map; |
| 7122 } |
| 7123 |
| 7124 var contentUidCounter = 1; |
| 7125 |
| 7126 // TODO(rafaelw): Setup a MutationObserver on content which clears the id |
| 7127 // so that bindingMaps regenerate when the template.content changes. |
| 7128 function getContentUid(content) { |
| 7129 var id = content.id_; |
| 7130 if (!id) |
| 7131 id = content.id_ = contentUidCounter++; |
| 7132 return id; |
| 7133 } |
| 7134 |
| 7135 // Each delegate is associated with a set of bindingMaps, one for each |
| 7136 // content which may be used by a template. The intent is that each binding |
| 7137 // delegate gets the opportunity to prepare the instance (via the prepare* |
| 7138 // delegate calls) once across all uses. |
| 7139 // TODO(rafaelw): Separate out the parse map from the binding map. In the |
| 7140 // current implementation, if two delegates need a binding map for the same |
| 7141 // content, the second will have to reparse. |
| 7142 function getInstanceBindingMap(content, delegate_) { |
| 7143 var contentId = getContentUid(content); |
| 7144 if (delegate_) { |
| 7145 var map = delegate_.bindingMaps[contentId]; |
| 7146 if (!map) { |
| 7147 map = delegate_.bindingMaps[contentId] = |
| 7148 createInstanceBindingMap(content, delegate_.prepareBinding) || []; |
| 7149 } |
| 7150 return map; |
| 7151 } |
| 7152 |
| 7153 var map = content.bindingMap_; |
| 7154 if (!map) { |
| 7155 map = content.bindingMap_ = |
| 7156 createInstanceBindingMap(content, undefined) || []; |
| 7157 } |
| 7158 return map; |
| 7159 } |
| 7160 |
| 7161 Object.defineProperty(Node.prototype, 'templateInstance', { |
| 7162 get: function() { |
| 7163 var instance = this.templateInstance_; |
| 7164 return instance ? instance : |
| 7165 (this.parentNode ? this.parentNode.templateInstance : undefined); |
| 7166 } |
| 7167 }); |
| 7168 |
| 7169 var emptyInstance = document.createDocumentFragment(); |
| 7170 emptyInstance.bindings_ = []; |
| 7171 emptyInstance.terminator_ = null; |
| 7172 |
| 7173 function TemplateIterator(templateElement) { |
| 7174 this.closed = false; |
| 7175 this.templateElement_ = templateElement; |
| 7176 this.instances = []; |
| 7177 this.deps = undefined; |
| 7178 this.iteratedValue = []; |
| 7179 this.presentValue = undefined; |
| 7180 this.arrayObserver = undefined; |
| 7181 } |
| 7182 |
| 7183 TemplateIterator.prototype = { |
| 7184 closeDeps: function() { |
| 7185 var deps = this.deps; |
| 7186 if (deps) { |
| 7187 if (deps.ifOneTime === false) |
| 7188 deps.ifValue.close(); |
| 7189 if (deps.oneTime === false) |
| 7190 deps.value.close(); |
| 7191 } |
| 7192 }, |
| 7193 |
| 7194 updateDependencies: function(directives, model) { |
| 7195 this.closeDeps(); |
| 7196 |
| 7197 var deps = this.deps = {}; |
| 7198 var template = this.templateElement_; |
| 7199 |
| 7200 var ifValue = true; |
| 7201 if (directives.if) { |
| 7202 deps.hasIf = true; |
| 7203 deps.ifOneTime = directives.if.onlyOneTime; |
| 7204 deps.ifValue = processBinding(IF, directives.if, template, model); |
| 7205 |
| 7206 ifValue = deps.ifValue; |
| 7207 |
| 7208 // oneTime if & predicate is false. nothing else to do. |
| 7209 if (deps.ifOneTime && !ifValue) { |
| 7210 this.valueChanged(); |
| 7211 return; |
| 7212 } |
| 7213 |
| 7214 if (!deps.ifOneTime) |
| 7215 ifValue = ifValue.open(this.updateIfValue, this); |
| 7216 } |
| 7217 |
| 7218 if (directives.repeat) { |
| 7219 deps.repeat = true; |
| 7220 deps.oneTime = directives.repeat.onlyOneTime; |
| 7221 deps.value = processBinding(REPEAT, directives.repeat, template, model); |
| 7222 } else { |
| 7223 deps.repeat = false; |
| 7224 deps.oneTime = directives.bind.onlyOneTime; |
| 7225 deps.value = processBinding(BIND, directives.bind, template, model); |
| 7226 } |
| 7227 |
| 7228 var value = deps.value; |
| 7229 if (!deps.oneTime) |
| 7230 value = value.open(this.updateIteratedValue, this); |
| 7231 |
| 7232 if (!ifValue) { |
| 7233 this.valueChanged(); |
| 7234 return; |
| 7235 } |
| 7236 |
| 7237 this.updateValue(value); |
| 7238 }, |
| 7239 |
| 7240 /** |
| 7241 * Gets the updated value of the bind/repeat. This can potentially call |
| 7242 * user code (if a bindingDelegate is set up) so we try to avoid it if we |
| 7243 * already have the value in hand (from Observer.open). |
| 7244 */ |
| 7245 getUpdatedValue: function() { |
| 7246 var value = this.deps.value; |
| 7247 if (!this.deps.oneTime) |
| 7248 value = value.discardChanges(); |
| 7249 return value; |
| 7250 }, |
| 7251 |
| 7252 updateIfValue: function(ifValue) { |
| 7253 if (!ifValue) { |
| 7254 this.valueChanged(); |
| 7255 return; |
| 7256 } |
| 7257 |
| 7258 this.updateValue(this.getUpdatedValue()); |
| 7259 }, |
| 7260 |
| 7261 updateIteratedValue: function(value) { |
| 7262 if (this.deps.hasIf) { |
| 7263 var ifValue = this.deps.ifValue; |
| 7264 if (!this.deps.ifOneTime) |
| 7265 ifValue = ifValue.discardChanges(); |
| 7266 if (!ifValue) { |
| 7267 this.valueChanged(); |
| 7268 return; |
| 7269 } |
| 7270 } |
| 7271 |
| 7272 this.updateValue(value); |
| 7273 }, |
| 7274 |
| 7275 updateValue: function(value) { |
| 7276 if (!this.deps.repeat) |
| 7277 value = [value]; |
| 7278 var observe = this.deps.repeat && |
| 7279 !this.deps.oneTime && |
| 7280 Array.isArray(value); |
| 7281 this.valueChanged(value, observe); |
| 7282 }, |
| 7283 |
| 7284 valueChanged: function(value, observeValue) { |
| 7285 if (!Array.isArray(value)) |
| 7286 value = []; |
| 7287 |
| 7288 if (value === this.iteratedValue) |
| 7289 return; |
| 7290 |
| 7291 this.unobserve(); |
| 7292 this.presentValue = value; |
| 7293 if (observeValue) { |
| 7294 this.arrayObserver = new ArrayObserver(this.presentValue); |
| 7295 this.arrayObserver.open(this.handleSplices, this); |
| 7296 } |
| 7297 |
| 7298 this.handleSplices(ArrayObserver.calculateSplices(this.presentValue, |
| 7299 this.iteratedValue)); |
| 7300 }, |
| 7301 |
| 7302 getLastInstanceNode: function(index) { |
| 7303 if (index == -1) |
| 7304 return this.templateElement_; |
| 7305 var instance = this.instances[index]; |
| 7306 var terminator = instance.terminator_; |
| 7307 if (!terminator) |
| 7308 return this.getLastInstanceNode(index - 1); |
| 7309 |
| 7310 if (terminator.nodeType !== Node.ELEMENT_NODE || |
| 7311 this.templateElement_ === terminator) { |
| 7312 return terminator; |
| 7313 } |
| 7314 |
| 7315 var subtemplateIterator = terminator.iterator_; |
| 7316 if (!subtemplateIterator) |
| 7317 return terminator; |
| 7318 |
| 7319 return subtemplateIterator.getLastTemplateNode(); |
| 7320 }, |
| 7321 |
| 7322 getLastTemplateNode: function() { |
| 7323 return this.getLastInstanceNode(this.instances.length - 1); |
| 7324 }, |
| 7325 |
| 7326 insertInstanceAt: function(index, fragment) { |
| 7327 var previousInstanceLast = this.getLastInstanceNode(index - 1); |
| 7328 var parent = this.templateElement_.parentNode; |
| 7329 this.instances.splice(index, 0, fragment); |
| 7330 |
| 7331 parent.insertBefore(fragment, previousInstanceLast.nextSibling); |
| 7332 }, |
| 7333 |
| 7334 extractInstanceAt: function(index) { |
| 7335 var previousInstanceLast = this.getLastInstanceNode(index - 1); |
| 7336 var lastNode = this.getLastInstanceNode(index); |
| 7337 var parent = this.templateElement_.parentNode; |
| 7338 var instance = this.instances.splice(index, 1)[0]; |
| 7339 |
| 7340 while (lastNode !== previousInstanceLast) { |
| 7341 var node = previousInstanceLast.nextSibling; |
| 7342 if (node == lastNode) |
| 7343 lastNode = previousInstanceLast; |
| 7344 |
| 7345 instance.appendChild(parent.removeChild(node)); |
| 7346 } |
| 7347 |
| 7348 return instance; |
| 7349 }, |
| 7350 |
| 7351 getDelegateFn: function(fn) { |
| 7352 fn = fn && fn(this.templateElement_); |
| 7353 return typeof fn === 'function' ? fn : null; |
| 7354 }, |
| 7355 |
| 7356 handleSplices: function(splices) { |
| 7357 if (this.closed || !splices.length) |
| 7358 return; |
| 7359 |
| 7360 var template = this.templateElement_; |
| 7361 |
| 7362 if (!template.parentNode) { |
| 7363 this.close(); |
| 7364 return; |
| 7365 } |
| 7366 |
| 7367 ArrayObserver.applySplices(this.iteratedValue, this.presentValue, |
| 7368 splices); |
| 7369 |
| 7370 var delegate = template.delegate_; |
| 7371 if (this.instanceModelFn_ === undefined) { |
| 7372 this.instanceModelFn_ = |
| 7373 this.getDelegateFn(delegate && delegate.prepareInstanceModel); |
| 7374 } |
| 7375 |
| 7376 if (this.instancePositionChangedFn_ === undefined) { |
| 7377 this.instancePositionChangedFn_ = |
| 7378 this.getDelegateFn(delegate && |
| 7379 delegate.prepareInstancePositionChanged); |
| 7380 } |
| 7381 |
| 7382 // Instance Removals |
| 7383 var instanceCache = new Map; |
| 7384 var removeDelta = 0; |
| 7385 for (var i = 0; i < splices.length; i++) { |
| 7386 var splice = splices[i]; |
| 7387 var removed = splice.removed; |
| 7388 for (var j = 0; j < removed.length; j++) { |
| 7389 var model = removed[j]; |
| 7390 var instance = this.extractInstanceAt(splice.index + removeDelta); |
| 7391 if (instance !== emptyInstance) { |
| 7392 instanceCache.set(model, instance); |
| 7393 } |
| 7394 } |
| 7395 |
| 7396 removeDelta -= splice.addedCount; |
| 7397 } |
| 7398 |
| 7399 // Instance Insertions |
| 7400 for (var i = 0; i < splices.length; i++) { |
| 7401 var splice = splices[i]; |
| 7402 var addIndex = splice.index; |
| 7403 for (; addIndex < splice.index + splice.addedCount; addIndex++) { |
| 7404 var model = this.iteratedValue[addIndex]; |
| 7405 var instance = instanceCache.get(model); |
| 7406 if (instance) { |
| 7407 instanceCache.delete(model); |
| 7408 } else { |
| 7409 if (this.instanceModelFn_) { |
| 7410 model = this.instanceModelFn_(model); |
| 7411 } |
| 7412 |
| 7413 if (model === undefined) { |
| 7414 instance = emptyInstance; |
| 7415 } else { |
| 7416 instance = template.createInstance(model, undefined, delegate); |
| 7417 } |
| 7418 } |
| 7419 |
| 7420 this.insertInstanceAt(addIndex, instance); |
| 7421 } |
| 7422 } |
| 7423 |
| 7424 instanceCache.forEach(function(instance) { |
| 7425 this.closeInstanceBindings(instance); |
| 7426 }, this); |
| 7427 |
| 7428 if (this.instancePositionChangedFn_) |
| 7429 this.reportInstancesMoved(splices); |
| 7430 }, |
| 7431 |
| 7432 reportInstanceMoved: function(index) { |
| 7433 var instance = this.instances[index]; |
| 7434 if (instance === emptyInstance) |
| 7435 return; |
| 7436 |
| 7437 this.instancePositionChangedFn_(instance.templateInstance_, index); |
| 7438 }, |
| 7439 |
| 7440 reportInstancesMoved: function(splices) { |
| 7441 var index = 0; |
| 7442 var offset = 0; |
| 7443 for (var i = 0; i < splices.length; i++) { |
| 7444 var splice = splices[i]; |
| 7445 if (offset != 0) { |
| 7446 while (index < splice.index) { |
| 7447 this.reportInstanceMoved(index); |
| 7448 index++; |
| 7449 } |
| 7450 } else { |
| 7451 index = splice.index; |
| 7452 } |
| 7453 |
| 7454 while (index < splice.index + splice.addedCount) { |
| 7455 this.reportInstanceMoved(index); |
| 7456 index++; |
| 7457 } |
| 7458 |
| 7459 offset += splice.addedCount - splice.removed.length; |
| 7460 } |
| 7461 |
| 7462 if (offset == 0) |
| 7463 return; |
| 7464 |
| 7465 var length = this.instances.length; |
| 7466 while (index < length) { |
| 7467 this.reportInstanceMoved(index); |
| 7468 index++; |
| 7469 } |
| 7470 }, |
| 7471 |
| 7472 closeInstanceBindings: function(instance) { |
| 7473 var bindings = instance.bindings_; |
| 7474 for (var i = 0; i < bindings.length; i++) { |
| 7475 bindings[i].close(); |
| 7476 } |
| 7477 }, |
| 7478 |
| 7479 unobserve: function() { |
| 7480 if (!this.arrayObserver) |
| 7481 return; |
| 7482 |
| 7483 this.arrayObserver.close(); |
| 7484 this.arrayObserver = undefined; |
| 7485 }, |
| 7486 |
| 7487 close: function() { |
| 7488 if (this.closed) |
| 7489 return; |
| 7490 this.unobserve(); |
| 7491 for (var i = 0; i < this.instances.length; i++) { |
| 7492 this.closeInstanceBindings(this.instances[i]); |
| 7493 } |
| 7494 |
| 7495 this.instances.length = 0; |
| 7496 this.closeDeps(); |
| 7497 this.templateElement_.iterator_ = undefined; |
| 7498 this.closed = true; |
| 7499 } |
| 7500 }; |
| 7501 |
| 7502 // Polyfill-specific API. |
| 7503 HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom; |
| 7504 })(this); |
| 7505 |
| 7506 (function(scope) { |
| 7507 'use strict'; |
| 7508 |
| 7509 // feature detect for URL constructor |
| 7510 var hasWorkingUrl = false; |
| 7511 if (!scope.forceJURL) { |
| 7512 try { |
| 7513 var u = new URL('b', 'http://a'); |
| 7514 hasWorkingUrl = u.href === 'http://a/b'; |
| 7515 } catch(e) {} |
| 7516 } |
| 7517 |
| 7518 if (hasWorkingUrl) |
| 7519 return; |
| 7520 |
| 7521 var relative = Object.create(null); |
| 7522 relative['ftp'] = 21; |
| 7523 relative['file'] = 0; |
| 7524 relative['gopher'] = 70; |
| 7525 relative['http'] = 80; |
| 7526 relative['https'] = 443; |
| 7527 relative['ws'] = 80; |
| 7528 relative['wss'] = 443; |
| 7529 |
| 7530 var relativePathDotMapping = Object.create(null); |
| 7531 relativePathDotMapping['%2e'] = '.'; |
| 7532 relativePathDotMapping['.%2e'] = '..'; |
| 7533 relativePathDotMapping['%2e.'] = '..'; |
| 7534 relativePathDotMapping['%2e%2e'] = '..'; |
| 7535 |
| 7536 function isRelativeScheme(scheme) { |
| 7537 return relative[scheme] !== undefined; |
| 7538 } |
| 7539 |
| 7540 function invalid() { |
| 7541 clear.call(this); |
| 7542 this._isInvalid = true; |
| 7543 } |
| 7544 |
| 7545 function IDNAToASCII(h) { |
| 7546 if ('' == h) { |
| 7547 invalid.call(this) |
| 7548 } |
| 7549 // XXX |
| 7550 return h.toLowerCase() |
| 7551 } |
| 7552 |
| 7553 function percentEscape(c) { |
| 7554 var unicode = c.charCodeAt(0); |
| 7555 if (unicode > 0x20 && |
| 7556 unicode < 0x7F && |
| 7557 // " # < > ? ` |
| 7558 [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1 |
| 7559 ) { |
| 7560 return c; |
| 7561 } |
| 7562 return encodeURIComponent(c); |
| 7563 } |
| 7564 |
| 7565 function percentEscapeQuery(c) { |
| 7566 // XXX This actually needs to encode c using encoding and then |
| 7567 // convert the bytes one-by-one. |
| 7568 |
| 7569 var unicode = c.charCodeAt(0); |
| 7570 if (unicode > 0x20 && |
| 7571 unicode < 0x7F && |
| 7572 // " # < > ` (do not escape '?') |
| 7573 [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1 |
| 7574 ) { |
| 7575 return c; |
| 7576 } |
| 7577 return encodeURIComponent(c); |
| 7578 } |
| 7579 |
| 7580 var EOF = undefined, |
| 7581 ALPHA = /[a-zA-Z]/, |
| 7582 ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/; |
| 7583 |
| 7584 function parse(input, stateOverride, base) { |
| 7585 function err(message) { |
| 7586 errors.push(message) |
| 7587 } |
| 7588 |
| 7589 var state = stateOverride || 'scheme start', |
| 7590 cursor = 0, |
| 7591 buffer = '', |
| 7592 seenAt = false, |
| 7593 seenBracket = false, |
| 7594 errors = []; |
| 7595 |
| 7596 loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid)
{ |
| 7597 var c = input[cursor]; |
| 7598 switch (state) { |
| 7599 case 'scheme start': |
| 7600 if (c && ALPHA.test(c)) { |
| 7601 buffer += c.toLowerCase(); // ASCII-safe |
| 7602 state = 'scheme'; |
| 7603 } else if (!stateOverride) { |
| 7604 buffer = ''; |
| 7605 state = 'no scheme'; |
| 7606 continue; |
| 7607 } else { |
| 7608 err('Invalid scheme.'); |
| 7609 break loop; |
| 7610 } |
| 7611 break; |
| 7612 |
| 7613 case 'scheme': |
| 7614 if (c && ALPHANUMERIC.test(c)) { |
| 7615 buffer += c.toLowerCase(); // ASCII-safe |
| 7616 } else if (':' == c) { |
| 7617 this._scheme = buffer; |
| 7618 buffer = ''; |
| 7619 if (stateOverride) { |
| 7620 break loop; |
| 7621 } |
| 7622 if (isRelativeScheme(this._scheme)) { |
| 7623 this._isRelative = true; |
| 7624 } |
| 7625 if ('file' == this._scheme) { |
| 7626 state = 'relative'; |
| 7627 } else if (this._isRelative && base && base._scheme == this._scheme)
{ |
| 7628 state = 'relative or authority'; |
| 7629 } else if (this._isRelative) { |
| 7630 state = 'authority first slash'; |
| 7631 } else { |
| 7632 state = 'scheme data'; |
| 7633 } |
| 7634 } else if (!stateOverride) { |
| 7635 buffer = ''; |
| 7636 cursor = 0; |
| 7637 state = 'no scheme'; |
| 7638 continue; |
| 7639 } else if (EOF == c) { |
| 7640 break loop; |
| 7641 } else { |
| 7642 err('Code point not allowed in scheme: ' + c) |
| 7643 break loop; |
| 7644 } |
| 7645 break; |
| 7646 |
| 7647 case 'scheme data': |
| 7648 if ('?' == c) { |
| 7649 query = '?'; |
| 7650 state = 'query'; |
| 7651 } else if ('#' == c) { |
| 7652 this._fragment = '#'; |
| 7653 state = 'fragment'; |
| 7654 } else { |
| 7655 // XXX error handling |
| 7656 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { |
| 7657 this._schemeData += percentEscape(c); |
| 7658 } |
| 7659 } |
| 7660 break; |
| 7661 |
| 7662 case 'no scheme': |
| 7663 if (!base || !(isRelativeScheme(base._scheme))) { |
| 7664 err('Missing scheme.'); |
| 7665 invalid.call(this); |
| 7666 } else { |
| 7667 state = 'relative'; |
| 7668 continue; |
| 7669 } |
| 7670 break; |
| 7671 |
| 7672 case 'relative or authority': |
| 7673 if ('/' == c && '/' == input[cursor+1]) { |
| 7674 state = 'authority ignore slashes'; |
| 7675 } else { |
| 7676 err('Expected /, got: ' + c); |
| 7677 state = 'relative'; |
| 7678 continue |
| 7679 } |
| 7680 break; |
| 7681 |
| 7682 case 'relative': |
| 7683 this._isRelative = true; |
| 7684 if ('file' != this._scheme) |
| 7685 this._scheme = base._scheme; |
| 7686 if (EOF == c) { |
| 7687 this._host = base._host; |
| 7688 this._port = base._port; |
| 7689 this._path = base._path.slice(); |
| 7690 this._query = base._query; |
| 7691 break loop; |
| 7692 } else if ('/' == c || '\\' == c) { |
| 7693 if ('\\' == c) |
| 7694 err('\\ is an invalid code point.'); |
| 7695 state = 'relative slash'; |
| 7696 } else if ('?' == c) { |
| 7697 this._host = base._host; |
| 7698 this._port = base._port; |
| 7699 this._path = base._path.slice(); |
| 7700 this._query = '?'; |
| 7701 state = 'query'; |
| 7702 } else if ('#' == c) { |
| 7703 this._host = base._host; |
| 7704 this._port = base._port; |
| 7705 this._path = base._path.slice(); |
| 7706 this._query = base._query; |
| 7707 this._fragment = '#'; |
| 7708 state = 'fragment'; |
| 7709 } else { |
| 7710 var nextC = input[cursor+1] |
| 7711 var nextNextC = input[cursor+2] |
| 7712 if ( |
| 7713 'file' != this._scheme || !ALPHA.test(c) || |
| 7714 (nextC != ':' && nextC != '|') || |
| 7715 (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?'
!= nextNextC && '#' != nextNextC)) { |
| 7716 this._host = base._host; |
| 7717 this._port = base._port; |
| 7718 this._path = base._path.slice(); |
| 7719 this._path.pop(); |
| 7720 } |
| 7721 state = 'relative path'; |
| 7722 continue; |
| 7723 } |
| 7724 break; |
| 7725 |
| 7726 case 'relative slash': |
| 7727 if ('/' == c || '\\' == c) { |
| 7728 if ('\\' == c) { |
| 7729 err('\\ is an invalid code point.'); |
| 7730 } |
| 7731 if ('file' == this._scheme) { |
| 7732 state = 'file host'; |
| 7733 } else { |
| 7734 state = 'authority ignore slashes'; |
| 7735 } |
| 7736 } else { |
| 7737 if ('file' != this._scheme) { |
| 7738 this._host = base._host; |
| 7739 this._port = base._port; |
| 7740 } |
| 7741 state = 'relative path'; |
| 7742 continue; |
| 7743 } |
| 7744 break; |
| 7745 |
| 7746 case 'authority first slash': |
| 7747 if ('/' == c) { |
| 7748 state = 'authority second slash'; |
| 7749 } else { |
| 7750 err("Expected '/', got: " + c); |
| 7751 state = 'authority ignore slashes'; |
| 7752 continue; |
| 7753 } |
| 7754 break; |
| 7755 |
| 7756 case 'authority second slash': |
| 7757 state = 'authority ignore slashes'; |
| 7758 if ('/' != c) { |
| 7759 err("Expected '/', got: " + c); |
| 7760 continue; |
| 7761 } |
| 7762 break; |
| 7763 |
| 7764 case 'authority ignore slashes': |
| 7765 if ('/' != c && '\\' != c) { |
| 7766 state = 'authority'; |
| 7767 continue; |
| 7768 } else { |
| 7769 err('Expected authority, got: ' + c); |
| 7770 } |
| 7771 break; |
| 7772 |
| 7773 case 'authority': |
| 7774 if ('@' == c) { |
| 7775 if (seenAt) { |
| 7776 err('@ already seen.'); |
| 7777 buffer += '%40'; |
| 7778 } |
| 7779 seenAt = true; |
| 7780 for (var i = 0; i < buffer.length; i++) { |
| 7781 var cp = buffer[i]; |
| 7782 if ('\t' == cp || '\n' == cp || '\r' == cp) { |
| 7783 err('Invalid whitespace in authority.'); |
| 7784 continue; |
| 7785 } |
| 7786 // XXX check URL code points |
| 7787 if (':' == cp && null === this._password) { |
| 7788 this._password = ''; |
| 7789 continue; |
| 7790 } |
| 7791 var tempC = percentEscape(cp); |
| 7792 (null !== this._password) ? this._password += tempC : this._userna
me += tempC; |
| 7793 } |
| 7794 buffer = ''; |
| 7795 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ |
| 7796 cursor -= buffer.length; |
| 7797 buffer = ''; |
| 7798 state = 'host'; |
| 7799 continue; |
| 7800 } else { |
| 7801 buffer += c; |
| 7802 } |
| 7803 break; |
| 7804 |
| 7805 case 'file host': |
| 7806 if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { |
| 7807 if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':'
|| buffer[1] == '|')) { |
| 7808 state = 'relative path'; |
| 7809 } else if (buffer.length == 0) { |
| 7810 state = 'relative path start'; |
| 7811 } else { |
| 7812 this._host = IDNAToASCII.call(this, buffer); |
| 7813 buffer = ''; |
| 7814 state = 'relative path start'; |
| 7815 } |
| 7816 continue; |
| 7817 } else if ('\t' == c || '\n' == c || '\r' == c) { |
| 7818 err('Invalid whitespace in file host.'); |
| 7819 } else { |
| 7820 buffer += c; |
| 7821 } |
| 7822 break; |
| 7823 |
| 7824 case 'host': |
| 7825 case 'hostname': |
| 7826 if (':' == c && !seenBracket) { |
| 7827 // XXX host parsing |
| 7828 this._host = IDNAToASCII.call(this, buffer); |
| 7829 buffer = ''; |
| 7830 state = 'port'; |
| 7831 if ('hostname' == stateOverride) { |
| 7832 break loop; |
| 7833 } |
| 7834 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ |
| 7835 this._host = IDNAToASCII.call(this, buffer); |
| 7836 buffer = ''; |
| 7837 state = 'relative path start'; |
| 7838 if (stateOverride) { |
| 7839 break loop; |
| 7840 } |
| 7841 continue; |
| 7842 } else if ('\t' != c && '\n' != c && '\r' != c) { |
| 7843 if ('[' == c) { |
| 7844 seenBracket = true; |
| 7845 } else if (']' == c) { |
| 7846 seenBracket = false; |
| 7847 } |
| 7848 buffer += c; |
| 7849 } else { |
| 7850 err('Invalid code point in host/hostname: ' + c); |
| 7851 } |
| 7852 break; |
| 7853 |
| 7854 case 'port': |
| 7855 if (/[0-9]/.test(c)) { |
| 7856 buffer += c; |
| 7857 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c |
| stateOverride) { |
| 7858 if ('' != buffer) { |
| 7859 var temp = parseInt(buffer, 10); |
| 7860 if (temp != relative[this._scheme]) { |
| 7861 this._port = temp + ''; |
| 7862 } |
| 7863 buffer = ''; |
| 7864 } |
| 7865 if (stateOverride) { |
| 7866 break loop; |
| 7867 } |
| 7868 state = 'relative path start'; |
| 7869 continue; |
| 7870 } else if ('\t' == c || '\n' == c || '\r' == c) { |
| 7871 err('Invalid code point in port: ' + c); |
| 7872 } else { |
| 7873 invalid.call(this); |
| 7874 } |
| 7875 break; |
| 7876 |
| 7877 case 'relative path start': |
| 7878 if ('\\' == c) |
| 7879 err("'\\' not allowed in path."); |
| 7880 state = 'relative path'; |
| 7881 if ('/' != c && '\\' != c) { |
| 7882 continue; |
| 7883 } |
| 7884 break; |
| 7885 |
| 7886 case 'relative path': |
| 7887 if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c
|| '#' == c))) { |
| 7888 if ('\\' == c) { |
| 7889 err('\\ not allowed in relative path.'); |
| 7890 } |
| 7891 var tmp; |
| 7892 if (tmp = relativePathDotMapping[buffer.toLowerCase()]) { |
| 7893 buffer = tmp; |
| 7894 } |
| 7895 if ('..' == buffer) { |
| 7896 this._path.pop(); |
| 7897 if ('/' != c && '\\' != c) { |
| 7898 this._path.push(''); |
| 7899 } |
| 7900 } else if ('.' == buffer && '/' != c && '\\' != c) { |
| 7901 this._path.push(''); |
| 7902 } else if ('.' != buffer) { |
| 7903 if ('file' == this._scheme && this._path.length == 0 && buffer.len
gth == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') { |
| 7904 buffer = buffer[0] + ':'; |
| 7905 } |
| 7906 this._path.push(buffer); |
| 7907 } |
| 7908 buffer = ''; |
| 7909 if ('?' == c) { |
| 7910 this._query = '?'; |
| 7911 state = 'query'; |
| 7912 } else if ('#' == c) { |
| 7913 this._fragment = '#'; |
| 7914 state = 'fragment'; |
| 7915 } |
| 7916 } else if ('\t' != c && '\n' != c && '\r' != c) { |
| 7917 buffer += percentEscape(c); |
| 7918 } |
| 7919 break; |
| 7920 |
| 7921 case 'query': |
| 7922 if (!stateOverride && '#' == c) { |
| 7923 this._fragment = '#'; |
| 7924 state = 'fragment'; |
| 7925 } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { |
| 7926 this._query += percentEscapeQuery(c); |
| 7927 } |
| 7928 break; |
| 7929 |
| 7930 case 'fragment': |
| 7931 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { |
| 7932 this._fragment += c; |
| 7933 } |
| 7934 break; |
| 7935 } |
| 7936 |
| 7937 cursor++; |
| 7938 } |
| 7939 } |
| 7940 |
| 7941 function clear() { |
| 7942 this._scheme = ''; |
| 7943 this._schemeData = ''; |
| 7944 this._username = ''; |
| 7945 this._password = null; |
| 7946 this._host = ''; |
| 7947 this._port = ''; |
| 7948 this._path = []; |
| 7949 this._query = ''; |
| 7950 this._fragment = ''; |
| 7951 this._isInvalid = false; |
| 7952 this._isRelative = false; |
| 7953 } |
| 7954 |
| 7955 // Does not process domain names or IP addresses. |
| 7956 // Does not handle encoding for the query parameter. |
| 7957 function jURL(url, base /* , encoding */) { |
| 7958 if (base !== undefined && !(base instanceof jURL)) |
| 7959 base = new jURL(String(base)); |
| 7960 |
| 7961 this._url = url; |
| 7962 clear.call(this); |
| 7963 |
| 7964 var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ''); |
| 7965 // encoding = encoding || 'utf-8' |
| 7966 |
| 7967 parse.call(this, input, null, base); |
| 7968 } |
| 7969 |
| 7970 jURL.prototype = { |
| 7971 get href() { |
| 7972 if (this._isInvalid) |
| 7973 return this._url; |
| 7974 |
| 7975 var authority = ''; |
| 7976 if ('' != this._username || null != this._password) { |
| 7977 authority = this._username + |
| 7978 (null != this._password ? ':' + this._password : '') + '@'; |
| 7979 } |
| 7980 |
| 7981 return this.protocol + |
| 7982 (this._isRelative ? '//' + authority + this.host : '') + |
| 7983 this.pathname + this._query + this._fragment; |
| 7984 }, |
| 7985 set href(href) { |
| 7986 clear.call(this); |
| 7987 parse.call(this, href); |
| 7988 }, |
| 7989 |
| 7990 get protocol() { |
| 7991 return this._scheme + ':'; |
| 7992 }, |
| 7993 set protocol(protocol) { |
| 7994 if (this._isInvalid) |
| 7995 return; |
| 7996 parse.call(this, protocol + ':', 'scheme start'); |
| 7997 }, |
| 7998 |
| 7999 get host() { |
| 8000 return this._isInvalid ? '' : this._port ? |
| 8001 this._host + ':' + this._port : this._host; |
| 8002 }, |
| 8003 set host(host) { |
| 8004 if (this._isInvalid || !this._isRelative) |
| 8005 return; |
| 8006 parse.call(this, host, 'host'); |
| 8007 }, |
| 8008 |
| 8009 get hostname() { |
| 8010 return this._host; |
| 8011 }, |
| 8012 set hostname(hostname) { |
| 8013 if (this._isInvalid || !this._isRelative) |
| 8014 return; |
| 8015 parse.call(this, hostname, 'hostname'); |
| 8016 }, |
| 8017 |
| 8018 get port() { |
| 8019 return this._port; |
| 8020 }, |
| 8021 set port(port) { |
| 8022 if (this._isInvalid || !this._isRelative) |
| 8023 return; |
| 8024 parse.call(this, port, 'port'); |
| 8025 }, |
| 8026 |
| 8027 get pathname() { |
| 8028 return this._isInvalid ? '' : this._isRelative ? |
| 8029 '/' + this._path.join('/') : this._schemeData; |
| 8030 }, |
| 8031 set pathname(pathname) { |
| 8032 if (this._isInvalid || !this._isRelative) |
| 8033 return; |
| 8034 this._path = []; |
| 8035 parse.call(this, pathname, 'relative path start'); |
| 8036 }, |
| 8037 |
| 8038 get search() { |
| 8039 return this._isInvalid || !this._query || '?' == this._query ? |
| 8040 '' : this._query; |
| 8041 }, |
| 8042 set search(search) { |
| 8043 if (this._isInvalid || !this._isRelative) |
| 8044 return; |
| 8045 this._query = '?'; |
| 8046 if ('?' == search[0]) |
| 8047 search = search.slice(1); |
| 8048 parse.call(this, search, 'query'); |
| 8049 }, |
| 8050 |
| 8051 get hash() { |
| 8052 return this._isInvalid || !this._fragment || '#' == this._fragment ? |
| 8053 '' : this._fragment; |
| 8054 }, |
| 8055 set hash(hash) { |
| 8056 if (this._isInvalid) |
| 8057 return; |
| 8058 this._fragment = '#'; |
| 8059 if ('#' == hash[0]) |
| 8060 hash = hash.slice(1); |
| 8061 parse.call(this, hash, 'fragment'); |
| 8062 }, |
| 8063 |
| 8064 get origin() { |
| 8065 var host; |
| 8066 if (this._isInvalid || !this._scheme) { |
| 8067 return ''; |
| 8068 } |
| 8069 // javascript: Gecko returns String(""), WebKit/Blink String("null") |
| 8070 // Gecko throws error for "data://" |
| 8071 // data: Gecko returns "", Blink returns "data://", WebKit returns "null" |
| 8072 // Gecko returns String("") for file: mailto: |
| 8073 // WebKit/Blink returns String("SCHEME://") for file: mailto: |
| 8074 switch (this._scheme) { |
| 8075 case 'data': |
| 8076 case 'file': |
| 8077 case 'javascript': |
| 8078 case 'mailto': |
| 8079 return 'null'; |
| 8080 } |
| 8081 host = this.host; |
| 8082 if (!host) { |
| 8083 return ''; |
| 8084 } |
| 8085 return this._scheme + '://' + host; |
| 8086 } |
| 8087 }; |
| 8088 |
| 8089 // Copy over the static methods |
| 8090 var OriginalURL = scope.URL; |
| 8091 if (OriginalURL) { |
| 8092 jURL.createObjectURL = function(blob) { |
| 8093 // IE extension allows a second optional options argument. |
| 8094 // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx |
| 8095 return OriginalURL.createObjectURL.apply(OriginalURL, arguments); |
| 8096 }; |
| 8097 jURL.revokeObjectURL = function(url) { |
| 8098 OriginalURL.revokeObjectURL(url); |
| 8099 }; |
| 8100 } |
| 8101 |
| 8102 scope.URL = jURL; |
| 8103 |
| 8104 })(this); |
| 8105 |
| 8106 (function(scope) { |
| 8107 |
| 8108 var iterations = 0; |
| 8109 var callbacks = []; |
| 8110 var twiddle = document.createTextNode(''); |
| 8111 |
| 8112 function endOfMicrotask(callback) { |
| 8113 twiddle.textContent = iterations++; |
| 8114 callbacks.push(callback); |
| 8115 } |
| 8116 |
| 8117 function atEndOfMicrotask() { |
| 8118 while (callbacks.length) { |
| 8119 callbacks.shift()(); |
| 8120 } |
| 8121 } |
| 8122 |
| 8123 new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask) |
| 8124 .observe(twiddle, {characterData: true}) |
| 8125 ; |
| 8126 |
| 8127 // exports |
| 8128 scope.endOfMicrotask = endOfMicrotask; |
| 8129 // bc |
| 8130 Platform.endOfMicrotask = endOfMicrotask; |
| 8131 |
| 8132 })(Polymer); |
| 8133 |
| 8134 |
| 8135 (function(scope) { |
| 8136 |
| 8137 /** |
| 8138 * @class Polymer |
| 8139 */ |
| 8140 |
| 8141 // imports |
| 8142 var endOfMicrotask = scope.endOfMicrotask; |
| 8143 |
| 8144 // logging |
| 8145 var log = window.WebComponents ? WebComponents.flags.log : {}; |
| 8146 |
| 8147 // inject style sheet |
| 8148 var style = document.createElement('style'); |
| 8149 style.textContent = 'template {display: none !important;} /* injected by platfor
m.js */'; |
| 8150 var head = document.querySelector('head'); |
| 8151 head.insertBefore(style, head.firstChild); |
| 8152 |
| 8153 |
| 8154 /** |
| 8155 * Force any pending data changes to be observed before |
| 8156 * the next task. Data changes are processed asynchronously but are guaranteed |
| 8157 * to be processed, for example, before painting. This method should rarely be |
| 8158 * needed. It does nothing when Object.observe is available; |
| 8159 * when Object.observe is not available, Polymer automatically flushes data |
| 8160 * changes approximately every 1/10 second. |
| 8161 * Therefore, `flush` should only be used when a data mutation should be |
| 8162 * observed sooner than this. |
| 8163 * |
| 8164 * @method flush |
| 8165 */ |
| 8166 // flush (with logging) |
| 8167 var flushing; |
| 8168 function flush() { |
| 8169 if (!flushing) { |
| 8170 flushing = true; |
| 8171 endOfMicrotask(function() { |
| 8172 flushing = false; |
| 8173 log.data && console.group('flush'); |
| 8174 Platform.performMicrotaskCheckpoint(); |
| 8175 log.data && console.groupEnd(); |
| 8176 }); |
| 8177 } |
| 8178 }; |
| 8179 |
| 8180 // polling dirty checker |
| 8181 // flush periodically if platform does not have object observe. |
| 8182 if (!Observer.hasObjectObserve) { |
| 8183 var FLUSH_POLL_INTERVAL = 125; |
| 8184 window.addEventListener('WebComponentsReady', function() { |
| 8185 flush(); |
| 8186 // watch document visiblity to toggle dirty-checking |
| 8187 var visibilityHandler = function() { |
| 8188 // only flush if the page is visibile |
| 8189 if (document.visibilityState === 'hidden') { |
| 8190 if (scope.flushPoll) { |
| 8191 clearInterval(scope.flushPoll); |
| 8192 } |
| 8193 } else { |
| 8194 scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL); |
| 8195 } |
| 8196 }; |
| 8197 if (typeof document.visibilityState === 'string') { |
| 8198 document.addEventListener('visibilitychange', visibilityHandler); |
| 8199 } |
| 8200 visibilityHandler(); |
| 8201 }); |
| 8202 } else { |
| 8203 // make flush a no-op when we have Object.observe |
| 8204 flush = function() {}; |
| 8205 } |
| 8206 |
| 8207 if (window.CustomElements && !CustomElements.useNative) { |
| 8208 var originalImportNode = Document.prototype.importNode; |
| 8209 Document.prototype.importNode = function(node, deep) { |
| 8210 var imported = originalImportNode.call(this, node, deep); |
| 8211 CustomElements.upgradeAll(imported); |
| 8212 return imported; |
| 8213 }; |
| 8214 } |
| 8215 |
| 8216 // exports |
| 8217 scope.flush = flush; |
| 8218 // bc |
| 8219 Platform.flush = flush; |
| 8220 |
| 8221 })(window.Polymer); |
| 8222 |
| 8223 |
| 8224 (function(scope) { |
| 8225 |
| 8226 var urlResolver = { |
| 8227 resolveDom: function(root, url) { |
| 8228 url = url || baseUrl(root); |
| 8229 this.resolveAttributes(root, url); |
| 8230 this.resolveStyles(root, url); |
| 8231 // handle template.content |
| 8232 var templates = root.querySelectorAll('template'); |
| 8233 if (templates) { |
| 8234 for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i+
+) { |
| 8235 if (t.content) { |
| 8236 this.resolveDom(t.content, url); |
| 8237 } |
| 8238 } |
| 8239 } |
| 8240 }, |
| 8241 resolveTemplate: function(template) { |
| 8242 this.resolveDom(template.content, baseUrl(template)); |
| 8243 }, |
| 8244 resolveStyles: function(root, url) { |
| 8245 var styles = root.querySelectorAll('style'); |
| 8246 if (styles) { |
| 8247 for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) { |
| 8248 this.resolveStyle(s, url); |
| 8249 } |
| 8250 } |
| 8251 }, |
| 8252 resolveStyle: function(style, url) { |
| 8253 url = url || baseUrl(style); |
| 8254 style.textContent = this.resolveCssText(style.textContent, url); |
| 8255 }, |
| 8256 resolveCssText: function(cssText, baseUrl, keepAbsolute) { |
| 8257 cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEX
P); |
| 8258 return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEX
P); |
| 8259 }, |
| 8260 resolveAttributes: function(root, url) { |
| 8261 if (root.hasAttributes && root.hasAttributes()) { |
| 8262 this.resolveElementAttributes(root, url); |
| 8263 } |
| 8264 // search for attributes that host urls |
| 8265 var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR); |
| 8266 if (nodes) { |
| 8267 for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) { |
| 8268 this.resolveElementAttributes(n, url); |
| 8269 } |
| 8270 } |
| 8271 }, |
| 8272 resolveElementAttributes: function(node, url) { |
| 8273 url = url || baseUrl(node); |
| 8274 URL_ATTRS.forEach(function(v) { |
| 8275 var attr = node.attributes[v]; |
| 8276 var value = attr && attr.value; |
| 8277 var replacement; |
| 8278 if (value && value.search(URL_TEMPLATE_SEARCH) < 0) { |
| 8279 if (v === 'style') { |
| 8280 replacement = replaceUrlsInCssText(value, url, false, CSS_URL_REGEXP); |
| 8281 } else { |
| 8282 replacement = resolveRelativeUrl(url, value); |
| 8283 } |
| 8284 attr.value = replacement; |
| 8285 } |
| 8286 }); |
| 8287 } |
| 8288 }; |
| 8289 |
| 8290 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; |
| 8291 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; |
| 8292 var URL_ATTRS = ['href', 'src', 'action', 'style', 'url']; |
| 8293 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; |
| 8294 var URL_TEMPLATE_SEARCH = '{{.*}}'; |
| 8295 var URL_HASH = '#'; |
| 8296 |
| 8297 function baseUrl(node) { |
| 8298 var u = new URL(node.ownerDocument.baseURI); |
| 8299 u.search = ''; |
| 8300 u.hash = ''; |
| 8301 return u; |
| 8302 } |
| 8303 |
| 8304 function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) { |
| 8305 return cssText.replace(regexp, function(m, pre, url, post) { |
| 8306 var urlPath = url.replace(/["']/g, ''); |
| 8307 urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute); |
| 8308 return pre + '\'' + urlPath + '\'' + post; |
| 8309 }); |
| 8310 } |
| 8311 |
| 8312 function resolveRelativeUrl(baseUrl, url, keepAbsolute) { |
| 8313 // do not resolve '/' absolute urls |
| 8314 if (url && url[0] === '/') { |
| 8315 return url; |
| 8316 } |
| 8317 var u = new URL(url, baseUrl); |
| 8318 return keepAbsolute ? u.href : makeDocumentRelPath(u.href); |
| 8319 } |
| 8320 |
| 8321 function makeDocumentRelPath(url) { |
| 8322 var root = baseUrl(document.documentElement); |
| 8323 var u = new URL(url, root); |
| 8324 if (u.host === root.host && u.port === root.port && |
| 8325 u.protocol === root.protocol) { |
| 8326 return makeRelPath(root, u); |
| 8327 } else { |
| 8328 return url; |
| 8329 } |
| 8330 } |
| 8331 |
| 8332 // make a relative path from source to target |
| 8333 function makeRelPath(sourceUrl, targetUrl) { |
| 8334 var source = sourceUrl.pathname; |
| 8335 var target = targetUrl.pathname; |
| 8336 var s = source.split('/'); |
| 8337 var t = target.split('/'); |
| 8338 while (s.length && s[0] === t[0]){ |
| 8339 s.shift(); |
| 8340 t.shift(); |
| 8341 } |
| 8342 for (var i = 0, l = s.length - 1; i < l; i++) { |
| 8343 t.unshift('..'); |
| 8344 } |
| 8345 // empty '#' is discarded but we need to preserve it. |
| 8346 var hash = (targetUrl.href.slice(-1) === URL_HASH) ? URL_HASH : targetUrl.hash
; |
| 8347 return t.join('/') + targetUrl.search + hash; |
| 8348 } |
| 8349 |
| 8350 // exports |
| 8351 scope.urlResolver = urlResolver; |
| 8352 |
| 8353 })(Polymer); |
| 8354 |
| 8355 (function(scope) { |
| 8356 var endOfMicrotask = Polymer.endOfMicrotask; |
| 8357 |
| 8358 // Generic url loader |
| 8359 function Loader(regex) { |
| 8360 this.cache = Object.create(null); |
| 8361 this.map = Object.create(null); |
| 8362 this.requests = 0; |
| 8363 this.regex = regex; |
| 8364 } |
| 8365 Loader.prototype = { |
| 8366 |
| 8367 // TODO(dfreedm): there may be a better factoring here |
| 8368 // extract absolute urls from the text (full of relative urls) |
| 8369 extractUrls: function(text, base) { |
| 8370 var matches = []; |
| 8371 var matched, u; |
| 8372 while ((matched = this.regex.exec(text))) { |
| 8373 u = new URL(matched[1], base); |
| 8374 matches.push({matched: matched[0], url: u.href}); |
| 8375 } |
| 8376 return matches; |
| 8377 }, |
| 8378 // take a text blob, a root url, and a callback and load all the urls found
within the text |
| 8379 // returns a map of absolute url to text |
| 8380 process: function(text, root, callback) { |
| 8381 var matches = this.extractUrls(text, root); |
| 8382 |
| 8383 // every call to process returns all the text this loader has ever receive
d |
| 8384 var done = callback.bind(null, this.map); |
| 8385 this.fetch(matches, done); |
| 8386 }, |
| 8387 // build a mapping of url -> text from matches |
| 8388 fetch: function(matches, callback) { |
| 8389 var inflight = matches.length; |
| 8390 |
| 8391 // return early if there is no fetching to be done |
| 8392 if (!inflight) { |
| 8393 return callback(); |
| 8394 } |
| 8395 |
| 8396 // wait for all subrequests to return |
| 8397 var done = function() { |
| 8398 if (--inflight === 0) { |
| 8399 callback(); |
| 8400 } |
| 8401 }; |
| 8402 |
| 8403 // start fetching all subrequests |
| 8404 var m, req, url; |
| 8405 for (var i = 0; i < inflight; i++) { |
| 8406 m = matches[i]; |
| 8407 url = m.url; |
| 8408 req = this.cache[url]; |
| 8409 // if this url has already been requested, skip requesting it again |
| 8410 if (!req) { |
| 8411 req = this.xhr(url); |
| 8412 req.match = m; |
| 8413 this.cache[url] = req; |
| 8414 } |
| 8415 // wait for the request to process its subrequests |
| 8416 req.wait(done); |
| 8417 } |
| 8418 }, |
| 8419 handleXhr: function(request) { |
| 8420 var match = request.match; |
| 8421 var url = match.url; |
| 8422 |
| 8423 // handle errors with an empty string |
| 8424 var response = request.response || request.responseText || ''; |
| 8425 this.map[url] = response; |
| 8426 this.fetch(this.extractUrls(response, url), request.resolve); |
| 8427 }, |
| 8428 xhr: function(url) { |
| 8429 this.requests++; |
| 8430 var request = new XMLHttpRequest(); |
| 8431 request.open('GET', url, true); |
| 8432 request.send(); |
| 8433 request.onerror = request.onload = this.handleXhr.bind(this, request); |
| 8434 |
| 8435 // queue of tasks to run after XHR returns |
| 8436 request.pending = []; |
| 8437 request.resolve = function() { |
| 8438 var pending = request.pending; |
| 8439 for(var i = 0; i < pending.length; i++) { |
| 8440 pending[i](); |
| 8441 } |
| 8442 request.pending = null; |
| 8443 }; |
| 8444 |
| 8445 // if we have already resolved, pending is null, async call the callback |
| 8446 request.wait = function(fn) { |
| 8447 if (request.pending) { |
| 8448 request.pending.push(fn); |
| 8449 } else { |
| 8450 endOfMicrotask(fn); |
| 8451 } |
| 8452 }; |
| 8453 |
| 8454 return request; |
| 8455 } |
| 8456 }; |
| 8457 |
| 8458 scope.Loader = Loader; |
| 8459 })(Polymer); |
| 8460 |
| 8461 (function(scope) { |
| 8462 |
| 8463 var urlResolver = scope.urlResolver; |
| 8464 var Loader = scope.Loader; |
| 8465 |
| 8466 function StyleResolver() { |
| 8467 this.loader = new Loader(this.regex); |
| 8468 } |
| 8469 StyleResolver.prototype = { |
| 8470 regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g, |
| 8471 // Recursively replace @imports with the text at that url |
| 8472 resolve: function(text, url, callback) { |
| 8473 var done = function(map) { |
| 8474 callback(this.flatten(text, url, map)); |
| 8475 }.bind(this); |
| 8476 this.loader.process(text, url, done); |
| 8477 }, |
| 8478 // resolve the textContent of a style node |
| 8479 resolveNode: function(style, url, callback) { |
| 8480 var text = style.textContent; |
| 8481 var done = function(text) { |
| 8482 style.textContent = text; |
| 8483 callback(style); |
| 8484 }; |
| 8485 this.resolve(text, url, done); |
| 8486 }, |
| 8487 // flatten all the @imports to text |
| 8488 flatten: function(text, base, map) { |
| 8489 var matches = this.loader.extractUrls(text, base); |
| 8490 var match, url, intermediate; |
| 8491 for (var i = 0; i < matches.length; i++) { |
| 8492 match = matches[i]; |
| 8493 url = match.url; |
| 8494 // resolve any css text to be relative to the importer, keep absolute url |
| 8495 intermediate = urlResolver.resolveCssText(map[url], url, true); |
| 8496 // flatten intermediate @imports |
| 8497 intermediate = this.flatten(intermediate, base, map); |
| 8498 text = text.replace(match.matched, intermediate); |
| 8499 } |
| 8500 return text; |
| 8501 }, |
| 8502 loadStyles: function(styles, base, callback) { |
| 8503 var loaded=0, l = styles.length; |
| 8504 // called in the context of the style |
| 8505 function loadedStyle(style) { |
| 8506 loaded++; |
| 8507 if (loaded === l && callback) { |
| 8508 callback(); |
| 8509 } |
| 8510 } |
| 8511 for (var i=0, s; (i<l) && (s=styles[i]); i++) { |
| 8512 this.resolveNode(s, base, loadedStyle); |
| 8513 } |
| 8514 } |
| 8515 }; |
| 8516 |
| 8517 var styleResolver = new StyleResolver(); |
| 8518 |
| 8519 // exports |
| 8520 scope.styleResolver = styleResolver; |
| 8521 |
| 8522 })(Polymer); |
| 8523 |
| 8524 (function(scope) { |
| 8525 |
| 8526 // copy own properties from 'api' to 'prototype, with name hinting for 'super' |
| 8527 function extend(prototype, api) { |
| 8528 if (prototype && api) { |
| 8529 // use only own properties of 'api' |
| 8530 Object.getOwnPropertyNames(api).forEach(function(n) { |
| 8531 // acquire property descriptor |
| 8532 var pd = Object.getOwnPropertyDescriptor(api, n); |
| 8533 if (pd) { |
| 8534 // clone property via descriptor |
| 8535 Object.defineProperty(prototype, n, pd); |
| 8536 // cache name-of-method for 'super' engine |
| 8537 if (typeof pd.value == 'function') { |
| 8538 // hint the 'super' engine |
| 8539 pd.value.nom = n; |
| 8540 } |
| 8541 } |
| 8542 }); |
| 8543 } |
| 8544 return prototype; |
| 8545 } |
| 8546 |
| 8547 |
| 8548 // mixin |
| 8549 |
| 8550 // copy all properties from inProps (et al) to inObj |
| 8551 function mixin(inObj/*, inProps, inMoreProps, ...*/) { |
| 8552 var obj = inObj || {}; |
| 8553 for (var i = 1; i < arguments.length; i++) { |
| 8554 var p = arguments[i]; |
| 8555 try { |
| 8556 for (var n in p) { |
| 8557 copyProperty(n, p, obj); |
| 8558 } |
| 8559 } catch(x) { |
| 8560 } |
| 8561 } |
| 8562 return obj; |
| 8563 } |
| 8564 |
| 8565 // copy property inName from inSource object to inTarget object |
| 8566 function copyProperty(inName, inSource, inTarget) { |
| 8567 var pd = getPropertyDescriptor(inSource, inName); |
| 8568 Object.defineProperty(inTarget, inName, pd); |
| 8569 } |
| 8570 |
| 8571 // get property descriptor for inName on inObject, even if |
| 8572 // inName exists on some link in inObject's prototype chain |
| 8573 function getPropertyDescriptor(inObject, inName) { |
| 8574 if (inObject) { |
| 8575 var pd = Object.getOwnPropertyDescriptor(inObject, inName); |
| 8576 return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName
); |
| 8577 } |
| 8578 } |
| 8579 |
| 8580 // exports |
| 8581 |
| 8582 scope.extend = extend; |
| 8583 scope.mixin = mixin; |
| 8584 |
| 8585 // for bc |
| 8586 Platform.mixin = mixin; |
| 8587 |
| 8588 })(Polymer); |
| 8589 |
| 8590 (function(scope) { |
| 8591 |
| 8592 // usage |
| 8593 |
| 8594 // invoke cb.call(this) in 100ms, unless the job is re-registered, |
| 8595 // which resets the timer |
| 8596 // |
| 8597 // this.myJob = this.job(this.myJob, cb, 100) |
| 8598 // |
| 8599 // returns a job handle which can be used to re-register a job |
| 8600 |
| 8601 var Job = function(inContext) { |
| 8602 this.context = inContext; |
| 8603 this.boundComplete = this.complete.bind(this) |
| 8604 }; |
| 8605 Job.prototype = { |
| 8606 go: function(callback, wait) { |
| 8607 this.callback = callback; |
| 8608 var h; |
| 8609 if (!wait) { |
| 8610 h = requestAnimationFrame(this.boundComplete); |
| 8611 this.handle = function() { |
| 8612 cancelAnimationFrame(h); |
| 8613 } |
| 8614 } else { |
| 8615 h = setTimeout(this.boundComplete, wait); |
| 8616 this.handle = function() { |
| 8617 clearTimeout(h); |
| 8618 } |
| 8619 } |
| 8620 }, |
| 8621 stop: function() { |
| 8622 if (this.handle) { |
| 8623 this.handle(); |
| 8624 this.handle = null; |
| 8625 } |
| 8626 }, |
| 8627 complete: function() { |
| 8628 if (this.handle) { |
| 8629 this.stop(); |
| 8630 this.callback.call(this.context); |
| 8631 } |
| 8632 } |
| 8633 }; |
| 8634 |
| 8635 function job(job, callback, wait) { |
| 8636 if (job) { |
| 8637 job.stop(); |
| 8638 } else { |
| 8639 job = new Job(this); |
| 8640 } |
| 8641 job.go(callback, wait); |
| 8642 return job; |
| 8643 } |
| 8644 |
| 8645 // exports |
| 8646 |
| 8647 scope.job = job; |
| 8648 |
| 8649 })(Polymer); |
| 8650 |
| 8651 (function(scope) { |
| 8652 |
| 8653 // dom polyfill, additions, and utility methods |
| 8654 |
| 8655 var registry = {}; |
| 8656 |
| 8657 HTMLElement.register = function(tag, prototype) { |
| 8658 registry[tag] = prototype; |
| 8659 }; |
| 8660 |
| 8661 // get prototype mapped to node <tag> |
| 8662 HTMLElement.getPrototypeForTag = function(tag) { |
| 8663 var prototype = !tag ? HTMLElement.prototype : registry[tag]; |
| 8664 // TODO(sjmiles): creating <tag> is likely to have wasteful side-effects |
| 8665 return prototype || Object.getPrototypeOf(document.createElement(tag)); |
| 8666 }; |
| 8667 |
| 8668 // we have to flag propagation stoppage for the event dispatcher |
| 8669 var originalStopPropagation = Event.prototype.stopPropagation; |
| 8670 Event.prototype.stopPropagation = function() { |
| 8671 this.cancelBubble = true; |
| 8672 originalStopPropagation.apply(this, arguments); |
| 8673 }; |
| 8674 |
| 8675 |
| 8676 // polyfill DOMTokenList |
| 8677 // * add/remove: allow these methods to take multiple classNames |
| 8678 // * toggle: add a 2nd argument which forces the given state rather |
| 8679 // than toggling. |
| 8680 |
| 8681 var add = DOMTokenList.prototype.add; |
| 8682 var remove = DOMTokenList.prototype.remove; |
| 8683 DOMTokenList.prototype.add = function() { |
| 8684 for (var i = 0; i < arguments.length; i++) { |
| 8685 add.call(this, arguments[i]); |
| 8686 } |
| 8687 }; |
| 8688 DOMTokenList.prototype.remove = function() { |
| 8689 for (var i = 0; i < arguments.length; i++) { |
| 8690 remove.call(this, arguments[i]); |
| 8691 } |
| 8692 }; |
| 8693 DOMTokenList.prototype.toggle = function(name, bool) { |
| 8694 if (arguments.length == 1) { |
| 8695 bool = !this.contains(name); |
| 8696 } |
| 8697 bool ? this.add(name) : this.remove(name); |
| 8698 }; |
| 8699 DOMTokenList.prototype.switch = function(oldName, newName) { |
| 8700 oldName && this.remove(oldName); |
| 8701 newName && this.add(newName); |
| 8702 }; |
| 8703 |
| 8704 // add array() to NodeList, NamedNodeMap, HTMLCollection |
| 8705 |
| 8706 var ArraySlice = function() { |
| 8707 return Array.prototype.slice.call(this); |
| 8708 }; |
| 8709 |
| 8710 var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {}); |
| 8711 |
| 8712 NodeList.prototype.array = ArraySlice; |
| 8713 namedNodeMap.prototype.array = ArraySlice; |
| 8714 HTMLCollection.prototype.array = ArraySlice; |
| 8715 |
| 8716 // utility |
| 8717 |
| 8718 function createDOM(inTagOrNode, inHTML, inAttrs) { |
| 8719 var dom = typeof inTagOrNode == 'string' ? |
| 8720 document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true); |
| 8721 dom.innerHTML = inHTML; |
| 8722 if (inAttrs) { |
| 8723 for (var n in inAttrs) { |
| 8724 dom.setAttribute(n, inAttrs[n]); |
| 8725 } |
| 8726 } |
| 8727 return dom; |
| 8728 } |
| 8729 |
| 8730 // exports |
| 8731 |
| 8732 scope.createDOM = createDOM; |
| 8733 |
| 8734 })(Polymer); |
| 8735 |
| 8736 (function(scope) { |
| 8737 // super |
| 8738 |
| 8739 // `arrayOfArgs` is an optional array of args like one might pass |
| 8740 // to `Function.apply` |
| 8741 |
| 8742 // TODO(sjmiles): |
| 8743 // $super must be installed on an instance or prototype chain |
| 8744 // as `super`, and invoked via `this`, e.g. |
| 8745 // `this.super();` |
| 8746 |
| 8747 // will not work if function objects are not unique, for example, |
| 8748 // when using mixins. |
| 8749 // The memoization strategy assumes each function exists on only one |
| 8750 // prototype chain i.e. we use the function object for memoizing) |
| 8751 // perhaps we can bookkeep on the prototype itself instead |
| 8752 function $super(arrayOfArgs) { |
| 8753 // since we are thunking a method call, performance is important here: |
| 8754 // memoize all lookups, once memoized the fast path calls no other |
| 8755 // functions |
| 8756 // |
| 8757 // find the caller (cannot be `strict` because of 'caller') |
| 8758 var caller = $super.caller; |
| 8759 // memoized 'name of method' |
| 8760 var nom = caller.nom; |
| 8761 // memoized next implementation prototype |
| 8762 var _super = caller._super; |
| 8763 if (!_super) { |
| 8764 if (!nom) { |
| 8765 nom = caller.nom = nameInThis.call(this, caller); |
| 8766 } |
| 8767 if (!nom) { |
| 8768 console.warn('called super() on a method not installed declaratively (
has no .nom property)'); |
| 8769 } |
| 8770 // super prototype is either cached or we have to find it |
| 8771 // by searching __proto__ (at the 'top') |
| 8772 // invariant: because we cache _super on fn below, we never reach |
| 8773 // here from inside a series of calls to super(), so it's ok to |
| 8774 // start searching from the prototype of 'this' (at the 'top') |
| 8775 // we must never memoize a null super for this reason |
| 8776 _super = memoizeSuper(caller, nom, getPrototypeOf(this)); |
| 8777 } |
| 8778 // our super function |
| 8779 var fn = _super[nom]; |
| 8780 if (fn) { |
| 8781 // memoize information so 'fn' can call 'super' |
| 8782 if (!fn._super) { |
| 8783 // must not memoize null, or we lose our invariant above |
| 8784 memoizeSuper(fn, nom, _super); |
| 8785 } |
| 8786 // invoke the inherited method |
| 8787 // if 'fn' is not function valued, this will throw |
| 8788 return fn.apply(this, arrayOfArgs || []); |
| 8789 } |
| 8790 } |
| 8791 |
| 8792 function nameInThis(value) { |
| 8793 var p = this.__proto__; |
| 8794 while (p && p !== HTMLElement.prototype) { |
| 8795 // TODO(sjmiles): getOwnPropertyNames is absurdly expensive |
| 8796 var n$ = Object.getOwnPropertyNames(p); |
| 8797 for (var i=0, l=n$.length, n; i<l && (n=n$[i]); i++) { |
| 8798 var d = Object.getOwnPropertyDescriptor(p, n); |
| 8799 if (typeof d.value === 'function' && d.value === value) { |
| 8800 return n; |
| 8801 } |
| 8802 } |
| 8803 p = p.__proto__; |
| 8804 } |
| 8805 } |
| 8806 |
| 8807 function memoizeSuper(method, name, proto) { |
| 8808 // find and cache next prototype containing `name` |
| 8809 // we need the prototype so we can do another lookup |
| 8810 // from here |
| 8811 var s = nextSuper(proto, name, method); |
| 8812 if (s[name]) { |
| 8813 // `s` is a prototype, the actual method is `s[name]` |
| 8814 // tag super method with it's name for quicker lookups |
| 8815 s[name].nom = name; |
| 8816 } |
| 8817 return method._super = s; |
| 8818 } |
| 8819 |
| 8820 function nextSuper(proto, name, caller) { |
| 8821 // look for an inherited prototype that implements name |
| 8822 while (proto) { |
| 8823 if ((proto[name] !== caller) && proto[name]) { |
| 8824 return proto; |
| 8825 } |
| 8826 proto = getPrototypeOf(proto); |
| 8827 } |
| 8828 // must not return null, or we lose our invariant above |
| 8829 // in this case, a super() call was invoked where no superclass |
| 8830 // method exists |
| 8831 // TODO(sjmiles): thow an exception? |
| 8832 return Object; |
| 8833 } |
| 8834 |
| 8835 // NOTE: In some platforms (IE10) the prototype chain is faked via |
| 8836 // __proto__. Therefore, always get prototype via __proto__ instead of |
| 8837 // the more standard Object.getPrototypeOf. |
| 8838 function getPrototypeOf(prototype) { |
| 8839 return prototype.__proto__; |
| 8840 } |
| 8841 |
| 8842 // utility function to precompute name tags for functions |
| 8843 // in a (unchained) prototype |
| 8844 function hintSuper(prototype) { |
| 8845 // tag functions with their prototype name to optimize |
| 8846 // super call invocations |
| 8847 for (var n in prototype) { |
| 8848 var pd = Object.getOwnPropertyDescriptor(prototype, n); |
| 8849 if (pd && typeof pd.value === 'function') { |
| 8850 pd.value.nom = n; |
| 8851 } |
| 8852 } |
| 8853 } |
| 8854 |
| 8855 // exports |
| 8856 |
| 8857 scope.super = $super; |
| 8858 |
| 8859 })(Polymer); |
| 8860 |
| 8861 (function(scope) { |
| 8862 |
| 8863 function noopHandler(value) { |
| 8864 return value; |
| 8865 } |
| 8866 |
| 8867 // helper for deserializing properties of various types to strings |
| 8868 var typeHandlers = { |
| 8869 string: noopHandler, |
| 8870 'undefined': noopHandler, |
| 8871 date: function(value) { |
| 8872 return new Date(Date.parse(value) || Date.now()); |
| 8873 }, |
| 8874 boolean: function(value) { |
| 8875 if (value === '') { |
| 8876 return true; |
| 8877 } |
| 8878 return value === 'false' ? false : !!value; |
| 8879 }, |
| 8880 number: function(value) { |
| 8881 var n = parseFloat(value); |
| 8882 // hex values like "0xFFFF" parseFloat as 0 |
| 8883 if (n === 0) { |
| 8884 n = parseInt(value); |
| 8885 } |
| 8886 return isNaN(n) ? value : n; |
| 8887 // this code disabled because encoded values (like "0xFFFF") |
| 8888 // do not round trip to their original format |
| 8889 //return (String(floatVal) === value) ? floatVal : value; |
| 8890 }, |
| 8891 object: function(value, currentValue) { |
| 8892 if (currentValue === null) { |
| 8893 return value; |
| 8894 } |
| 8895 try { |
| 8896 // If the string is an object, we can parse is with the JSON library. |
| 8897 // include convenience replace for single-quotes. If the author omits |
| 8898 // quotes altogether, parse will fail. |
| 8899 return JSON.parse(value.replace(/'/g, '"')); |
| 8900 } catch(e) { |
| 8901 // The object isn't valid JSON, return the raw value |
| 8902 return value; |
| 8903 } |
| 8904 }, |
| 8905 // avoid deserialization of functions |
| 8906 'function': function(value, currentValue) { |
| 8907 return currentValue; |
| 8908 } |
| 8909 }; |
| 8910 |
| 8911 function deserializeValue(value, currentValue) { |
| 8912 // attempt to infer type from default value |
| 8913 var inferredType = typeof currentValue; |
| 8914 // invent 'date' type value for Date |
| 8915 if (currentValue instanceof Date) { |
| 8916 inferredType = 'date'; |
| 8917 } |
| 8918 // delegate deserialization via type string |
| 8919 return typeHandlers[inferredType](value, currentValue); |
| 8920 } |
| 8921 |
| 8922 // exports |
| 8923 |
| 8924 scope.deserializeValue = deserializeValue; |
| 8925 |
| 8926 })(Polymer); |
| 8927 |
| 8928 (function(scope) { |
| 8929 |
| 8930 // imports |
| 8931 |
| 8932 var extend = scope.extend; |
| 8933 |
| 8934 // module |
| 8935 |
| 8936 var api = {}; |
| 8937 |
| 8938 api.declaration = {}; |
| 8939 api.instance = {}; |
| 8940 |
| 8941 api.publish = function(apis, prototype) { |
| 8942 for (var n in apis) { |
| 8943 extend(prototype, apis[n]); |
| 8944 } |
| 8945 }; |
| 8946 |
| 8947 // exports |
| 8948 |
| 8949 scope.api = api; |
| 8950 |
| 8951 })(Polymer); |
| 8952 |
| 8953 (function(scope) { |
| 8954 |
| 8955 /** |
| 8956 * @class polymer-base |
| 8957 */ |
| 8958 |
| 8959 var utils = { |
| 8960 |
| 8961 /** |
| 8962 * Invokes a function asynchronously. The context of the callback |
| 8963 * function is bound to 'this' automatically. Returns a handle which may |
| 8964 * be passed to <a href="#cancelAsync">cancelAsync</a> to cancel the |
| 8965 * asynchronous call. |
| 8966 * |
| 8967 * @method async |
| 8968 * @param {Function|String} method |
| 8969 * @param {any|Array} args |
| 8970 * @param {number} timeout |
| 8971 */ |
| 8972 async: function(method, args, timeout) { |
| 8973 // when polyfilling Object.observe, ensure changes |
| 8974 // propagate before executing the async method |
| 8975 Polymer.flush(); |
| 8976 // second argument to `apply` must be an array |
| 8977 args = (args && args.length) ? args : [args]; |
| 8978 // function to invoke |
| 8979 var fn = function() { |
| 8980 (this[method] || method).apply(this, args); |
| 8981 }.bind(this); |
| 8982 // execute `fn` sooner or later |
| 8983 var handle = timeout ? setTimeout(fn, timeout) : |
| 8984 requestAnimationFrame(fn); |
| 8985 // NOTE: switch on inverting handle to determine which time is used. |
| 8986 return timeout ? handle : ~handle; |
| 8987 }, |
| 8988 |
| 8989 /** |
| 8990 * Cancels a pending callback that was scheduled via |
| 8991 * <a href="#async">async</a>. |
| 8992 * |
| 8993 * @method cancelAsync |
| 8994 * @param {handle} handle Handle of the `async` to cancel. |
| 8995 */ |
| 8996 cancelAsync: function(handle) { |
| 8997 if (handle < 0) { |
| 8998 cancelAnimationFrame(~handle); |
| 8999 } else { |
| 9000 clearTimeout(handle); |
| 9001 } |
| 9002 }, |
| 9003 |
| 9004 /** |
| 9005 * Fire an event. |
| 9006 * |
| 9007 * @method fire |
| 9008 * @returns {Object} event |
| 9009 * @param {string} type An event name. |
| 9010 * @param {any} detail |
| 9011 * @param {Node} onNode Target node. |
| 9012 * @param {Boolean} bubbles Set false to prevent bubbling, defaults to true |
| 9013 * @param {Boolean} cancelable Set false to prevent cancellation, defaults
to true |
| 9014 */ |
| 9015 fire: function(type, detail, onNode, bubbles, cancelable) { |
| 9016 var node = onNode || this; |
| 9017 var detail = detail === null || detail === undefined ? {} : detail; |
| 9018 var event = new CustomEvent(type, { |
| 9019 bubbles: bubbles !== undefined ? bubbles : true, |
| 9020 cancelable: cancelable !== undefined ? cancelable : true, |
| 9021 detail: detail |
| 9022 }); |
| 9023 node.dispatchEvent(event); |
| 9024 return event; |
| 9025 }, |
| 9026 |
| 9027 /** |
| 9028 * Fire an event asynchronously. |
| 9029 * |
| 9030 * @method asyncFire |
| 9031 * @param {string} type An event name. |
| 9032 * @param detail |
| 9033 * @param {Node} toNode Target node. |
| 9034 */ |
| 9035 asyncFire: function(/*inType, inDetail*/) { |
| 9036 this.async("fire", arguments); |
| 9037 }, |
| 9038 |
| 9039 /** |
| 9040 * Remove class from old, add class to anew, if they exist. |
| 9041 * |
| 9042 * @param classFollows |
| 9043 * @param anew A node. |
| 9044 * @param old A node |
| 9045 * @param className |
| 9046 */ |
| 9047 classFollows: function(anew, old, className) { |
| 9048 if (old) { |
| 9049 old.classList.remove(className); |
| 9050 } |
| 9051 if (anew) { |
| 9052 anew.classList.add(className); |
| 9053 } |
| 9054 }, |
| 9055 |
| 9056 /** |
| 9057 * Inject HTML which contains markup bound to this element into |
| 9058 * a target element (replacing target element content). |
| 9059 * |
| 9060 * @param String html to inject |
| 9061 * @param Element target element |
| 9062 */ |
| 9063 injectBoundHTML: function(html, element) { |
| 9064 var template = document.createElement('template'); |
| 9065 template.innerHTML = html; |
| 9066 var fragment = this.instanceTemplate(template); |
| 9067 if (element) { |
| 9068 element.textContent = ''; |
| 9069 element.appendChild(fragment); |
| 9070 } |
| 9071 return fragment; |
| 9072 } |
| 9073 }; |
| 9074 |
| 9075 // no-operation function for handy stubs |
| 9076 var nop = function() {}; |
| 9077 |
| 9078 // null-object for handy stubs |
| 9079 var nob = {}; |
| 9080 |
| 9081 // deprecated |
| 9082 |
| 9083 utils.asyncMethod = utils.async; |
| 9084 |
| 9085 // exports |
| 9086 |
| 9087 scope.api.instance.utils = utils; |
| 9088 scope.nop = nop; |
| 9089 scope.nob = nob; |
| 9090 |
| 9091 })(Polymer); |
| 9092 |
| 9093 (function(scope) { |
| 9094 |
| 9095 // imports |
| 9096 |
| 9097 var log = window.WebComponents ? WebComponents.flags.log : {}; |
| 9098 var EVENT_PREFIX = 'on-'; |
| 9099 |
| 9100 // instance events api |
| 9101 var events = { |
| 9102 // read-only |
| 9103 EVENT_PREFIX: EVENT_PREFIX, |
| 9104 // event listeners on host |
| 9105 addHostListeners: function() { |
| 9106 var events = this.eventDelegates; |
| 9107 log.events && (Object.keys(events).length > 0) && console.log('[%s] addHos
tListeners:', this.localName, events); |
| 9108 // NOTE: host events look like bindings but really are not; |
| 9109 // (1) we don't want the attribute to be set and (2) we want to support |
| 9110 // multiple event listeners ('host' and 'instance') and Node.bind |
| 9111 // by default supports 1 thing being bound. |
| 9112 for (var type in events) { |
| 9113 var methodName = events[type]; |
| 9114 PolymerGestures.addEventListener(this, type, this.element.getEventHandle
r(this, this, methodName)); |
| 9115 } |
| 9116 }, |
| 9117 // call 'method' or function method on 'obj' with 'args', if the method exis
ts |
| 9118 dispatchMethod: function(obj, method, args) { |
| 9119 if (obj) { |
| 9120 log.events && console.group('[%s] dispatch [%s]', obj.localName, method)
; |
| 9121 var fn = typeof method === 'function' ? method : obj[method]; |
| 9122 if (fn) { |
| 9123 fn[args ? 'apply' : 'call'](obj, args); |
| 9124 } |
| 9125 log.events && console.groupEnd(); |
| 9126 // NOTE: dirty check right after calling method to ensure |
| 9127 // changes apply quickly; in a very complicated app using high |
| 9128 // frequency events, this can be a perf concern; in this case, |
| 9129 // imperative handlers can be used to avoid flushing. |
| 9130 Polymer.flush(); |
| 9131 } |
| 9132 } |
| 9133 }; |
| 9134 |
| 9135 // exports |
| 9136 |
| 9137 scope.api.instance.events = events; |
| 9138 |
| 9139 /** |
| 9140 * @class Polymer |
| 9141 */ |
| 9142 |
| 9143 /** |
| 9144 * Add a gesture aware event handler to the given `node`. Can be used |
| 9145 * in place of `element.addEventListener` and ensures gestures will function |
| 9146 * as expected on mobile platforms. Please note that Polymer's declarative |
| 9147 * event handlers include this functionality by default. |
| 9148 * |
| 9149 * @method addEventListener |
| 9150 * @param {Node} node node on which to listen |
| 9151 * @param {String} eventType name of the event |
| 9152 * @param {Function} handlerFn event handler function |
| 9153 * @param {Boolean} capture set to true to invoke event capturing |
| 9154 * @type Function |
| 9155 */ |
| 9156 // alias PolymerGestures event listener logic |
| 9157 scope.addEventListener = function(node, eventType, handlerFn, capture) { |
| 9158 PolymerGestures.addEventListener(wrap(node), eventType, handlerFn, capture); |
| 9159 }; |
| 9160 |
| 9161 /** |
| 9162 * Remove a gesture aware event handler on the given `node`. To remove an |
| 9163 * event listener, the exact same arguments are required that were passed |
| 9164 * to `Polymer.addEventListener`. |
| 9165 * |
| 9166 * @method removeEventListener |
| 9167 * @param {Node} node node on which to listen |
| 9168 * @param {String} eventType name of the event |
| 9169 * @param {Function} handlerFn event handler function |
| 9170 * @param {Boolean} capture set to true to invoke event capturing |
| 9171 * @type Function |
| 9172 */ |
| 9173 scope.removeEventListener = function(node, eventType, handlerFn, capture) { |
| 9174 PolymerGestures.removeEventListener(wrap(node), eventType, handlerFn, captur
e); |
| 9175 }; |
| 9176 |
| 9177 })(Polymer); |
| 9178 |
| 9179 (function(scope) { |
| 9180 |
| 9181 // instance api for attributes |
| 9182 |
| 9183 var attributes = { |
| 9184 // copy attributes defined in the element declaration to the instance |
| 9185 // e.g. <polymer-element name="x-foo" tabIndex="0"> tabIndex is copied |
| 9186 // to the element instance here. |
| 9187 copyInstanceAttributes: function () { |
| 9188 var a$ = this._instanceAttributes; |
| 9189 for (var k in a$) { |
| 9190 if (!this.hasAttribute(k)) { |
| 9191 this.setAttribute(k, a$[k]); |
| 9192 } |
| 9193 } |
| 9194 }, |
| 9195 // for each attribute on this, deserialize value to property as needed |
| 9196 takeAttributes: function() { |
| 9197 // if we have no publish lookup table, we have no attributes to take |
| 9198 // TODO(sjmiles): ad hoc |
| 9199 if (this._publishLC) { |
| 9200 for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++)
{ |
| 9201 this.attributeToProperty(a.name, a.value); |
| 9202 } |
| 9203 } |
| 9204 }, |
| 9205 // if attribute 'name' is mapped to a property, deserialize |
| 9206 // 'value' into that property |
| 9207 attributeToProperty: function(name, value) { |
| 9208 // try to match this attribute to a property (attributes are |
| 9209 // all lower-case, so this is case-insensitive search) |
| 9210 var name = this.propertyForAttribute(name); |
| 9211 if (name) { |
| 9212 // filter out 'mustached' values, these are to be |
| 9213 // replaced with bound-data and are not yet values |
| 9214 // themselves |
| 9215 if (value && value.search(scope.bindPattern) >= 0) { |
| 9216 return; |
| 9217 } |
| 9218 // get original value |
| 9219 var currentValue = this[name]; |
| 9220 // deserialize Boolean or Number values from attribute |
| 9221 var value = this.deserializeValue(value, currentValue); |
| 9222 // only act if the value has changed |
| 9223 if (value !== currentValue) { |
| 9224 // install new value (has side-effects) |
| 9225 this[name] = value; |
| 9226 } |
| 9227 } |
| 9228 }, |
| 9229 // return the published property matching name, or undefined |
| 9230 propertyForAttribute: function(name) { |
| 9231 var match = this._publishLC && this._publishLC[name]; |
| 9232 return match; |
| 9233 }, |
| 9234 // convert representation of `stringValue` based on type of `currentValue` |
| 9235 deserializeValue: function(stringValue, currentValue) { |
| 9236 return scope.deserializeValue(stringValue, currentValue); |
| 9237 }, |
| 9238 // convert to a string value based on the type of `inferredType` |
| 9239 serializeValue: function(value, inferredType) { |
| 9240 if (inferredType === 'boolean') { |
| 9241 return value ? '' : undefined; |
| 9242 } else if (inferredType !== 'object' && inferredType !== 'function' |
| 9243 && value !== undefined) { |
| 9244 return value; |
| 9245 } |
| 9246 }, |
| 9247 // serializes `name` property value and updates the corresponding attribute |
| 9248 // note that reflection is opt-in. |
| 9249 reflectPropertyToAttribute: function(name) { |
| 9250 var inferredType = typeof this[name]; |
| 9251 // try to intelligently serialize property value |
| 9252 var serializedValue = this.serializeValue(this[name], inferredType); |
| 9253 // boolean properties must reflect as boolean attributes |
| 9254 if (serializedValue !== undefined) { |
| 9255 this.setAttribute(name, serializedValue); |
| 9256 // TODO(sorvell): we should remove attr for all properties |
| 9257 // that have undefined serialization; however, we will need to |
| 9258 // refine the attr reflection system to achieve this; pica, for example, |
| 9259 // relies on having inferredType object properties not removed as |
| 9260 // attrs. |
| 9261 } else if (inferredType === 'boolean') { |
| 9262 this.removeAttribute(name); |
| 9263 } |
| 9264 } |
| 9265 }; |
| 9266 |
| 9267 // exports |
| 9268 |
| 9269 scope.api.instance.attributes = attributes; |
| 9270 |
| 9271 })(Polymer); |
| 9272 |
| 9273 (function(scope) { |
| 9274 |
| 9275 /** |
| 9276 * @class polymer-base |
| 9277 */ |
| 9278 |
| 9279 // imports |
| 9280 |
| 9281 var log = window.WebComponents ? WebComponents.flags.log : {}; |
| 9282 |
| 9283 // magic words |
| 9284 |
| 9285 var OBSERVE_SUFFIX = 'Changed'; |
| 9286 |
| 9287 // element api |
| 9288 |
| 9289 var empty = []; |
| 9290 |
| 9291 var updateRecord = { |
| 9292 object: undefined, |
| 9293 type: 'update', |
| 9294 name: undefined, |
| 9295 oldValue: undefined |
| 9296 }; |
| 9297 |
| 9298 var numberIsNaN = Number.isNaN || function(value) { |
| 9299 return typeof value === 'number' && isNaN(value); |
| 9300 }; |
| 9301 |
| 9302 function areSameValue(left, right) { |
| 9303 if (left === right) |
| 9304 return left !== 0 || 1 / left === 1 / right; |
| 9305 if (numberIsNaN(left) && numberIsNaN(right)) |
| 9306 return true; |
| 9307 return left !== left && right !== right; |
| 9308 } |
| 9309 |
| 9310 // capture A's value if B's value is null or undefined, |
| 9311 // otherwise use B's value |
| 9312 function resolveBindingValue(oldValue, value) { |
| 9313 if (value === undefined && oldValue === null) { |
| 9314 return value; |
| 9315 } |
| 9316 return (value === null || value === undefined) ? oldValue : value; |
| 9317 } |
| 9318 |
| 9319 var properties = { |
| 9320 |
| 9321 // creates a CompoundObserver to observe property changes |
| 9322 // NOTE, this is only done there are any properties in the `observe` object |
| 9323 createPropertyObserver: function() { |
| 9324 var n$ = this._observeNames; |
| 9325 if (n$ && n$.length) { |
| 9326 var o = this._propertyObserver = new CompoundObserver(true); |
| 9327 this.registerObserver(o); |
| 9328 // TODO(sorvell): may not be kosher to access the value here (this[n]); |
| 9329 // previously we looked at the descriptor on the prototype |
| 9330 // this doesn't work for inheritance and not for accessors without |
| 9331 // a value property |
| 9332 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { |
| 9333 o.addPath(this, n); |
| 9334 this.observeArrayValue(n, this[n], null); |
| 9335 } |
| 9336 } |
| 9337 }, |
| 9338 |
| 9339 // start observing property changes |
| 9340 openPropertyObserver: function() { |
| 9341 if (this._propertyObserver) { |
| 9342 this._propertyObserver.open(this.notifyPropertyChanges, this); |
| 9343 } |
| 9344 }, |
| 9345 |
| 9346 // handler for property changes; routes changes to observing methods |
| 9347 // note: array valued properties are observed for array splices |
| 9348 notifyPropertyChanges: function(newValues, oldValues, paths) { |
| 9349 var name, method, called = {}; |
| 9350 for (var i in oldValues) { |
| 9351 // note: paths is of form [object, path, object, path] |
| 9352 name = paths[2 * i + 1]; |
| 9353 method = this.observe[name]; |
| 9354 if (method) { |
| 9355 var ov = oldValues[i], nv = newValues[i]; |
| 9356 // observes the value if it is an array |
| 9357 this.observeArrayValue(name, nv, ov); |
| 9358 if (!called[method]) { |
| 9359 // only invoke change method if one of ov or nv is not (undefined |
null) |
| 9360 if ((ov !== undefined && ov !== null) || (nv !== undefined && nv !==
null)) { |
| 9361 called[method] = true; |
| 9362 // TODO(sorvell): call method with the set of values it's expectin
g; |
| 9363 // e.g. 'foo bar': 'invalidate' expects the new and old values for |
| 9364 // foo and bar. Currently we give only one of these and then |
| 9365 // deliver all the arguments. |
| 9366 this.invokeMethod(method, [ov, nv, arguments]); |
| 9367 } |
| 9368 } |
| 9369 } |
| 9370 } |
| 9371 }, |
| 9372 |
| 9373 // call method iff it exists. |
| 9374 invokeMethod: function(method, args) { |
| 9375 var fn = this[method] || method; |
| 9376 if (typeof fn === 'function') { |
| 9377 fn.apply(this, args); |
| 9378 } |
| 9379 }, |
| 9380 |
| 9381 /** |
| 9382 * Force any pending property changes to synchronously deliver to |
| 9383 * handlers specified in the `observe` object. |
| 9384 * Note, normally changes are processed at microtask time. |
| 9385 * |
| 9386 * @method deliverChanges |
| 9387 */ |
| 9388 deliverChanges: function() { |
| 9389 if (this._propertyObserver) { |
| 9390 this._propertyObserver.deliver(); |
| 9391 } |
| 9392 }, |
| 9393 |
| 9394 observeArrayValue: function(name, value, old) { |
| 9395 // we only care if there are registered side-effects |
| 9396 var callbackName = this.observe[name]; |
| 9397 if (callbackName) { |
| 9398 // if we are observing the previous value, stop |
| 9399 if (Array.isArray(old)) { |
| 9400 log.observe && console.log('[%s] observeArrayValue: unregister observe
r [%s]', this.localName, name); |
| 9401 this.closeNamedObserver(name + '__array'); |
| 9402 } |
| 9403 // if the new value is an array, being observing it |
| 9404 if (Array.isArray(value)) { |
| 9405 log.observe && console.log('[%s] observeArrayValue: register observer
[%s]', this.localName, name, value); |
| 9406 var observer = new ArrayObserver(value); |
| 9407 observer.open(function(splices) { |
| 9408 this.invokeMethod(callbackName, [splices]); |
| 9409 }, this); |
| 9410 this.registerNamedObserver(name + '__array', observer); |
| 9411 } |
| 9412 } |
| 9413 }, |
| 9414 |
| 9415 emitPropertyChangeRecord: function(name, value, oldValue) { |
| 9416 var object = this; |
| 9417 if (areSameValue(value, oldValue)) { |
| 9418 return; |
| 9419 } |
| 9420 // invoke property change side effects |
| 9421 this._propertyChanged(name, value, oldValue); |
| 9422 // emit change record |
| 9423 if (!Observer.hasObjectObserve) { |
| 9424 return; |
| 9425 } |
| 9426 var notifier = this._objectNotifier; |
| 9427 if (!notifier) { |
| 9428 notifier = this._objectNotifier = Object.getNotifier(this); |
| 9429 } |
| 9430 updateRecord.object = this; |
| 9431 updateRecord.name = name; |
| 9432 updateRecord.oldValue = oldValue; |
| 9433 notifier.notify(updateRecord); |
| 9434 }, |
| 9435 |
| 9436 _propertyChanged: function(name, value, oldValue) { |
| 9437 if (this.reflect[name]) { |
| 9438 this.reflectPropertyToAttribute(name); |
| 9439 } |
| 9440 }, |
| 9441 |
| 9442 // creates a property binding (called via bind) to a published property. |
| 9443 bindProperty: function(property, observable, oneTime) { |
| 9444 if (oneTime) { |
| 9445 this[property] = observable; |
| 9446 return; |
| 9447 } |
| 9448 var computed = this.element.prototype.computed; |
| 9449 // Binding an "out-only" value to a computed property. Note that |
| 9450 // since this observer isn't opened, it doesn't need to be closed on |
| 9451 // cleanup. |
| 9452 if (computed && computed[property]) { |
| 9453 var privateComputedBoundValue = property + 'ComputedBoundObservable_'; |
| 9454 this[privateComputedBoundValue] = observable; |
| 9455 return; |
| 9456 } |
| 9457 return this.bindToAccessor(property, observable, resolveBindingValue); |
| 9458 }, |
| 9459 |
| 9460 // NOTE property `name` must be published. This makes it an accessor. |
| 9461 bindToAccessor: function(name, observable, resolveFn) { |
| 9462 var privateName = name + '_'; |
| 9463 var privateObservable = name + 'Observable_'; |
| 9464 // Present for properties which are computed and published and have a |
| 9465 // bound value. |
| 9466 var privateComputedBoundValue = name + 'ComputedBoundObservable_'; |
| 9467 this[privateObservable] = observable; |
| 9468 var oldValue = this[privateName]; |
| 9469 // observable callback |
| 9470 var self = this; |
| 9471 function updateValue(value, oldValue) { |
| 9472 self[privateName] = value; |
| 9473 var setObserveable = self[privateComputedBoundValue]; |
| 9474 if (setObserveable && typeof setObserveable.setValue == 'function') { |
| 9475 setObserveable.setValue(value); |
| 9476 } |
| 9477 self.emitPropertyChangeRecord(name, value, oldValue); |
| 9478 } |
| 9479 // resolve initial value |
| 9480 var value = observable.open(updateValue); |
| 9481 if (resolveFn && !areSameValue(oldValue, value)) { |
| 9482 var resolvedValue = resolveFn(oldValue, value); |
| 9483 if (!areSameValue(value, resolvedValue)) { |
| 9484 value = resolvedValue; |
| 9485 if (observable.setValue) { |
| 9486 observable.setValue(value); |
| 9487 } |
| 9488 } |
| 9489 } |
| 9490 updateValue(value, oldValue); |
| 9491 // register and return observable |
| 9492 var observer = { |
| 9493 close: function() { |
| 9494 observable.close(); |
| 9495 self[privateObservable] = undefined; |
| 9496 self[privateComputedBoundValue] = undefined; |
| 9497 } |
| 9498 }; |
| 9499 this.registerObserver(observer); |
| 9500 return observer; |
| 9501 }, |
| 9502 |
| 9503 createComputedProperties: function() { |
| 9504 if (!this._computedNames) { |
| 9505 return; |
| 9506 } |
| 9507 for (var i = 0; i < this._computedNames.length; i++) { |
| 9508 var name = this._computedNames[i]; |
| 9509 var expressionText = this.computed[name]; |
| 9510 try { |
| 9511 var expression = PolymerExpressions.getExpression(expressionText); |
| 9512 var observable = expression.getBinding(this, this.element.syntax); |
| 9513 this.bindToAccessor(name, observable); |
| 9514 } catch (ex) { |
| 9515 console.error('Failed to create computed property', ex); |
| 9516 } |
| 9517 } |
| 9518 }, |
| 9519 |
| 9520 // property bookkeeping |
| 9521 registerObserver: function(observer) { |
| 9522 if (!this._observers) { |
| 9523 this._observers = [observer]; |
| 9524 return; |
| 9525 } |
| 9526 this._observers.push(observer); |
| 9527 }, |
| 9528 |
| 9529 closeObservers: function() { |
| 9530 if (!this._observers) { |
| 9531 return; |
| 9532 } |
| 9533 // observer array items are arrays of observers. |
| 9534 var observers = this._observers; |
| 9535 for (var i = 0; i < observers.length; i++) { |
| 9536 var observer = observers[i]; |
| 9537 if (observer && typeof observer.close == 'function') { |
| 9538 observer.close(); |
| 9539 } |
| 9540 } |
| 9541 this._observers = []; |
| 9542 }, |
| 9543 |
| 9544 // bookkeeping observers for memory management |
| 9545 registerNamedObserver: function(name, observer) { |
| 9546 var o$ = this._namedObservers || (this._namedObservers = {}); |
| 9547 o$[name] = observer; |
| 9548 }, |
| 9549 |
| 9550 closeNamedObserver: function(name) { |
| 9551 var o$ = this._namedObservers; |
| 9552 if (o$ && o$[name]) { |
| 9553 o$[name].close(); |
| 9554 o$[name] = null; |
| 9555 return true; |
| 9556 } |
| 9557 }, |
| 9558 |
| 9559 closeNamedObservers: function() { |
| 9560 if (this._namedObservers) { |
| 9561 for (var i in this._namedObservers) { |
| 9562 this.closeNamedObserver(i); |
| 9563 } |
| 9564 this._namedObservers = {}; |
| 9565 } |
| 9566 } |
| 9567 |
| 9568 }; |
| 9569 |
| 9570 // logging |
| 9571 var LOG_OBSERVE = '[%s] watching [%s]'; |
| 9572 var LOG_OBSERVED = '[%s#%s] watch: [%s] now [%s] was [%s]'; |
| 9573 var LOG_CHANGED = '[%s#%s] propertyChanged: [%s] now [%s] was [%s]'; |
| 9574 |
| 9575 // exports |
| 9576 |
| 9577 scope.api.instance.properties = properties; |
| 9578 |
| 9579 })(Polymer); |
| 9580 |
| 9581 (function(scope) { |
| 9582 |
| 9583 /** |
| 9584 * @class polymer-base |
| 9585 */ |
| 9586 |
| 9587 // imports |
| 9588 |
| 9589 var log = window.WebComponents ? WebComponents.flags.log : {}; |
| 9590 |
| 9591 // element api supporting mdv |
| 9592 var mdv = { |
| 9593 |
| 9594 /** |
| 9595 * Creates dom cloned from the given template, instantiating bindings |
| 9596 * with this element as the template model and `PolymerExpressions` as the |
| 9597 * binding delegate. |
| 9598 * |
| 9599 * @method instanceTemplate |
| 9600 * @param {Template} template source template from which to create dom. |
| 9601 */ |
| 9602 instanceTemplate: function(template) { |
| 9603 // ensure template is decorated (lets' things like <tr template ...> work) |
| 9604 HTMLTemplateElement.decorate(template); |
| 9605 // ensure a default bindingDelegate |
| 9606 var syntax = this.syntax || (!template.bindingDelegate && |
| 9607 this.element.syntax); |
| 9608 var dom = template.createInstance(this, syntax); |
| 9609 var observers = dom.bindings_; |
| 9610 for (var i = 0; i < observers.length; i++) { |
| 9611 this.registerObserver(observers[i]); |
| 9612 } |
| 9613 return dom; |
| 9614 }, |
| 9615 |
| 9616 // Called by TemplateBinding/NodeBind to setup a binding to the given |
| 9617 // property. It's overridden here to support property bindings |
| 9618 // in addition to attribute bindings that are supported by default. |
| 9619 bind: function(name, observable, oneTime) { |
| 9620 var property = this.propertyForAttribute(name); |
| 9621 if (!property) { |
| 9622 // TODO(sjmiles): this mixin method must use the special form |
| 9623 // of `super` installed by `mixinMethod` in declaration/prototype.js |
| 9624 return this.mixinSuper(arguments); |
| 9625 } else { |
| 9626 // use n-way Polymer binding |
| 9627 var observer = this.bindProperty(property, observable, oneTime); |
| 9628 // NOTE: reflecting binding information is typically required only for |
| 9629 // tooling. It has a performance cost so it's opt-in in Node.bind. |
| 9630 if (Platform.enableBindingsReflection && observer) { |
| 9631 observer.path = observable.path_; |
| 9632 this._recordBinding(property, observer); |
| 9633 } |
| 9634 if (this.reflect[property]) { |
| 9635 this.reflectPropertyToAttribute(property); |
| 9636 } |
| 9637 return observer; |
| 9638 } |
| 9639 }, |
| 9640 |
| 9641 _recordBinding: function(name, observer) { |
| 9642 this.bindings_ = this.bindings_ || {}; |
| 9643 this.bindings_[name] = observer; |
| 9644 }, |
| 9645 |
| 9646 // Called by TemplateBinding when all bindings on an element have been |
| 9647 // executed. This signals that all element inputs have been gathered |
| 9648 // and it's safe to ready the element, create shadow-root and start |
| 9649 // data-observation. |
| 9650 bindFinished: function() { |
| 9651 this.makeElementReady(); |
| 9652 }, |
| 9653 |
| 9654 // called at detached time to signal that an element's bindings should be |
| 9655 // cleaned up. This is done asynchronously so that users have the chance |
| 9656 // to call `cancelUnbindAll` to prevent unbinding. |
| 9657 asyncUnbindAll: function() { |
| 9658 if (!this._unbound) { |
| 9659 log.unbind && console.log('[%s] asyncUnbindAll', this.localName); |
| 9660 this._unbindAllJob = this.job(this._unbindAllJob, this.unbindAll, 0); |
| 9661 } |
| 9662 }, |
| 9663 |
| 9664 /** |
| 9665 * This method should rarely be used and only if |
| 9666 * <a href="#cancelUnbindAll">`cancelUnbindAll`</a> has been called to |
| 9667 * prevent element unbinding. In this case, the element's bindings will |
| 9668 * not be automatically cleaned up and it cannot be garbage collected |
| 9669 * by the system. If memory pressure is a concern or a |
| 9670 * large amount of elements need to be managed in this way, `unbindAll` |
| 9671 * can be called to deactivate the element's bindings and allow its |
| 9672 * memory to be reclaimed. |
| 9673 * |
| 9674 * @method unbindAll |
| 9675 */ |
| 9676 unbindAll: function() { |
| 9677 if (!this._unbound) { |
| 9678 this.closeObservers(); |
| 9679 this.closeNamedObservers(); |
| 9680 this._unbound = true; |
| 9681 } |
| 9682 }, |
| 9683 |
| 9684 /** |
| 9685 * Call in `detached` to prevent the element from unbinding when it is |
| 9686 * detached from the dom. The element is unbound as a cleanup step that |
| 9687 * allows its memory to be reclaimed. |
| 9688 * If `cancelUnbindAll` is used, consider calling |
| 9689 * <a href="#unbindAll">`unbindAll`</a> when the element is no longer |
| 9690 * needed. This will allow its memory to be reclaimed. |
| 9691 * |
| 9692 * @method cancelUnbindAll |
| 9693 */ |
| 9694 cancelUnbindAll: function() { |
| 9695 if (this._unbound) { |
| 9696 log.unbind && console.warn('[%s] already unbound, cannot cancel unbindAl
l', this.localName); |
| 9697 return; |
| 9698 } |
| 9699 log.unbind && console.log('[%s] cancelUnbindAll', this.localName); |
| 9700 if (this._unbindAllJob) { |
| 9701 this._unbindAllJob = this._unbindAllJob.stop(); |
| 9702 } |
| 9703 } |
| 9704 |
| 9705 }; |
| 9706 |
| 9707 function unbindNodeTree(node) { |
| 9708 forNodeTree(node, _nodeUnbindAll); |
| 9709 } |
| 9710 |
| 9711 function _nodeUnbindAll(node) { |
| 9712 node.unbindAll(); |
| 9713 } |
| 9714 |
| 9715 function forNodeTree(node, callback) { |
| 9716 if (node) { |
| 9717 callback(node); |
| 9718 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 9719 forNodeTree(child, callback); |
| 9720 } |
| 9721 } |
| 9722 } |
| 9723 |
| 9724 var mustachePattern = /\{\{([^{}]*)}}/; |
| 9725 |
| 9726 // exports |
| 9727 |
| 9728 scope.bindPattern = mustachePattern; |
| 9729 scope.api.instance.mdv = mdv; |
| 9730 |
| 9731 })(Polymer); |
| 9732 |
| 9733 (function(scope) { |
| 9734 |
| 9735 /** |
| 9736 * Common prototype for all Polymer Elements. |
| 9737 * |
| 9738 * @class polymer-base |
| 9739 * @homepage polymer.github.io |
| 9740 */ |
| 9741 var base = { |
| 9742 /** |
| 9743 * Tags this object as the canonical Base prototype. |
| 9744 * |
| 9745 * @property PolymerBase |
| 9746 * @type boolean |
| 9747 * @default true |
| 9748 */ |
| 9749 PolymerBase: true, |
| 9750 |
| 9751 /** |
| 9752 * Debounce signals. |
| 9753 * |
| 9754 * Call `job` to defer a named signal, and all subsequent matching signals, |
| 9755 * until a wait time has elapsed with no new signal. |
| 9756 * |
| 9757 * debouncedClickAction: function(e) { |
| 9758 * // processClick only when it's been 100ms since the last click |
| 9759 * this.job('click', function() { |
| 9760 * this.processClick; |
| 9761 * }, 100); |
| 9762 * } |
| 9763 * |
| 9764 * @method job |
| 9765 * @param String {String} job A string identifier for the job to debounce. |
| 9766 * @param Function {Function} callback A function that is called (with `this
` context) when the wait time elapses. |
| 9767 * @param Number {Number} wait Time in milliseconds (ms) after the last sign
al that must elapse before invoking `callback` |
| 9768 * @type Handle |
| 9769 */ |
| 9770 job: function(job, callback, wait) { |
| 9771 if (typeof job === 'string') { |
| 9772 var n = '___' + job; |
| 9773 this[n] = Polymer.job.call(this, this[n], callback, wait); |
| 9774 } else { |
| 9775 // TODO(sjmiles): suggest we deprecate this call signature |
| 9776 return Polymer.job.call(this, job, callback, wait); |
| 9777 } |
| 9778 }, |
| 9779 |
| 9780 /** |
| 9781 * Invoke a superclass method. |
| 9782 * |
| 9783 * Use `super()` to invoke the most recently overridden call to the |
| 9784 * currently executing function. |
| 9785 * |
| 9786 * To pass arguments through, use the literal `arguments` as the parameter |
| 9787 * to `super()`. |
| 9788 * |
| 9789 * nextPageAction: function(e) { |
| 9790 * // invoke the superclass version of `nextPageAction` |
| 9791 * this.super(arguments); |
| 9792 * } |
| 9793 * |
| 9794 * To pass custom arguments, arrange them in an array. |
| 9795 * |
| 9796 * appendSerialNo: function(value, serial) { |
| 9797 * // prefix the superclass serial number with our lot # before |
| 9798 * // invoking the superlcass |
| 9799 * return this.super([value, this.lotNo + serial]) |
| 9800 * } |
| 9801 * |
| 9802 * @method super |
| 9803 * @type Any |
| 9804 * @param {args) An array of arguments to use when calling the superclass me
thod, or null. |
| 9805 */ |
| 9806 super: Polymer.super, |
| 9807 |
| 9808 /** |
| 9809 * Lifecycle method called when the element is instantiated. |
| 9810 * |
| 9811 * Override `created` to perform custom create-time tasks. No need to call |
| 9812 * super-class `created` unless you are extending another Polymer element. |
| 9813 * Created is called before the element creates `shadowRoot` or prepares |
| 9814 * data-observation. |
| 9815 * |
| 9816 * @method created |
| 9817 * @type void |
| 9818 */ |
| 9819 created: function() { |
| 9820 }, |
| 9821 |
| 9822 /** |
| 9823 * Lifecycle method called when the element has populated it's `shadowRoot`, |
| 9824 * prepared data-observation, and made itself ready for API interaction. |
| 9825 * |
| 9826 * @method ready |
| 9827 * @type void |
| 9828 */ |
| 9829 ready: function() { |
| 9830 }, |
| 9831 |
| 9832 /** |
| 9833 * Low-level lifecycle method called as part of standard Custom Elements |
| 9834 * operation. Polymer implements this method to provide basic default |
| 9835 * functionality. For custom create-time tasks, implement `created` |
| 9836 * instead, which is called immediately after `createdCallback`. |
| 9837 * |
| 9838 * @method createdCallback |
| 9839 */ |
| 9840 createdCallback: function() { |
| 9841 if (this.templateInstance && this.templateInstance.model) { |
| 9842 console.warn('Attributes on ' + this.localName + ' were data bound ' + |
| 9843 'prior to Polymer upgrading the element. This may result in ' + |
| 9844 'incorrect binding types.'); |
| 9845 } |
| 9846 this.created(); |
| 9847 this.prepareElement(); |
| 9848 if (!this.ownerDocument.isStagingDocument) { |
| 9849 this.makeElementReady(); |
| 9850 } |
| 9851 }, |
| 9852 |
| 9853 // system entry point, do not override |
| 9854 prepareElement: function() { |
| 9855 if (this._elementPrepared) { |
| 9856 console.warn('Element already prepared', this.localName); |
| 9857 return; |
| 9858 } |
| 9859 this._elementPrepared = true; |
| 9860 // storage for shadowRoots info |
| 9861 this.shadowRoots = {}; |
| 9862 // install property observers |
| 9863 this.createPropertyObserver(); |
| 9864 this.openPropertyObserver(); |
| 9865 // install boilerplate attributes |
| 9866 this.copyInstanceAttributes(); |
| 9867 // process input attributes |
| 9868 this.takeAttributes(); |
| 9869 // add event listeners |
| 9870 this.addHostListeners(); |
| 9871 }, |
| 9872 |
| 9873 // system entry point, do not override |
| 9874 makeElementReady: function() { |
| 9875 if (this._readied) { |
| 9876 return; |
| 9877 } |
| 9878 this._readied = true; |
| 9879 this.createComputedProperties(); |
| 9880 this.parseDeclarations(this.__proto__); |
| 9881 // NOTE: Support use of the `unresolved` attribute to help polyfill |
| 9882 // custom elements' `:unresolved` feature. |
| 9883 this.removeAttribute('unresolved'); |
| 9884 // user entry point |
| 9885 this.ready(); |
| 9886 }, |
| 9887 |
| 9888 /** |
| 9889 * Low-level lifecycle method called as part of standard Custom Elements |
| 9890 * operation. Polymer implements this method to provide basic default |
| 9891 * functionality. For custom tasks in your element, implement `attributeChan
ged` |
| 9892 * instead, which is called immediately after `attributeChangedCallback`. |
| 9893 * |
| 9894 * @method attributeChangedCallback |
| 9895 */ |
| 9896 attributeChangedCallback: function(name, oldValue) { |
| 9897 // TODO(sjmiles): adhoc filter |
| 9898 if (name !== 'class' && name !== 'style') { |
| 9899 this.attributeToProperty(name, this.getAttribute(name)); |
| 9900 } |
| 9901 if (this.attributeChanged) { |
| 9902 this.attributeChanged.apply(this, arguments); |
| 9903 } |
| 9904 }, |
| 9905 |
| 9906 /** |
| 9907 * Low-level lifecycle method called as part of standard Custom Elements |
| 9908 * operation. Polymer implements this method to provide basic default |
| 9909 * functionality. For custom create-time tasks, implement `attached` |
| 9910 * instead, which is called immediately after `attachedCallback`. |
| 9911 * |
| 9912 * @method attachedCallback |
| 9913 */ |
| 9914 attachedCallback: function() { |
| 9915 // when the element is attached, prevent it from unbinding. |
| 9916 this.cancelUnbindAll(); |
| 9917 // invoke user action |
| 9918 if (this.attached) { |
| 9919 this.attached(); |
| 9920 } |
| 9921 if (!this.hasBeenAttached) { |
| 9922 this.hasBeenAttached = true; |
| 9923 if (this.domReady) { |
| 9924 this.async('domReady'); |
| 9925 } |
| 9926 } |
| 9927 }, |
| 9928 |
| 9929 /** |
| 9930 * Implement to access custom elements in dom descendants, ancestors, |
| 9931 * or siblings. Because custom elements upgrade in document order, |
| 9932 * elements accessed in `ready` or `attached` may not be upgraded. When |
| 9933 * `domReady` is called, all registered custom elements are guaranteed |
| 9934 * to have been upgraded. |
| 9935 * |
| 9936 * @method domReady |
| 9937 */ |
| 9938 |
| 9939 /** |
| 9940 * Low-level lifecycle method called as part of standard Custom Elements |
| 9941 * operation. Polymer implements this method to provide basic default |
| 9942 * functionality. For custom create-time tasks, implement `detached` |
| 9943 * instead, which is called immediately after `detachedCallback`. |
| 9944 * |
| 9945 * @method detachedCallback |
| 9946 */ |
| 9947 detachedCallback: function() { |
| 9948 if (!this.preventDispose) { |
| 9949 this.asyncUnbindAll(); |
| 9950 } |
| 9951 // invoke user action |
| 9952 if (this.detached) { |
| 9953 this.detached(); |
| 9954 } |
| 9955 // TODO(sorvell): bc |
| 9956 if (this.leftView) { |
| 9957 this.leftView(); |
| 9958 } |
| 9959 }, |
| 9960 |
| 9961 /** |
| 9962 * Walks the prototype-chain of this element and allows specific |
| 9963 * classes a chance to process static declarations. |
| 9964 * |
| 9965 * In particular, each polymer-element has it's own `template`. |
| 9966 * `parseDeclarations` is used to accumulate all element `template`s |
| 9967 * from an inheritance chain. |
| 9968 * |
| 9969 * `parseDeclaration` static methods implemented in the chain are called |
| 9970 * recursively, oldest first, with the `<polymer-element>` associated |
| 9971 * with the current prototype passed as an argument. |
| 9972 * |
| 9973 * An element may override this method to customize shadow-root generation. |
| 9974 * |
| 9975 * @method parseDeclarations |
| 9976 */ |
| 9977 parseDeclarations: function(p) { |
| 9978 if (p && p.element) { |
| 9979 this.parseDeclarations(p.__proto__); |
| 9980 p.parseDeclaration.call(this, p.element); |
| 9981 } |
| 9982 }, |
| 9983 |
| 9984 /** |
| 9985 * Perform init-time actions based on static information in the |
| 9986 * `<polymer-element>` instance argument. |
| 9987 * |
| 9988 * For example, the standard implementation locates the template associated |
| 9989 * with the given `<polymer-element>` and stamps it into a shadow-root to |
| 9990 * implement shadow inheritance. |
| 9991 * |
| 9992 * An element may override this method for custom behavior. |
| 9993 * |
| 9994 * @method parseDeclaration |
| 9995 */ |
| 9996 parseDeclaration: function(elementElement) { |
| 9997 var template = this.fetchTemplate(elementElement); |
| 9998 if (template) { |
| 9999 var root = this.shadowFromTemplate(template); |
| 10000 this.shadowRoots[elementElement.name] = root; |
| 10001 } |
| 10002 }, |
| 10003 |
| 10004 /** |
| 10005 * Given a `<polymer-element>`, find an associated template (if any) to be |
| 10006 * used for shadow-root generation. |
| 10007 * |
| 10008 * An element may override this method for custom behavior. |
| 10009 * |
| 10010 * @method fetchTemplate |
| 10011 */ |
| 10012 fetchTemplate: function(elementElement) { |
| 10013 return elementElement.querySelector('template'); |
| 10014 }, |
| 10015 |
| 10016 /** |
| 10017 * Create a shadow-root in this host and stamp `template` as it's |
| 10018 * content. |
| 10019 * |
| 10020 * An element may override this method for custom behavior. |
| 10021 * |
| 10022 * @method shadowFromTemplate |
| 10023 */ |
| 10024 shadowFromTemplate: function(template) { |
| 10025 if (template) { |
| 10026 // make a shadow root |
| 10027 var root = this.createShadowRoot(); |
| 10028 // stamp template |
| 10029 // which includes parsing and applying MDV bindings before being |
| 10030 // inserted (to avoid {{}} in attribute values) |
| 10031 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
| 10032 var dom = this.instanceTemplate(template); |
| 10033 // append to shadow dom |
| 10034 root.appendChild(dom); |
| 10035 // perform post-construction initialization tasks on shadow root |
| 10036 this.shadowRootReady(root, template); |
| 10037 // return the created shadow root |
| 10038 return root; |
| 10039 } |
| 10040 }, |
| 10041 |
| 10042 // utility function that stamps a <template> into light-dom |
| 10043 lightFromTemplate: function(template, refNode) { |
| 10044 if (template) { |
| 10045 // TODO(sorvell): mark this element as an eventController so that |
| 10046 // event listeners on bound nodes inside it will be called on it. |
| 10047 // Note, the expectation here is that events on all descendants |
| 10048 // should be handled by this element. |
| 10049 this.eventController = this; |
| 10050 // stamp template |
| 10051 // which includes parsing and applying MDV bindings before being |
| 10052 // inserted (to avoid {{}} in attribute values) |
| 10053 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. |
| 10054 var dom = this.instanceTemplate(template); |
| 10055 // append to shadow dom |
| 10056 if (refNode) { |
| 10057 this.insertBefore(dom, refNode); |
| 10058 } else { |
| 10059 this.appendChild(dom); |
| 10060 } |
| 10061 // perform post-construction initialization tasks on ahem, light root |
| 10062 this.shadowRootReady(this); |
| 10063 // return the created shadow root |
| 10064 return dom; |
| 10065 } |
| 10066 }, |
| 10067 |
| 10068 shadowRootReady: function(root) { |
| 10069 // locate nodes with id and store references to them in this.$ hash |
| 10070 this.marshalNodeReferences(root); |
| 10071 }, |
| 10072 |
| 10073 // locate nodes with id and store references to them in this.$ hash |
| 10074 marshalNodeReferences: function(root) { |
| 10075 // establish $ instance variable |
| 10076 var $ = this.$ = this.$ || {}; |
| 10077 // populate $ from nodes with ID from the LOCAL tree |
| 10078 if (root) { |
| 10079 var n$ = root.querySelectorAll("[id]"); |
| 10080 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { |
| 10081 $[n.id] = n; |
| 10082 }; |
| 10083 } |
| 10084 }, |
| 10085 |
| 10086 /** |
| 10087 * Register a one-time callback when a child-list or sub-tree mutation |
| 10088 * occurs on node. |
| 10089 * |
| 10090 * For persistent callbacks, call onMutation from your listener. |
| 10091 * |
| 10092 * @method onMutation |
| 10093 * @param Node {Node} node Node to watch for mutations. |
| 10094 * @param Function {Function} listener Function to call on mutation. The fun
ction is invoked as `listener.call(this, observer, mutations);` where `observer`
is the MutationObserver that triggered the notification, and `mutations` is the
native mutation list. |
| 10095 */ |
| 10096 onMutation: function(node, listener) { |
| 10097 var observer = new MutationObserver(function(mutations) { |
| 10098 listener.call(this, observer, mutations); |
| 10099 observer.disconnect(); |
| 10100 }.bind(this)); |
| 10101 observer.observe(node, {childList: true, subtree: true}); |
| 10102 } |
| 10103 }; |
| 10104 |
| 10105 /** |
| 10106 * @class Polymer |
| 10107 */ |
| 10108 |
| 10109 /** |
| 10110 * Returns true if the object includes <a href="#polymer-base">polymer-base</a
> in it's prototype chain. |
| 10111 * |
| 10112 * @method isBase |
| 10113 * @param Object {Object} object Object to test. |
| 10114 * @type Boolean |
| 10115 */ |
| 10116 function isBase(object) { |
| 10117 return object.hasOwnProperty('PolymerBase') |
| 10118 } |
| 10119 |
| 10120 // name a base constructor for dev tools |
| 10121 |
| 10122 /** |
| 10123 * The Polymer base-class constructor. |
| 10124 * |
| 10125 * @property Base |
| 10126 * @type Function |
| 10127 */ |
| 10128 function PolymerBase() {}; |
| 10129 PolymerBase.prototype = base; |
| 10130 base.constructor = PolymerBase; |
| 10131 |
| 10132 // exports |
| 10133 |
| 10134 scope.Base = PolymerBase; |
| 10135 scope.isBase = isBase; |
| 10136 scope.api.instance.base = base; |
| 10137 |
| 10138 })(Polymer); |
| 10139 |
| 10140 (function(scope) { |
| 10141 |
| 10142 // imports |
| 10143 |
| 10144 var log = window.WebComponents ? WebComponents.flags.log : {}; |
| 10145 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; |
| 10146 |
| 10147 // magic words |
| 10148 |
| 10149 var STYLE_SCOPE_ATTRIBUTE = 'element'; |
| 10150 var STYLE_CONTROLLER_SCOPE = 'controller'; |
| 10151 |
| 10152 var styles = { |
| 10153 STYLE_SCOPE_ATTRIBUTE: STYLE_SCOPE_ATTRIBUTE, |
| 10154 /** |
| 10155 * Installs external stylesheets and <style> elements with the attribute |
| 10156 * polymer-scope='controller' into the scope of element. This is intended |
| 10157 * to be a called during custom element construction. |
| 10158 */ |
| 10159 installControllerStyles: function() { |
| 10160 // apply controller styles, but only if they are not yet applied |
| 10161 var scope = this.findStyleScope(); |
| 10162 if (scope && !this.scopeHasNamedStyle(scope, this.localName)) { |
| 10163 // allow inherited controller styles |
| 10164 var proto = getPrototypeOf(this), cssText = ''; |
| 10165 while (proto && proto.element) { |
| 10166 cssText += proto.element.cssTextForScope(STYLE_CONTROLLER_SCOPE); |
| 10167 proto = getPrototypeOf(proto); |
| 10168 } |
| 10169 if (cssText) { |
| 10170 this.installScopeCssText(cssText, scope); |
| 10171 } |
| 10172 } |
| 10173 }, |
| 10174 installScopeStyle: function(style, name, scope) { |
| 10175 var scope = scope || this.findStyleScope(), name = name || ''; |
| 10176 if (scope && !this.scopeHasNamedStyle(scope, this.localName + name)) { |
| 10177 var cssText = ''; |
| 10178 if (style instanceof Array) { |
| 10179 for (var i=0, l=style.length, s; (i<l) && (s=style[i]); i++) { |
| 10180 cssText += s.textContent + '\n\n'; |
| 10181 } |
| 10182 } else { |
| 10183 cssText = style.textContent; |
| 10184 } |
| 10185 this.installScopeCssText(cssText, scope, name); |
| 10186 } |
| 10187 }, |
| 10188 installScopeCssText: function(cssText, scope, name) { |
| 10189 scope = scope || this.findStyleScope(); |
| 10190 name = name || ''; |
| 10191 if (!scope) { |
| 10192 return; |
| 10193 } |
| 10194 if (hasShadowDOMPolyfill) { |
| 10195 cssText = shimCssText(cssText, scope.host); |
| 10196 } |
| 10197 var style = this.element.cssTextToScopeStyle(cssText, |
| 10198 STYLE_CONTROLLER_SCOPE); |
| 10199 Polymer.applyStyleToScope(style, scope); |
| 10200 // cache that this style has been applied |
| 10201 this.styleCacheForScope(scope)[this.localName + name] = true; |
| 10202 }, |
| 10203 findStyleScope: function(node) { |
| 10204 // find the shadow root that contains this element |
| 10205 var n = node || this; |
| 10206 while (n.parentNode) { |
| 10207 n = n.parentNode; |
| 10208 } |
| 10209 return n; |
| 10210 }, |
| 10211 scopeHasNamedStyle: function(scope, name) { |
| 10212 var cache = this.styleCacheForScope(scope); |
| 10213 return cache[name]; |
| 10214 }, |
| 10215 styleCacheForScope: function(scope) { |
| 10216 if (hasShadowDOMPolyfill) { |
| 10217 var scopeName = scope.host ? scope.host.localName : scope.localName; |
| 10218 return polyfillScopeStyleCache[scopeName] || (polyfillScopeStyleCache[sc
opeName] = {}); |
| 10219 } else { |
| 10220 return scope._scopeStyles = (scope._scopeStyles || {}); |
| 10221 } |
| 10222 } |
| 10223 }; |
| 10224 |
| 10225 var polyfillScopeStyleCache = {}; |
| 10226 |
| 10227 // NOTE: use raw prototype traversal so that we ensure correct traversal |
| 10228 // on platforms where the protoype chain is simulated via __proto__ (IE10) |
| 10229 function getPrototypeOf(prototype) { |
| 10230 return prototype.__proto__; |
| 10231 } |
| 10232 |
| 10233 function shimCssText(cssText, host) { |
| 10234 var name = '', is = false; |
| 10235 if (host) { |
| 10236 name = host.localName; |
| 10237 is = host.hasAttribute('is'); |
| 10238 } |
| 10239 var selector = WebComponents.ShadowCSS.makeScopeSelector(name, is); |
| 10240 return WebComponents.ShadowCSS.shimCssText(cssText, selector); |
| 10241 } |
| 10242 |
| 10243 // exports |
| 10244 |
| 10245 scope.api.instance.styles = styles; |
| 10246 |
| 10247 })(Polymer); |
| 10248 |
| 10249 (function(scope) { |
| 10250 |
| 10251 // imports |
| 10252 |
| 10253 var extend = scope.extend; |
| 10254 var api = scope.api; |
| 10255 |
| 10256 // imperative implementation: Polymer() |
| 10257 |
| 10258 // specify an 'own' prototype for tag `name` |
| 10259 function element(name, prototype) { |
| 10260 if (typeof name !== 'string') { |
| 10261 var script = prototype || document._currentScript; |
| 10262 prototype = name; |
| 10263 name = script && script.parentNode && script.parentNode.getAttribute ? |
| 10264 script.parentNode.getAttribute('name') : ''; |
| 10265 if (!name) { |
| 10266 throw 'Element name could not be inferred.'; |
| 10267 } |
| 10268 } |
| 10269 if (getRegisteredPrototype(name)) { |
| 10270 throw 'Already registered (Polymer) prototype for element ' + name; |
| 10271 } |
| 10272 // cache the prototype |
| 10273 registerPrototype(name, prototype); |
| 10274 // notify the registrar waiting for 'name', if any |
| 10275 notifyPrototype(name); |
| 10276 } |
| 10277 |
| 10278 // async prototype source |
| 10279 |
| 10280 function waitingForPrototype(name, client) { |
| 10281 waitPrototype[name] = client; |
| 10282 } |
| 10283 |
| 10284 var waitPrototype = {}; |
| 10285 |
| 10286 function notifyPrototype(name) { |
| 10287 if (waitPrototype[name]) { |
| 10288 waitPrototype[name].registerWhenReady(); |
| 10289 delete waitPrototype[name]; |
| 10290 } |
| 10291 } |
| 10292 |
| 10293 // utility and bookkeeping |
| 10294 |
| 10295 // maps tag names to prototypes, as registered with |
| 10296 // Polymer. Prototypes associated with a tag name |
| 10297 // using document.registerElement are available from |
| 10298 // HTMLElement.getPrototypeForTag(). |
| 10299 // If an element was fully registered by Polymer, then |
| 10300 // Polymer.getRegisteredPrototype(name) === |
| 10301 // HTMLElement.getPrototypeForTag(name) |
| 10302 |
| 10303 var prototypesByName = {}; |
| 10304 |
| 10305 function registerPrototype(name, prototype) { |
| 10306 return prototypesByName[name] = prototype || {}; |
| 10307 } |
| 10308 |
| 10309 function getRegisteredPrototype(name) { |
| 10310 return prototypesByName[name]; |
| 10311 } |
| 10312 |
| 10313 function instanceOfType(element, type) { |
| 10314 if (typeof type !== 'string') { |
| 10315 return false; |
| 10316 } |
| 10317 var proto = HTMLElement.getPrototypeForTag(type); |
| 10318 var ctor = proto && proto.constructor; |
| 10319 if (!ctor) { |
| 10320 return false; |
| 10321 } |
| 10322 if (CustomElements.instanceof) { |
| 10323 return CustomElements.instanceof(element, ctor); |
| 10324 } |
| 10325 return element instanceof ctor; |
| 10326 } |
| 10327 |
| 10328 // exports |
| 10329 |
| 10330 scope.getRegisteredPrototype = getRegisteredPrototype; |
| 10331 scope.waitingForPrototype = waitingForPrototype; |
| 10332 scope.instanceOfType = instanceOfType; |
| 10333 |
| 10334 // namespace shenanigans so we can expose our scope on the registration |
| 10335 // function |
| 10336 |
| 10337 // make window.Polymer reference `element()` |
| 10338 |
| 10339 window.Polymer = element; |
| 10340 |
| 10341 // TODO(sjmiles): find a way to do this that is less terrible |
| 10342 // copy window.Polymer properties onto `element()` |
| 10343 |
| 10344 extend(Polymer, scope); |
| 10345 |
| 10346 // Under the HTMLImports polyfill, scripts in the main document |
| 10347 // do not block on imports; we want to allow calls to Polymer in the main |
| 10348 // document. WebComponents collects those calls until we can process them, whi
ch |
| 10349 // we do here. |
| 10350 |
| 10351 if (WebComponents.consumeDeclarations) { |
| 10352 WebComponents.consumeDeclarations(function(declarations) { |
| 10353 if (declarations) { |
| 10354 for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i+
+) { |
| 10355 element.apply(null, d); |
| 10356 } |
| 10357 } |
| 10358 }); |
| 10359 } |
| 10360 |
| 10361 })(Polymer); |
| 10362 |
| 10363 (function(scope) { |
| 10364 |
| 10365 /** |
| 10366 * @class polymer-base |
| 10367 */ |
| 10368 |
| 10369 /** |
| 10370 * Resolve a url path to be relative to a `base` url. If unspecified, `base` |
| 10371 * defaults to the element's ownerDocument url. Can be used to resolve |
| 10372 * paths from element's in templates loaded in HTMLImports to be relative |
| 10373 * to the document containing the element. Polymer automatically does this for |
| 10374 * url attributes in element templates; however, if a url, for |
| 10375 * example, contains a binding, then `resolvePath` can be used to ensure it is |
| 10376 * relative to the element document. For example, in an element's template, |
| 10377 * |
| 10378 * <a href="{{resolvePath(path)}}">Resolved</a> |
| 10379 * |
| 10380 * @method resolvePath |
| 10381 * @param {String} url Url path to resolve. |
| 10382 * @param {String} base Optional base url against which to resolve, defaults |
| 10383 * to the element's ownerDocument url. |
| 10384 * returns {String} resolved url. |
| 10385 */ |
| 10386 |
| 10387 var path = { |
| 10388 resolveElementPaths: function(node) { |
| 10389 Polymer.urlResolver.resolveDom(node); |
| 10390 }, |
| 10391 addResolvePathApi: function() { |
| 10392 // let assetpath attribute modify the resolve path |
| 10393 var assetPath = this.getAttribute('assetpath') || ''; |
| 10394 var root = new URL(assetPath, this.ownerDocument.baseURI); |
| 10395 this.prototype.resolvePath = function(urlPath, base) { |
| 10396 var u = new URL(urlPath, base || root); |
| 10397 return u.href; |
| 10398 }; |
| 10399 } |
| 10400 }; |
| 10401 |
| 10402 // exports |
| 10403 scope.api.declaration.path = path; |
| 10404 |
| 10405 })(Polymer); |
| 10406 |
| 10407 (function(scope) { |
| 10408 |
| 10409 // imports |
| 10410 |
| 10411 var log = window.WebComponents ? WebComponents.flags.log : {}; |
| 10412 var api = scope.api.instance.styles; |
| 10413 var STYLE_SCOPE_ATTRIBUTE = api.STYLE_SCOPE_ATTRIBUTE; |
| 10414 |
| 10415 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; |
| 10416 |
| 10417 // magic words |
| 10418 |
| 10419 var STYLE_SELECTOR = 'style'; |
| 10420 var STYLE_LOADABLE_MATCH = '@import'; |
| 10421 var SHEET_SELECTOR = 'link[rel=stylesheet]'; |
| 10422 var STYLE_GLOBAL_SCOPE = 'global'; |
| 10423 var SCOPE_ATTR = 'polymer-scope'; |
| 10424 |
| 10425 var styles = { |
| 10426 // returns true if resources are loading |
| 10427 loadStyles: function(callback) { |
| 10428 var template = this.fetchTemplate(); |
| 10429 var content = template && this.templateContent(); |
| 10430 if (content) { |
| 10431 this.convertSheetsToStyles(content); |
| 10432 var styles = this.findLoadableStyles(content); |
| 10433 if (styles.length) { |
| 10434 var templateUrl = template.ownerDocument.baseURI; |
| 10435 return Polymer.styleResolver.loadStyles(styles, templateUrl, callback)
; |
| 10436 } |
| 10437 } |
| 10438 if (callback) { |
| 10439 callback(); |
| 10440 } |
| 10441 }, |
| 10442 convertSheetsToStyles: function(root) { |
| 10443 var s$ = root.querySelectorAll(SHEET_SELECTOR); |
| 10444 for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) { |
| 10445 c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI)
, |
| 10446 this.ownerDocument); |
| 10447 this.copySheetAttributes(c, s); |
| 10448 s.parentNode.replaceChild(c, s); |
| 10449 } |
| 10450 }, |
| 10451 copySheetAttributes: function(style, link) { |
| 10452 for (var i=0, a$=link.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) { |
| 10453 if (a.name !== 'rel' && a.name !== 'href') { |
| 10454 style.setAttribute(a.name, a.value); |
| 10455 } |
| 10456 } |
| 10457 }, |
| 10458 findLoadableStyles: function(root) { |
| 10459 var loadables = []; |
| 10460 if (root) { |
| 10461 var s$ = root.querySelectorAll(STYLE_SELECTOR); |
| 10462 for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) { |
| 10463 if (s.textContent.match(STYLE_LOADABLE_MATCH)) { |
| 10464 loadables.push(s); |
| 10465 } |
| 10466 } |
| 10467 } |
| 10468 return loadables; |
| 10469 }, |
| 10470 /** |
| 10471 * Install external stylesheets loaded in <polymer-element> elements into th
e |
| 10472 * element's template. |
| 10473 * @param elementElement The <element> element to style. |
| 10474 */ |
| 10475 installSheets: function() { |
| 10476 this.cacheSheets(); |
| 10477 this.cacheStyles(); |
| 10478 this.installLocalSheets(); |
| 10479 this.installGlobalStyles(); |
| 10480 }, |
| 10481 /** |
| 10482 * Remove all sheets from element and store for later use. |
| 10483 */ |
| 10484 cacheSheets: function() { |
| 10485 this.sheets = this.findNodes(SHEET_SELECTOR); |
| 10486 this.sheets.forEach(function(s) { |
| 10487 if (s.parentNode) { |
| 10488 s.parentNode.removeChild(s); |
| 10489 } |
| 10490 }); |
| 10491 }, |
| 10492 cacheStyles: function() { |
| 10493 this.styles = this.findNodes(STYLE_SELECTOR + '[' + SCOPE_ATTR + ']'); |
| 10494 this.styles.forEach(function(s) { |
| 10495 if (s.parentNode) { |
| 10496 s.parentNode.removeChild(s); |
| 10497 } |
| 10498 }); |
| 10499 }, |
| 10500 /** |
| 10501 * Takes external stylesheets loaded in an <element> element and moves |
| 10502 * their content into a <style> element inside the <element>'s template. |
| 10503 * The sheet is then removed from the <element>. This is done only so |
| 10504 * that if the element is loaded in the main document, the sheet does |
| 10505 * not become active. |
| 10506 * Note, ignores sheets with the attribute 'polymer-scope'. |
| 10507 * @param elementElement The <element> element to style. |
| 10508 */ |
| 10509 installLocalSheets: function () { |
| 10510 var sheets = this.sheets.filter(function(s) { |
| 10511 return !s.hasAttribute(SCOPE_ATTR); |
| 10512 }); |
| 10513 var content = this.templateContent(); |
| 10514 if (content) { |
| 10515 var cssText = ''; |
| 10516 sheets.forEach(function(sheet) { |
| 10517 cssText += cssTextFromSheet(sheet) + '\n'; |
| 10518 }); |
| 10519 if (cssText) { |
| 10520 var style = createStyleElement(cssText, this.ownerDocument); |
| 10521 content.insertBefore(style, content.firstChild); |
| 10522 } |
| 10523 } |
| 10524 }, |
| 10525 findNodes: function(selector, matcher) { |
| 10526 var nodes = this.querySelectorAll(selector).array(); |
| 10527 var content = this.templateContent(); |
| 10528 if (content) { |
| 10529 var templateNodes = content.querySelectorAll(selector).array(); |
| 10530 nodes = nodes.concat(templateNodes); |
| 10531 } |
| 10532 return matcher ? nodes.filter(matcher) : nodes; |
| 10533 }, |
| 10534 /** |
| 10535 * Promotes external stylesheets and <style> elements with the attribute |
| 10536 * polymer-scope='global' into global scope. |
| 10537 * This is particularly useful for defining @keyframe rules which |
| 10538 * currently do not function in scoped or shadow style elements. |
| 10539 * (See wkb.ug/72462) |
| 10540 * @param elementElement The <element> element to style. |
| 10541 */ |
| 10542 // TODO(sorvell): remove when wkb.ug/72462 is addressed. |
| 10543 installGlobalStyles: function() { |
| 10544 var style = this.styleForScope(STYLE_GLOBAL_SCOPE); |
| 10545 applyStyleToScope(style, document.head); |
| 10546 }, |
| 10547 cssTextForScope: function(scopeDescriptor) { |
| 10548 var cssText = ''; |
| 10549 // handle stylesheets |
| 10550 var selector = '[' + SCOPE_ATTR + '=' + scopeDescriptor + ']'; |
| 10551 var matcher = function(s) { |
| 10552 return matchesSelector(s, selector); |
| 10553 }; |
| 10554 var sheets = this.sheets.filter(matcher); |
| 10555 sheets.forEach(function(sheet) { |
| 10556 cssText += cssTextFromSheet(sheet) + '\n\n'; |
| 10557 }); |
| 10558 // handle cached style elements |
| 10559 var styles = this.styles.filter(matcher); |
| 10560 styles.forEach(function(style) { |
| 10561 cssText += style.textContent + '\n\n'; |
| 10562 }); |
| 10563 return cssText; |
| 10564 }, |
| 10565 styleForScope: function(scopeDescriptor) { |
| 10566 var cssText = this.cssTextForScope(scopeDescriptor); |
| 10567 return this.cssTextToScopeStyle(cssText, scopeDescriptor); |
| 10568 }, |
| 10569 cssTextToScopeStyle: function(cssText, scopeDescriptor) { |
| 10570 if (cssText) { |
| 10571 var style = createStyleElement(cssText); |
| 10572 style.setAttribute(STYLE_SCOPE_ATTRIBUTE, this.getAttribute('name') + |
| 10573 '-' + scopeDescriptor); |
| 10574 return style; |
| 10575 } |
| 10576 } |
| 10577 }; |
| 10578 |
| 10579 function importRuleForSheet(sheet, baseUrl) { |
| 10580 var href = new URL(sheet.getAttribute('href'), baseUrl).href; |
| 10581 return '@import \'' + href + '\';'; |
| 10582 } |
| 10583 |
| 10584 function applyStyleToScope(style, scope) { |
| 10585 if (style) { |
| 10586 if (scope === document) { |
| 10587 scope = document.head; |
| 10588 } |
| 10589 if (hasShadowDOMPolyfill) { |
| 10590 scope = document.head; |
| 10591 } |
| 10592 // TODO(sorvell): necessary for IE |
| 10593 // see https://connect.microsoft.com/IE/feedback/details/790212/ |
| 10594 // cloning-a-style-element-and-adding-to-document-produces |
| 10595 // -unexpected-result#details |
| 10596 // var clone = style.cloneNode(true); |
| 10597 var clone = createStyleElement(style.textContent); |
| 10598 var attr = style.getAttribute(STYLE_SCOPE_ATTRIBUTE); |
| 10599 if (attr) { |
| 10600 clone.setAttribute(STYLE_SCOPE_ATTRIBUTE, attr); |
| 10601 } |
| 10602 // TODO(sorvell): probably too brittle; try to figure out |
| 10603 // where to put the element. |
| 10604 var refNode = scope.firstElementChild; |
| 10605 if (scope === document.head) { |
| 10606 var selector = 'style[' + STYLE_SCOPE_ATTRIBUTE + ']'; |
| 10607 var s$ = document.head.querySelectorAll(selector); |
| 10608 if (s$.length) { |
| 10609 refNode = s$[s$.length-1].nextElementSibling; |
| 10610 } |
| 10611 } |
| 10612 scope.insertBefore(clone, refNode); |
| 10613 } |
| 10614 } |
| 10615 |
| 10616 function createStyleElement(cssText, scope) { |
| 10617 scope = scope || document; |
| 10618 scope = scope.createElement ? scope : scope.ownerDocument; |
| 10619 var style = scope.createElement('style'); |
| 10620 style.textContent = cssText; |
| 10621 return style; |
| 10622 } |
| 10623 |
| 10624 function cssTextFromSheet(sheet) { |
| 10625 return (sheet && sheet.__resource) || ''; |
| 10626 } |
| 10627 |
| 10628 function matchesSelector(node, inSelector) { |
| 10629 if (matches) { |
| 10630 return matches.call(node, inSelector); |
| 10631 } |
| 10632 } |
| 10633 var p = HTMLElement.prototype; |
| 10634 var matches = p.matches || p.matchesSelector || p.webkitMatchesSelector |
| 10635 || p.mozMatchesSelector; |
| 10636 |
| 10637 // exports |
| 10638 |
| 10639 scope.api.declaration.styles = styles; |
| 10640 scope.applyStyleToScope = applyStyleToScope; |
| 10641 |
| 10642 })(Polymer); |
| 10643 |
| 10644 (function(scope) { |
| 10645 |
| 10646 // imports |
| 10647 |
| 10648 var log = window.WebComponents ? WebComponents.flags.log : {}; |
| 10649 var api = scope.api.instance.events; |
| 10650 var EVENT_PREFIX = api.EVENT_PREFIX; |
| 10651 |
| 10652 var mixedCaseEventTypes = {}; |
| 10653 [ |
| 10654 'webkitAnimationStart', |
| 10655 'webkitAnimationEnd', |
| 10656 'webkitTransitionEnd', |
| 10657 'DOMFocusOut', |
| 10658 'DOMFocusIn', |
| 10659 'DOMMouseScroll' |
| 10660 ].forEach(function(e) { |
| 10661 mixedCaseEventTypes[e.toLowerCase()] = e; |
| 10662 }); |
| 10663 |
| 10664 // polymer-element declarative api: events feature |
| 10665 var events = { |
| 10666 parseHostEvents: function() { |
| 10667 // our delegates map |
| 10668 var delegates = this.prototype.eventDelegates; |
| 10669 // extract data from attributes into delegates |
| 10670 this.addAttributeDelegates(delegates); |
| 10671 }, |
| 10672 addAttributeDelegates: function(delegates) { |
| 10673 // for each attribute |
| 10674 for (var i=0, a; a=this.attributes[i]; i++) { |
| 10675 // does it have magic marker identifying it as an event delegate? |
| 10676 if (this.hasEventPrefix(a.name)) { |
| 10677 // if so, add the info to delegates |
| 10678 delegates[this.removeEventPrefix(a.name)] = a.value.replace('{{', '') |
| 10679 .replace('}}', '').trim(); |
| 10680 } |
| 10681 } |
| 10682 }, |
| 10683 // starts with 'on-' |
| 10684 hasEventPrefix: function (n) { |
| 10685 return n && (n[0] === 'o') && (n[1] === 'n') && (n[2] === '-'); |
| 10686 }, |
| 10687 removeEventPrefix: function(n) { |
| 10688 return n.slice(prefixLength); |
| 10689 }, |
| 10690 findController: function(node) { |
| 10691 while (node.parentNode) { |
| 10692 if (node.eventController) { |
| 10693 return node.eventController; |
| 10694 } |
| 10695 node = node.parentNode; |
| 10696 } |
| 10697 return node.host; |
| 10698 }, |
| 10699 getEventHandler: function(controller, target, method) { |
| 10700 var events = this; |
| 10701 return function(e) { |
| 10702 if (!controller || !controller.PolymerBase) { |
| 10703 controller = events.findController(target); |
| 10704 } |
| 10705 |
| 10706 var args = [e, e.detail, e.currentTarget]; |
| 10707 controller.dispatchMethod(controller, method, args); |
| 10708 }; |
| 10709 }, |
| 10710 prepareEventBinding: function(pathString, name, node) { |
| 10711 if (!this.hasEventPrefix(name)) |
| 10712 return; |
| 10713 |
| 10714 var eventType = this.removeEventPrefix(name); |
| 10715 eventType = mixedCaseEventTypes[eventType] || eventType; |
| 10716 |
| 10717 var events = this; |
| 10718 |
| 10719 return function(model, node, oneTime) { |
| 10720 var handler = events.getEventHandler(undefined, node, pathString); |
| 10721 PolymerGestures.addEventListener(node, eventType, handler); |
| 10722 |
| 10723 if (oneTime) |
| 10724 return; |
| 10725 |
| 10726 // TODO(rafaelw): This is really pointless work. Aside from the cost |
| 10727 // of these allocations, NodeBind is going to setAttribute back to its |
| 10728 // current value. Fixing this would mean changing the TemplateBinding |
| 10729 // binding delegate API. |
| 10730 function bindingValue() { |
| 10731 return '{{ ' + pathString + ' }}'; |
| 10732 } |
| 10733 |
| 10734 return { |
| 10735 open: bindingValue, |
| 10736 discardChanges: bindingValue, |
| 10737 close: function() { |
| 10738 PolymerGestures.removeEventListener(node, eventType, handler); |
| 10739 } |
| 10740 }; |
| 10741 }; |
| 10742 } |
| 10743 }; |
| 10744 |
| 10745 var prefixLength = EVENT_PREFIX.length; |
| 10746 |
| 10747 // exports |
| 10748 scope.api.declaration.events = events; |
| 10749 |
| 10750 })(Polymer); |
| 10751 |
| 10752 (function(scope) { |
| 10753 |
| 10754 // element api |
| 10755 |
| 10756 var observationBlacklist = ['attribute']; |
| 10757 |
| 10758 var properties = { |
| 10759 inferObservers: function(prototype) { |
| 10760 // called before prototype.observe is chained to inherited object |
| 10761 var observe = prototype.observe, property; |
| 10762 for (var n in prototype) { |
| 10763 if (n.slice(-7) === 'Changed') { |
| 10764 property = n.slice(0, -7); |
| 10765 if (this.canObserveProperty(property)) { |
| 10766 if (!observe) { |
| 10767 observe = (prototype.observe = {}); |
| 10768 } |
| 10769 observe[property] = observe[property] || n; |
| 10770 } |
| 10771 } |
| 10772 } |
| 10773 }, |
| 10774 canObserveProperty: function(property) { |
| 10775 return (observationBlacklist.indexOf(property) < 0); |
| 10776 }, |
| 10777 explodeObservers: function(prototype) { |
| 10778 // called before prototype.observe is chained to inherited object |
| 10779 var o = prototype.observe; |
| 10780 if (o) { |
| 10781 var exploded = {}; |
| 10782 for (var n in o) { |
| 10783 var names = n.split(' '); |
| 10784 for (var i=0, ni; ni=names[i]; i++) { |
| 10785 exploded[ni] = o[n]; |
| 10786 } |
| 10787 } |
| 10788 prototype.observe = exploded; |
| 10789 } |
| 10790 }, |
| 10791 optimizePropertyMaps: function(prototype) { |
| 10792 if (prototype.observe) { |
| 10793 // construct name list |
| 10794 var a = prototype._observeNames = []; |
| 10795 for (var n in prototype.observe) { |
| 10796 var names = n.split(' '); |
| 10797 for (var i=0, ni; ni=names[i]; i++) { |
| 10798 a.push(ni); |
| 10799 } |
| 10800 } |
| 10801 } |
| 10802 if (prototype.publish) { |
| 10803 // construct name list |
| 10804 var a = prototype._publishNames = []; |
| 10805 for (var n in prototype.publish) { |
| 10806 a.push(n); |
| 10807 } |
| 10808 } |
| 10809 if (prototype.computed) { |
| 10810 // construct name list |
| 10811 var a = prototype._computedNames = []; |
| 10812 for (var n in prototype.computed) { |
| 10813 a.push(n); |
| 10814 } |
| 10815 } |
| 10816 }, |
| 10817 publishProperties: function(prototype, base) { |
| 10818 // if we have any properties to publish |
| 10819 var publish = prototype.publish; |
| 10820 if (publish) { |
| 10821 // transcribe `publish` entries onto own prototype |
| 10822 this.requireProperties(publish, prototype, base); |
| 10823 // warn and remove accessor names that are broken on some browsers |
| 10824 this.filterInvalidAccessorNames(publish); |
| 10825 // construct map of lower-cased property names |
| 10826 prototype._publishLC = this.lowerCaseMap(publish); |
| 10827 } |
| 10828 var computed = prototype.computed; |
| 10829 if (computed) { |
| 10830 // warn and remove accessor names that are broken on some browsers |
| 10831 this.filterInvalidAccessorNames(computed); |
| 10832 } |
| 10833 }, |
| 10834 // Publishing/computing a property where the name might conflict with a |
| 10835 // browser property is not currently supported to help users of Polymer |
| 10836 // avoid browser bugs: |
| 10837 // |
| 10838 // https://code.google.com/p/chromium/issues/detail?id=43394 |
| 10839 // https://bugs.webkit.org/show_bug.cgi?id=49739 |
| 10840 // |
| 10841 // We can lift this restriction when those bugs are fixed. |
| 10842 filterInvalidAccessorNames: function(propertyNames) { |
| 10843 for (var name in propertyNames) { |
| 10844 // Check if the name is in our blacklist. |
| 10845 if (this.propertyNameBlacklist[name]) { |
| 10846 console.warn('Cannot define property "' + name + '" for element "' + |
| 10847 this.name + '" because it has the same name as an HTMLElement ' + |
| 10848 'property, and not all browsers support overriding that. ' + |
| 10849 'Consider giving it a different name.'); |
| 10850 // Remove the invalid accessor from the list. |
| 10851 delete propertyNames[name]; |
| 10852 } |
| 10853 } |
| 10854 }, |
| 10855 // |
| 10856 // `name: value` entries in the `publish` object may need to generate |
| 10857 // matching properties on the prototype. |
| 10858 // |
| 10859 // Values that are objects may have a `reflect` property, which |
| 10860 // signals that the value describes property control metadata. |
| 10861 // In metadata objects, the prototype default value (if any) |
| 10862 // is encoded in the `value` property. |
| 10863 // |
| 10864 // publish: { |
| 10865 // foo: 5, |
| 10866 // bar: {value: true, reflect: true}, |
| 10867 // zot: {} |
| 10868 // } |
| 10869 // |
| 10870 // `reflect` metadata property controls whether changes to the property |
| 10871 // are reflected back to the attribute (default false). |
| 10872 // |
| 10873 // A value is stored on the prototype unless it's === `undefined`, |
| 10874 // in which case the base chain is checked for a value. |
| 10875 // If the basal value is also undefined, `null` is stored on the prototype. |
| 10876 // |
| 10877 // The reflection data is stored on another prototype object, `reflect` |
| 10878 // which also can be specified directly. |
| 10879 // |
| 10880 // reflect: { |
| 10881 // foo: true |
| 10882 // } |
| 10883 // |
| 10884 requireProperties: function(propertyInfos, prototype, base) { |
| 10885 // per-prototype storage for reflected properties |
| 10886 prototype.reflect = prototype.reflect || {}; |
| 10887 // ensure a prototype value for each property |
| 10888 // and update the property's reflect to attribute status |
| 10889 for (var n in propertyInfos) { |
| 10890 var value = propertyInfos[n]; |
| 10891 // value has metadata if it has a `reflect` property |
| 10892 if (value && value.reflect !== undefined) { |
| 10893 prototype.reflect[n] = Boolean(value.reflect); |
| 10894 value = value.value; |
| 10895 } |
| 10896 // only set a value if one is specified |
| 10897 if (value !== undefined) { |
| 10898 prototype[n] = value; |
| 10899 } |
| 10900 } |
| 10901 }, |
| 10902 lowerCaseMap: function(properties) { |
| 10903 var map = {}; |
| 10904 for (var n in properties) { |
| 10905 map[n.toLowerCase()] = n; |
| 10906 } |
| 10907 return map; |
| 10908 }, |
| 10909 createPropertyAccessor: function(name, ignoreWrites) { |
| 10910 var proto = this.prototype; |
| 10911 |
| 10912 var privateName = name + '_'; |
| 10913 var privateObservable = name + 'Observable_'; |
| 10914 proto[privateName] = proto[name]; |
| 10915 |
| 10916 Object.defineProperty(proto, name, { |
| 10917 get: function() { |
| 10918 var observable = this[privateObservable]; |
| 10919 if (observable) |
| 10920 observable.deliver(); |
| 10921 |
| 10922 return this[privateName]; |
| 10923 }, |
| 10924 set: function(value) { |
| 10925 if (ignoreWrites) { |
| 10926 return this[privateName]; |
| 10927 } |
| 10928 |
| 10929 var observable = this[privateObservable]; |
| 10930 if (observable) { |
| 10931 observable.setValue(value); |
| 10932 return; |
| 10933 } |
| 10934 |
| 10935 var oldValue = this[privateName]; |
| 10936 this[privateName] = value; |
| 10937 this.emitPropertyChangeRecord(name, value, oldValue); |
| 10938 |
| 10939 return value; |
| 10940 }, |
| 10941 configurable: true |
| 10942 }); |
| 10943 }, |
| 10944 createPropertyAccessors: function(prototype) { |
| 10945 var n$ = prototype._computedNames; |
| 10946 if (n$ && n$.length) { |
| 10947 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { |
| 10948 this.createPropertyAccessor(n, true); |
| 10949 } |
| 10950 } |
| 10951 var n$ = prototype._publishNames; |
| 10952 if (n$ && n$.length) { |
| 10953 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { |
| 10954 // If the property is computed and published, the accessor is created |
| 10955 // above. |
| 10956 if (!prototype.computed || !prototype.computed[n]) { |
| 10957 this.createPropertyAccessor(n); |
| 10958 } |
| 10959 } |
| 10960 } |
| 10961 }, |
| 10962 // This list contains some property names that people commonly want to use, |
| 10963 // but won't work because of Chrome/Safari bugs. It isn't an exhaustive |
| 10964 // list. In particular it doesn't contain any property names found on |
| 10965 // subtypes of HTMLElement (e.g. name, value). Rather it attempts to catch |
| 10966 // some common cases. |
| 10967 propertyNameBlacklist: { |
| 10968 children: 1, |
| 10969 'class': 1, |
| 10970 id: 1, |
| 10971 hidden: 1, |
| 10972 style: 1, |
| 10973 title: 1, |
| 10974 } |
| 10975 }; |
| 10976 |
| 10977 // exports |
| 10978 |
| 10979 scope.api.declaration.properties = properties; |
| 10980 |
| 10981 })(Polymer); |
| 10982 |
| 10983 (function(scope) { |
| 10984 |
| 10985 // magic words |
| 10986 |
| 10987 var ATTRIBUTES_ATTRIBUTE = 'attributes'; |
| 10988 var ATTRIBUTES_REGEX = /\s|,/; |
| 10989 |
| 10990 // attributes api |
| 10991 |
| 10992 var attributes = { |
| 10993 |
| 10994 inheritAttributesObjects: function(prototype) { |
| 10995 // chain our lower-cased publish map to the inherited version |
| 10996 this.inheritObject(prototype, 'publishLC'); |
| 10997 // chain our instance attributes map to the inherited version |
| 10998 this.inheritObject(prototype, '_instanceAttributes'); |
| 10999 }, |
| 11000 |
| 11001 publishAttributes: function(prototype, base) { |
| 11002 // merge names from 'attributes' attribute into the 'publish' object |
| 11003 var attributes = this.getAttribute(ATTRIBUTES_ATTRIBUTE); |
| 11004 if (attributes) { |
| 11005 // create a `publish` object if needed. |
| 11006 // the `publish` object is only relevant to this prototype, the |
| 11007 // publishing logic in `declaration/properties.js` is responsible for |
| 11008 // managing property values on the prototype chain. |
| 11009 // TODO(sjmiles): the `publish` object is later chained to it's |
| 11010 // ancestor object, presumably this is only for |
| 11011 // reflection or other non-library uses. |
| 11012 var publish = prototype.publish || (prototype.publish = {}); |
| 11013 // names='a b c' or names='a,b,c' |
| 11014 var names = attributes.split(ATTRIBUTES_REGEX); |
| 11015 // record each name for publishing |
| 11016 for (var i=0, l=names.length, n; i<l; i++) { |
| 11017 // remove excess ws |
| 11018 n = names[i].trim(); |
| 11019 // looks weird, but causes n to exist on `publish` if it does not; |
| 11020 // a more careful test would need expensive `in` operator |
| 11021 if (n && publish[n] === undefined) { |
| 11022 publish[n] = undefined; |
| 11023 } |
| 11024 } |
| 11025 } |
| 11026 }, |
| 11027 |
| 11028 // record clonable attributes from <element> |
| 11029 accumulateInstanceAttributes: function() { |
| 11030 // inherit instance attributes |
| 11031 var clonable = this.prototype._instanceAttributes; |
| 11032 // merge attributes from element |
| 11033 var a$ = this.attributes; |
| 11034 for (var i=0, l=a$.length, a; (i<l) && (a=a$[i]); i++) { |
| 11035 if (this.isInstanceAttribute(a.name)) { |
| 11036 clonable[a.name] = a.value; |
| 11037 } |
| 11038 } |
| 11039 }, |
| 11040 |
| 11041 isInstanceAttribute: function(name) { |
| 11042 return !this.blackList[name] && name.slice(0,3) !== 'on-'; |
| 11043 }, |
| 11044 |
| 11045 // do not clone these attributes onto instances |
| 11046 blackList: { |
| 11047 name: 1, |
| 11048 'extends': 1, |
| 11049 constructor: 1, |
| 11050 noscript: 1, |
| 11051 assetpath: 1, |
| 11052 'cache-csstext': 1 |
| 11053 } |
| 11054 |
| 11055 }; |
| 11056 |
| 11057 // add ATTRIBUTES_ATTRIBUTE to the blacklist |
| 11058 attributes.blackList[ATTRIBUTES_ATTRIBUTE] = 1; |
| 11059 |
| 11060 // exports |
| 11061 |
| 11062 scope.api.declaration.attributes = attributes; |
| 11063 |
| 11064 })(Polymer); |
| 11065 |
| 11066 (function(scope) { |
| 11067 |
| 11068 // imports |
| 11069 var events = scope.api.declaration.events; |
| 11070 |
| 11071 var syntax = new PolymerExpressions(); |
| 11072 var prepareBinding = syntax.prepareBinding; |
| 11073 |
| 11074 // Polymer takes a first crack at the binding to see if it's a declarative |
| 11075 // event handler. |
| 11076 syntax.prepareBinding = function(pathString, name, node) { |
| 11077 return events.prepareEventBinding(pathString, name, node) || |
| 11078 prepareBinding.call(syntax, pathString, name, node); |
| 11079 }; |
| 11080 |
| 11081 // declaration api supporting mdv |
| 11082 var mdv = { |
| 11083 syntax: syntax, |
| 11084 fetchTemplate: function() { |
| 11085 return this.querySelector('template'); |
| 11086 }, |
| 11087 templateContent: function() { |
| 11088 var template = this.fetchTemplate(); |
| 11089 return template && template.content; |
| 11090 }, |
| 11091 installBindingDelegate: function(template) { |
| 11092 if (template) { |
| 11093 template.bindingDelegate = this.syntax; |
| 11094 } |
| 11095 } |
| 11096 }; |
| 11097 |
| 11098 // exports |
| 11099 scope.api.declaration.mdv = mdv; |
| 11100 |
| 11101 })(Polymer); |
| 11102 |
| 11103 (function(scope) { |
| 11104 |
| 11105 // imports |
| 11106 |
| 11107 var api = scope.api; |
| 11108 var isBase = scope.isBase; |
| 11109 var extend = scope.extend; |
| 11110 |
| 11111 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; |
| 11112 |
| 11113 // prototype api |
| 11114 |
| 11115 var prototype = { |
| 11116 |
| 11117 register: function(name, extendeeName) { |
| 11118 // build prototype combining extendee, Polymer base, and named api |
| 11119 this.buildPrototype(name, extendeeName); |
| 11120 // register our custom element with the platform |
| 11121 this.registerPrototype(name, extendeeName); |
| 11122 // reference constructor in a global named by 'constructor' attribute |
| 11123 this.publishConstructor(); |
| 11124 }, |
| 11125 |
| 11126 buildPrototype: function(name, extendeeName) { |
| 11127 // get our custom prototype (before chaining) |
| 11128 var extension = scope.getRegisteredPrototype(name); |
| 11129 // get basal prototype |
| 11130 var base = this.generateBasePrototype(extendeeName); |
| 11131 // implement declarative features |
| 11132 this.desugarBeforeChaining(extension, base); |
| 11133 // join prototypes |
| 11134 this.prototype = this.chainPrototypes(extension, base); |
| 11135 // more declarative features |
| 11136 this.desugarAfterChaining(name, extendeeName); |
| 11137 }, |
| 11138 |
| 11139 desugarBeforeChaining: function(prototype, base) { |
| 11140 // back reference declaration element |
| 11141 // TODO(sjmiles): replace `element` with `elementElement` or `declaration` |
| 11142 prototype.element = this; |
| 11143 // transcribe `attributes` declarations onto own prototype's `publish` |
| 11144 this.publishAttributes(prototype, base); |
| 11145 // `publish` properties to the prototype and to attribute watch |
| 11146 this.publishProperties(prototype, base); |
| 11147 // infer observers for `observe` list based on method names |
| 11148 this.inferObservers(prototype); |
| 11149 // desugar compound observer syntax, e.g. 'a b c' |
| 11150 this.explodeObservers(prototype); |
| 11151 }, |
| 11152 |
| 11153 chainPrototypes: function(prototype, base) { |
| 11154 // chain various meta-data objects to inherited versions |
| 11155 this.inheritMetaData(prototype, base); |
| 11156 // chain custom api to inherited |
| 11157 var chained = this.chainObject(prototype, base); |
| 11158 // x-platform fixup |
| 11159 ensurePrototypeTraversal(chained); |
| 11160 return chained; |
| 11161 }, |
| 11162 |
| 11163 inheritMetaData: function(prototype, base) { |
| 11164 // chain observe object to inherited |
| 11165 this.inheritObject('observe', prototype, base); |
| 11166 // chain publish object to inherited |
| 11167 this.inheritObject('publish', prototype, base); |
| 11168 // chain reflect object to inherited |
| 11169 this.inheritObject('reflect', prototype, base); |
| 11170 // chain our lower-cased publish map to the inherited version |
| 11171 this.inheritObject('_publishLC', prototype, base); |
| 11172 // chain our instance attributes map to the inherited version |
| 11173 this.inheritObject('_instanceAttributes', prototype, base); |
| 11174 // chain our event delegates map to the inherited version |
| 11175 this.inheritObject('eventDelegates', prototype, base); |
| 11176 }, |
| 11177 |
| 11178 // implement various declarative features |
| 11179 desugarAfterChaining: function(name, extendee) { |
| 11180 // build side-chained lists to optimize iterations |
| 11181 this.optimizePropertyMaps(this.prototype); |
| 11182 this.createPropertyAccessors(this.prototype); |
| 11183 // install mdv delegate on template |
| 11184 this.installBindingDelegate(this.fetchTemplate()); |
| 11185 // install external stylesheets as if they are inline |
| 11186 this.installSheets(); |
| 11187 // adjust any paths in dom from imports |
| 11188 this.resolveElementPaths(this); |
| 11189 // compile list of attributes to copy to instances |
| 11190 this.accumulateInstanceAttributes(); |
| 11191 // parse on-* delegates declared on `this` element |
| 11192 this.parseHostEvents(); |
| 11193 // |
| 11194 // install a helper method this.resolvePath to aid in |
| 11195 // setting resource urls. e.g. |
| 11196 // this.$.image.src = this.resolvePath('images/foo.png') |
| 11197 this.addResolvePathApi(); |
| 11198 // under ShadowDOMPolyfill, transforms to approximate missing CSS features |
| 11199 if (hasShadowDOMPolyfill) { |
| 11200 WebComponents.ShadowCSS.shimStyling(this.templateContent(), name, |
| 11201 extendee); |
| 11202 } |
| 11203 // allow custom element access to the declarative context |
| 11204 if (this.prototype.registerCallback) { |
| 11205 this.prototype.registerCallback(this); |
| 11206 } |
| 11207 }, |
| 11208 |
| 11209 // if a named constructor is requested in element, map a reference |
| 11210 // to the constructor to the given symbol |
| 11211 publishConstructor: function() { |
| 11212 var symbol = this.getAttribute('constructor'); |
| 11213 if (symbol) { |
| 11214 window[symbol] = this.ctor; |
| 11215 } |
| 11216 }, |
| 11217 |
| 11218 // build prototype combining extendee, Polymer base, and named api |
| 11219 generateBasePrototype: function(extnds) { |
| 11220 var prototype = this.findBasePrototype(extnds); |
| 11221 if (!prototype) { |
| 11222 // create a prototype based on tag-name extension |
| 11223 var prototype = HTMLElement.getPrototypeForTag(extnds); |
| 11224 // insert base api in inheritance chain (if needed) |
| 11225 prototype = this.ensureBaseApi(prototype); |
| 11226 // memoize this base |
| 11227 memoizedBases[extnds] = prototype; |
| 11228 } |
| 11229 return prototype; |
| 11230 }, |
| 11231 |
| 11232 findBasePrototype: function(name) { |
| 11233 return memoizedBases[name]; |
| 11234 }, |
| 11235 |
| 11236 // install Polymer instance api into prototype chain, as needed |
| 11237 ensureBaseApi: function(prototype) { |
| 11238 if (prototype.PolymerBase) { |
| 11239 return prototype; |
| 11240 } |
| 11241 var extended = Object.create(prototype); |
| 11242 // we need a unique copy of base api for each base prototype |
| 11243 // therefore we 'extend' here instead of simply chaining |
| 11244 api.publish(api.instance, extended); |
| 11245 // TODO(sjmiles): sharing methods across prototype chains is |
| 11246 // not supported by 'super' implementation which optimizes |
| 11247 // by memoizing prototype relationships. |
| 11248 // Probably we should have a version of 'extend' that is |
| 11249 // share-aware: it could study the text of each function, |
| 11250 // look for usage of 'super', and wrap those functions in |
| 11251 // closures. |
| 11252 // As of now, there is only one problematic method, so |
| 11253 // we just patch it manually. |
| 11254 // To avoid re-entrancy problems, the special super method |
| 11255 // installed is called `mixinSuper` and the mixin method |
| 11256 // must use this method instead of the default `super`. |
| 11257 this.mixinMethod(extended, prototype, api.instance.mdv, 'bind'); |
| 11258 // return buffed-up prototype |
| 11259 return extended; |
| 11260 }, |
| 11261 |
| 11262 mixinMethod: function(extended, prototype, api, name) { |
| 11263 var $super = function(args) { |
| 11264 return prototype[name].apply(this, args); |
| 11265 }; |
| 11266 extended[name] = function() { |
| 11267 this.mixinSuper = $super; |
| 11268 return api[name].apply(this, arguments); |
| 11269 } |
| 11270 }, |
| 11271 |
| 11272 // ensure prototype[name] inherits from a prototype.prototype[name] |
| 11273 inheritObject: function(name, prototype, base) { |
| 11274 // require an object |
| 11275 var source = prototype[name] || {}; |
| 11276 // chain inherited properties onto a new object |
| 11277 prototype[name] = this.chainObject(source, base[name]); |
| 11278 }, |
| 11279 |
| 11280 // register 'prototype' to custom element 'name', store constructor |
| 11281 registerPrototype: function(name, extendee) { |
| 11282 var info = { |
| 11283 prototype: this.prototype |
| 11284 } |
| 11285 // native element must be specified in extends |
| 11286 var typeExtension = this.findTypeExtension(extendee); |
| 11287 if (typeExtension) { |
| 11288 info.extends = typeExtension; |
| 11289 } |
| 11290 // register the prototype with HTMLElement for name lookup |
| 11291 HTMLElement.register(name, this.prototype); |
| 11292 // register the custom type |
| 11293 this.ctor = document.registerElement(name, info); |
| 11294 }, |
| 11295 |
| 11296 findTypeExtension: function(name) { |
| 11297 if (name && name.indexOf('-') < 0) { |
| 11298 return name; |
| 11299 } else { |
| 11300 var p = this.findBasePrototype(name); |
| 11301 if (p.element) { |
| 11302 return this.findTypeExtension(p.element.extends); |
| 11303 } |
| 11304 } |
| 11305 } |
| 11306 |
| 11307 }; |
| 11308 |
| 11309 // memoize base prototypes |
| 11310 var memoizedBases = {}; |
| 11311 |
| 11312 // implementation of 'chainObject' depends on support for __proto__ |
| 11313 if (Object.__proto__) { |
| 11314 prototype.chainObject = function(object, inherited) { |
| 11315 if (object && inherited && object !== inherited) { |
| 11316 object.__proto__ = inherited; |
| 11317 } |
| 11318 return object; |
| 11319 } |
| 11320 } else { |
| 11321 prototype.chainObject = function(object, inherited) { |
| 11322 if (object && inherited && object !== inherited) { |
| 11323 var chained = Object.create(inherited); |
| 11324 object = extend(chained, object); |
| 11325 } |
| 11326 return object; |
| 11327 } |
| 11328 } |
| 11329 |
| 11330 // On platforms that do not support __proto__ (versions of IE), the prototype |
| 11331 // chain of a custom element is simulated via installation of __proto__. |
| 11332 // Although custom elements manages this, we install it here so it's |
| 11333 // available during desugaring. |
| 11334 function ensurePrototypeTraversal(prototype) { |
| 11335 if (!Object.__proto__) { |
| 11336 var ancestor = Object.getPrototypeOf(prototype); |
| 11337 prototype.__proto__ = ancestor; |
| 11338 if (isBase(ancestor)) { |
| 11339 ancestor.__proto__ = Object.getPrototypeOf(ancestor); |
| 11340 } |
| 11341 } |
| 11342 } |
| 11343 |
| 11344 // exports |
| 11345 |
| 11346 api.declaration.prototype = prototype; |
| 11347 |
| 11348 })(Polymer); |
| 11349 |
| 11350 (function(scope) { |
| 11351 |
| 11352 /* |
| 11353 |
| 11354 Elements are added to a registration queue so that they register in |
| 11355 the proper order at the appropriate time. We do this for a few reasons: |
| 11356 |
| 11357 * to enable elements to load resources (like stylesheets) |
| 11358 asynchronously. We need to do this until the platform provides an efficient |
| 11359 alternative. One issue is that remote @import stylesheets are |
| 11360 re-fetched whenever stamped into a shadowRoot. |
| 11361 |
| 11362 * to ensure elements loaded 'at the same time' (e.g. via some set of |
| 11363 imports) are registered as a batch. This allows elements to be enured from |
| 11364 upgrade ordering as long as they query the dom tree 1 task after |
| 11365 upgrade (aka domReady). This is a performance tradeoff. On the one hand, |
| 11366 elements that could register while imports are loading are prevented from |
| 11367 doing so. On the other, grouping upgrades into a single task means less |
| 11368 incremental work (for example style recalcs), Also, we can ensure the |
| 11369 document is in a known state at the single quantum of time when |
| 11370 elements upgrade. |
| 11371 |
| 11372 */ |
| 11373 var queue = { |
| 11374 |
| 11375 // tell the queue to wait for an element to be ready |
| 11376 wait: function(element) { |
| 11377 if (!element.__queue) { |
| 11378 element.__queue = {}; |
| 11379 elements.push(element); |
| 11380 } |
| 11381 }, |
| 11382 |
| 11383 // enqueue an element to the next spot in the queue. |
| 11384 enqueue: function(element, check, go) { |
| 11385 var shouldAdd = element.__queue && !element.__queue.check; |
| 11386 if (shouldAdd) { |
| 11387 queueForElement(element).push(element); |
| 11388 element.__queue.check = check; |
| 11389 element.__queue.go = go; |
| 11390 } |
| 11391 return (this.indexOf(element) !== 0); |
| 11392 }, |
| 11393 |
| 11394 indexOf: function(element) { |
| 11395 var i = queueForElement(element).indexOf(element); |
| 11396 if (i >= 0 && document.contains(element)) { |
| 11397 i += (HTMLImports.useNative || HTMLImports.ready) ? |
| 11398 importQueue.length : 1e9; |
| 11399 } |
| 11400 return i; |
| 11401 }, |
| 11402 |
| 11403 // tell the queue an element is ready to be registered |
| 11404 go: function(element) { |
| 11405 var readied = this.remove(element); |
| 11406 if (readied) { |
| 11407 element.__queue.flushable = true; |
| 11408 this.addToFlushQueue(readied); |
| 11409 this.check(); |
| 11410 } |
| 11411 }, |
| 11412 |
| 11413 remove: function(element) { |
| 11414 var i = this.indexOf(element); |
| 11415 if (i !== 0) { |
| 11416 //console.warn('queue order wrong', i); |
| 11417 return; |
| 11418 } |
| 11419 return queueForElement(element).shift(); |
| 11420 }, |
| 11421 |
| 11422 check: function() { |
| 11423 // next |
| 11424 var element = this.nextElement(); |
| 11425 if (element) { |
| 11426 element.__queue.check.call(element); |
| 11427 } |
| 11428 if (this.canReady()) { |
| 11429 this.ready(); |
| 11430 return true; |
| 11431 } |
| 11432 }, |
| 11433 |
| 11434 nextElement: function() { |
| 11435 return nextQueued(); |
| 11436 }, |
| 11437 |
| 11438 canReady: function() { |
| 11439 return !this.waitToReady && this.isEmpty(); |
| 11440 }, |
| 11441 |
| 11442 isEmpty: function() { |
| 11443 for (var i=0, l=elements.length, e; (i<l) && |
| 11444 (e=elements[i]); i++) { |
| 11445 if (e.__queue && !e.__queue.flushable) { |
| 11446 return; |
| 11447 } |
| 11448 } |
| 11449 return true; |
| 11450 }, |
| 11451 |
| 11452 addToFlushQueue: function(element) { |
| 11453 flushQueue.push(element); |
| 11454 }, |
| 11455 |
| 11456 flush: function() { |
| 11457 // prevent re-entrance |
| 11458 if (this.flushing) { |
| 11459 return; |
| 11460 } |
| 11461 this.flushing = true; |
| 11462 var element; |
| 11463 while (flushQueue.length) { |
| 11464 element = flushQueue.shift(); |
| 11465 element.__queue.go.call(element); |
| 11466 element.__queue = null; |
| 11467 } |
| 11468 this.flushing = false; |
| 11469 }, |
| 11470 |
| 11471 ready: function() { |
| 11472 // TODO(sorvell): As an optimization, turn off CE polyfill upgrading |
| 11473 // while registering. This way we avoid having to upgrade each document |
| 11474 // piecemeal per registration and can instead register all elements |
| 11475 // and upgrade once in a batch. Without this optimization, upgrade time |
| 11476 // degrades significantly when SD polyfill is used. This is mainly because |
| 11477 // querying the document tree for elements is slow under the SD polyfill. |
| 11478 var polyfillWasReady = CustomElements.ready; |
| 11479 CustomElements.ready = false; |
| 11480 this.flush(); |
| 11481 if (!CustomElements.useNative) { |
| 11482 CustomElements.upgradeDocumentTree(document); |
| 11483 } |
| 11484 CustomElements.ready = polyfillWasReady; |
| 11485 Polymer.flush(); |
| 11486 requestAnimationFrame(this.flushReadyCallbacks); |
| 11487 }, |
| 11488 |
| 11489 addReadyCallback: function(callback) { |
| 11490 if (callback) { |
| 11491 readyCallbacks.push(callback); |
| 11492 } |
| 11493 }, |
| 11494 |
| 11495 flushReadyCallbacks: function() { |
| 11496 if (readyCallbacks) { |
| 11497 var fn; |
| 11498 while (readyCallbacks.length) { |
| 11499 fn = readyCallbacks.shift(); |
| 11500 fn(); |
| 11501 } |
| 11502 } |
| 11503 }, |
| 11504 |
| 11505 /** |
| 11506 Returns a list of elements that have had polymer-elements created but |
| 11507 are not yet ready to register. The list is an array of element definitions. |
| 11508 */ |
| 11509 waitingFor: function() { |
| 11510 var e$ = []; |
| 11511 for (var i=0, l=elements.length, e; (i<l) && |
| 11512 (e=elements[i]); i++) { |
| 11513 if (e.__queue && !e.__queue.flushable) { |
| 11514 e$.push(e); |
| 11515 } |
| 11516 } |
| 11517 return e$; |
| 11518 }, |
| 11519 |
| 11520 waitToReady: true |
| 11521 |
| 11522 }; |
| 11523 |
| 11524 var elements = []; |
| 11525 var flushQueue = []; |
| 11526 var importQueue = []; |
| 11527 var mainQueue = []; |
| 11528 var readyCallbacks = []; |
| 11529 |
| 11530 function queueForElement(element) { |
| 11531 return document.contains(element) ? mainQueue : importQueue; |
| 11532 } |
| 11533 |
| 11534 function nextQueued() { |
| 11535 return importQueue.length ? importQueue[0] : mainQueue[0]; |
| 11536 } |
| 11537 |
| 11538 function whenReady(callback) { |
| 11539 queue.waitToReady = true; |
| 11540 Polymer.endOfMicrotask(function() { |
| 11541 HTMLImports.whenReady(function() { |
| 11542 queue.addReadyCallback(callback); |
| 11543 queue.waitToReady = false; |
| 11544 queue.check(); |
| 11545 }); |
| 11546 }); |
| 11547 } |
| 11548 |
| 11549 /** |
| 11550 Forces polymer to register any pending elements. Can be used to abort |
| 11551 waiting for elements that are partially defined. |
| 11552 @param timeout {Integer} Optional timeout in milliseconds |
| 11553 */ |
| 11554 function forceReady(timeout) { |
| 11555 if (timeout === undefined) { |
| 11556 queue.ready(); |
| 11557 return; |
| 11558 } |
| 11559 var handle = setTimeout(function() { |
| 11560 queue.ready(); |
| 11561 }, timeout); |
| 11562 Polymer.whenReady(function() { |
| 11563 clearTimeout(handle); |
| 11564 }); |
| 11565 } |
| 11566 |
| 11567 // exports |
| 11568 scope.elements = elements; |
| 11569 scope.waitingFor = queue.waitingFor.bind(queue); |
| 11570 scope.forceReady = forceReady; |
| 11571 scope.queue = queue; |
| 11572 scope.whenReady = scope.whenPolymerReady = whenReady; |
| 11573 })(Polymer); |
| 11574 |
| 11575 (function(scope) { |
| 11576 |
| 11577 // imports |
| 11578 |
| 11579 var extend = scope.extend; |
| 11580 var api = scope.api; |
| 11581 var queue = scope.queue; |
| 11582 var whenReady = scope.whenReady; |
| 11583 var getRegisteredPrototype = scope.getRegisteredPrototype; |
| 11584 var waitingForPrototype = scope.waitingForPrototype; |
| 11585 |
| 11586 // declarative implementation: <polymer-element> |
| 11587 |
| 11588 var prototype = extend(Object.create(HTMLElement.prototype), { |
| 11589 |
| 11590 createdCallback: function() { |
| 11591 if (this.getAttribute('name')) { |
| 11592 this.init(); |
| 11593 } |
| 11594 }, |
| 11595 |
| 11596 init: function() { |
| 11597 // fetch declared values |
| 11598 this.name = this.getAttribute('name'); |
| 11599 this.extends = this.getAttribute('extends'); |
| 11600 queue.wait(this); |
| 11601 // initiate any async resource fetches |
| 11602 this.loadResources(); |
| 11603 // register when all constraints are met |
| 11604 this.registerWhenReady(); |
| 11605 }, |
| 11606 |
| 11607 // TODO(sorvell): we currently queue in the order the prototypes are |
| 11608 // registered, but we should queue in the order that polymer-elements |
| 11609 // are registered. We are currently blocked from doing this based on |
| 11610 // crbug.com/395686. |
| 11611 registerWhenReady: function() { |
| 11612 if (this.registered |
| 11613 || this.waitingForPrototype(this.name) |
| 11614 || this.waitingForQueue() |
| 11615 || this.waitingForResources()) { |
| 11616 return; |
| 11617 } |
| 11618 queue.go(this); |
| 11619 }, |
| 11620 |
| 11621 _register: function() { |
| 11622 //console.log('registering', this.name); |
| 11623 // warn if extending from a custom element not registered via Polymer |
| 11624 if (isCustomTag(this.extends) && !isRegistered(this.extends)) { |
| 11625 console.warn('%s is attempting to extend %s, an unregistered element ' + |
| 11626 'or one that was not registered with Polymer.', this.name, |
| 11627 this.extends); |
| 11628 } |
| 11629 this.register(this.name, this.extends); |
| 11630 this.registered = true; |
| 11631 }, |
| 11632 |
| 11633 waitingForPrototype: function(name) { |
| 11634 if (!getRegisteredPrototype(name)) { |
| 11635 // then wait for a prototype |
| 11636 waitingForPrototype(name, this); |
| 11637 // emulate script if user is not supplying one |
| 11638 this.handleNoScript(name); |
| 11639 // prototype not ready yet |
| 11640 return true; |
| 11641 } |
| 11642 }, |
| 11643 |
| 11644 handleNoScript: function(name) { |
| 11645 // if explicitly marked as 'noscript' |
| 11646 if (this.hasAttribute('noscript') && !this.noscript) { |
| 11647 this.noscript = true; |
| 11648 // imperative element registration |
| 11649 Polymer(name); |
| 11650 } |
| 11651 }, |
| 11652 |
| 11653 waitingForResources: function() { |
| 11654 return this._needsResources; |
| 11655 }, |
| 11656 |
| 11657 // NOTE: Elements must be queued in proper order for inheritance/composition |
| 11658 // dependency resolution. Previously this was enforced for inheritance, |
| 11659 // and by rule for composition. It's now entirely by rule. |
| 11660 waitingForQueue: function() { |
| 11661 return queue.enqueue(this, this.registerWhenReady, this._register); |
| 11662 }, |
| 11663 |
| 11664 loadResources: function() { |
| 11665 this._needsResources = true; |
| 11666 this.loadStyles(function() { |
| 11667 this._needsResources = false; |
| 11668 this.registerWhenReady(); |
| 11669 }.bind(this)); |
| 11670 } |
| 11671 |
| 11672 }); |
| 11673 |
| 11674 // semi-pluggable APIs |
| 11675 |
| 11676 // TODO(sjmiles): should be fully pluggable (aka decoupled, currently |
| 11677 // the various plugins are allowed to depend on each other directly) |
| 11678 api.publish(api.declaration, prototype); |
| 11679 |
| 11680 // utility and bookkeeping |
| 11681 |
| 11682 function isRegistered(name) { |
| 11683 return Boolean(HTMLElement.getPrototypeForTag(name)); |
| 11684 } |
| 11685 |
| 11686 function isCustomTag(name) { |
| 11687 return (name && name.indexOf('-') >= 0); |
| 11688 } |
| 11689 |
| 11690 // boot tasks |
| 11691 |
| 11692 whenReady(function() { |
| 11693 document.body.removeAttribute('unresolved'); |
| 11694 document.dispatchEvent( |
| 11695 new CustomEvent('polymer-ready', {bubbles: true}) |
| 11696 ); |
| 11697 }); |
| 11698 |
| 11699 // register polymer-element with document |
| 11700 |
| 11701 document.registerElement('polymer-element', {prototype: prototype}); |
| 11702 |
| 11703 })(Polymer); |
| 11704 |
| 11705 (function(scope) { |
| 11706 |
| 11707 /** |
| 11708 * @class Polymer |
| 11709 */ |
| 11710 |
| 11711 var whenReady = scope.whenReady; |
| 11712 |
| 11713 /** |
| 11714 * Loads the set of HTMLImports contained in `node`. Notifies when all |
| 11715 * the imports have loaded by calling the `callback` function argument. |
| 11716 * This method can be used to lazily load imports. For example, given a |
| 11717 * template: |
| 11718 * |
| 11719 * <template> |
| 11720 * <link rel="import" href="my-import1.html"> |
| 11721 * <link rel="import" href="my-import2.html"> |
| 11722 * </template> |
| 11723 * |
| 11724 * Polymer.importElements(template.content, function() { |
| 11725 * console.log('imports lazily loaded'); |
| 11726 * }); |
| 11727 * |
| 11728 * @method importElements |
| 11729 * @param {Node} node Node containing the HTMLImports to load. |
| 11730 * @param {Function} callback Callback called when all imports have loaded. |
| 11731 */ |
| 11732 function importElements(node, callback) { |
| 11733 if (node) { |
| 11734 document.head.appendChild(node); |
| 11735 whenReady(callback); |
| 11736 } else if (callback) { |
| 11737 callback(); |
| 11738 } |
| 11739 } |
| 11740 |
| 11741 /** |
| 11742 * Loads an HTMLImport for each url specified in the `urls` array. |
| 11743 * Notifies when all the imports have loaded by calling the `callback` |
| 11744 * function argument. This method can be used to lazily load imports. |
| 11745 * For example, |
| 11746 * |
| 11747 * Polymer.import(['my-import1.html', 'my-import2.html'], function() { |
| 11748 * console.log('imports lazily loaded'); |
| 11749 * }); |
| 11750 * |
| 11751 * @method import |
| 11752 * @param {Array} urls Array of urls to load as HTMLImports. |
| 11753 * @param {Function} callback Callback called when all imports have loaded. |
| 11754 */ |
| 11755 function _import(urls, callback) { |
| 11756 if (urls && urls.length) { |
| 11757 var frag = document.createDocumentFragment(); |
| 11758 for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) { |
| 11759 link = document.createElement('link'); |
| 11760 link.rel = 'import'; |
| 11761 link.href = url; |
| 11762 frag.appendChild(link); |
| 11763 } |
| 11764 importElements(frag, callback); |
| 11765 } else if (callback) { |
| 11766 callback(); |
| 11767 } |
| 11768 } |
| 11769 |
| 11770 // exports |
| 11771 scope.import = _import; |
| 11772 scope.importElements = importElements; |
| 11773 |
| 11774 })(Polymer); |
| 11775 |
| 11776 /** |
| 11777 * The `auto-binding` element extends the template element. It provides a quick |
| 11778 * and easy way to do data binding without the need to setup a model. |
| 11779 * The `auto-binding` element itself serves as the model and controller for the |
| 11780 * elements it contains. Both data and event handlers can be bound. |
| 11781 * |
| 11782 * The `auto-binding` element acts just like a template that is bound to |
| 11783 * a model. It stamps its content in the dom adjacent to itself. When the |
| 11784 * content is stamped, the `template-bound` event is fired. |
| 11785 * |
| 11786 * Example: |
| 11787 * |
| 11788 * <template is="auto-binding"> |
| 11789 * <div>Say something: <input value="{{value}}"></div> |
| 11790 * <div>You said: {{value}}</div> |
| 11791 * <button on-tap="{{buttonTap}}">Tap me!</button> |
| 11792 * </template> |
| 11793 * <script> |
| 11794 * var template = document.querySelector('template'); |
| 11795 * template.value = 'something'; |
| 11796 * template.buttonTap = function() { |
| 11797 * console.log('tap!'); |
| 11798 * }; |
| 11799 * </script> |
| 11800 * |
| 11801 * @module Polymer |
| 11802 * @status stable |
| 11803 */ |
| 11804 |
| 11805 (function() { |
| 11806 |
| 11807 var element = document.createElement('polymer-element'); |
| 11808 element.setAttribute('name', 'auto-binding'); |
| 11809 element.setAttribute('extends', 'template'); |
| 11810 element.init(); |
| 11811 |
| 11812 Polymer('auto-binding', { |
| 11813 |
| 11814 createdCallback: function() { |
| 11815 this.syntax = this.bindingDelegate = this.makeSyntax(); |
| 11816 // delay stamping until polymer-ready so that auto-binding is not |
| 11817 // required to load last. |
| 11818 Polymer.whenPolymerReady(function() { |
| 11819 this.model = this; |
| 11820 this.setAttribute('bind', ''); |
| 11821 // we don't bother with an explicit signal here, we could ust a MO |
| 11822 // if necessary |
| 11823 this.async(function() { |
| 11824 // note: this will marshall *all* the elements in the parentNode |
| 11825 // rather than just stamped ones. We'd need to use createInstance |
| 11826 // to fix this or something else fancier. |
| 11827 this.marshalNodeReferences(this.parentNode); |
| 11828 // template stamping is asynchronous so stamping isn't complete |
| 11829 // by polymer-ready; fire an event so users can use stamped elements |
| 11830 this.fire('template-bound'); |
| 11831 }); |
| 11832 }.bind(this)); |
| 11833 }, |
| 11834 |
| 11835 makeSyntax: function() { |
| 11836 var events = Object.create(Polymer.api.declaration.events); |
| 11837 var self = this; |
| 11838 events.findController = function() { return self.model; }; |
| 11839 |
| 11840 var syntax = new PolymerExpressions(); |
| 11841 var prepareBinding = syntax.prepareBinding; |
| 11842 syntax.prepareBinding = function(pathString, name, node) { |
| 11843 return events.prepareEventBinding(pathString, name, node) || |
| 11844 prepareBinding.call(syntax, pathString, name, node); |
| 11845 }; |
| 11846 return syntax; |
| 11847 } |
| 11848 |
| 11849 }); |
| 11850 |
| 11851 })(); |
OLD | NEW |