| Index: pkg/polymer/lib/src/js/polymer/polymer.concat.js | 
| diff --git a/pkg/polymer/lib/src/js/polymer/polymer.concat.js b/pkg/polymer/lib/src/js/polymer/polymer.concat.js | 
| index 2478c928e744ef62ef805160cffdaedeb98749bb..b217d9d9aaa2cac257c48dd302f54f86f1c7c594 100644 | 
| --- a/pkg/polymer/lib/src/js/polymer/polymer.concat.js | 
| +++ b/pkg/polymer/lib/src/js/polymer/polymer.concat.js | 
| @@ -7,7 +7,10 @@ | 
| * Code distributed by Google as part of the polymer project is also | 
| * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt | 
| */ | 
| -window.PolymerGestures = {}; | 
| +window.PolymerGestures = { | 
| +  hasSDPolyfill: Boolean(window.ShadowDOMPolyfill) | 
| +}; | 
| +PolymerGestures.wrap = PolymerGestures.hasSDPolyfill ? ShadowDOMPolyfill.wrapIfNeeded : function(a){ return a; }; | 
|  | 
| /* | 
| * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | 
| @@ -19,6 +22,30 @@ window.PolymerGestures = {}; | 
| */ | 
|  | 
| (function(scope) { | 
| +  var HAS_FULL_PATH = false; | 
| + | 
| +  // test for full event path support | 
| +  var pathTest = document.createElement('meta'); | 
| +  if (!scope.hasSDPolyfill && pathTest.createShadowRoot) { | 
| +    var sr = pathTest.createShadowRoot(); | 
| +    var s = document.createElement('span'); | 
| +    sr.appendChild(s); | 
| +    pathTest.addEventListener('testpath', function(ev) { | 
| +      if (ev.path) { | 
| +        // if the span is in the event path, then path[0] is the real source for all events | 
| +        HAS_FULL_PATH = ev.path[0] === s; | 
| +      } | 
| +      ev.stopPropagation(); | 
| +    }); | 
| +    var ev = new CustomEvent('testpath', {bubbles: true}); | 
| +    // must add node to DOM to trigger event listener | 
| +    document.head.appendChild(pathTest); | 
| +    s.dispatchEvent(ev); | 
| +    pathTest.parentNode.removeChild(pathTest); | 
| +    sr = s = null; | 
| +  } | 
| +  pathTest = null; | 
| + | 
| var target = { | 
| shadow: function(inEl) { | 
| if (inEl) { | 
| @@ -53,25 +80,18 @@ window.PolymerGestures = {}; | 
| return shadows; | 
| }, | 
| searchRoot: function(inRoot, x, y) { | 
| +      var t, st, sr, os; | 
| if (inRoot) { | 
| -        var t = inRoot.elementFromPoint(x, y); | 
| -        var st, sr, os; | 
| -        // is element a shadow host? | 
| -        sr = this.targetingShadow(t); | 
| -        while (sr) { | 
| -          // find the the element inside the shadow root | 
| -          st = sr.elementFromPoint(x, y); | 
| -          if (!st) { | 
| -            // check for older shadows | 
| -            sr = this.olderShadow(sr); | 
| -          } else { | 
| -            // shadowed element may contain a shadow root | 
| -            var ssr = this.targetingShadow(st); | 
| -            return this.searchRoot(ssr, x, y) || st; | 
| -          } | 
| +        t = inRoot.elementFromPoint(x, y); | 
| +        if (t) { | 
| +          // found element, check if it has a ShadowRoot | 
| +          sr = this.targetingShadow(t); | 
| +        } else if (inRoot !== document) { | 
| +          // check for sibling roots | 
| +          sr = this.olderShadow(inRoot); | 
| } | 
| -        // light dom element is the target | 
| -        return t; | 
| +        // search other roots, fall back to light dom element | 
| +        return this.searchRoot(sr, x, y) || t; | 
| } | 
| }, | 
| owner: function(element) { | 
| @@ -90,6 +110,9 @@ window.PolymerGestures = {}; | 
| return s; | 
| }, | 
| findTarget: function(inEvent) { | 
| +      if (HAS_FULL_PATH && inEvent.path) { | 
| +        return inEvent.path[0]; | 
| +      } | 
| var x = inEvent.clientX, y = inEvent.clientY; | 
| // if the listener is in the shadow root, it is much faster to start there | 
| var s = this.owner(inEvent.target); | 
| @@ -99,6 +122,26 @@ window.PolymerGestures = {}; | 
| } | 
| return this.searchRoot(s, x, y); | 
| }, | 
| +    findScrollAxis: function(inEvent) { | 
| +      var n; | 
| +      if (HAS_FULL_PATH && inEvent.path) { | 
| +        var path = inEvent.path; | 
| +        for (var i = 0; i < path.length; i++) { | 
| +          n = path[i]; | 
| +          if (n._scrollType) { | 
| +            return n._scrollType; | 
| +          } | 
| +        } | 
| +      } else { | 
| +        n = scope.wrap(inEvent.currentTarget); | 
| +        while(n) { | 
| +          if (n._scrollType) { | 
| +            return n._scrollType; | 
| +          } | 
| +          n = n.parentNode || n.host; | 
| +        } | 
| +      } | 
| +    }, | 
| LCA: function(a, b) { | 
| if (a === b) { | 
| return a; | 
| @@ -122,14 +165,14 @@ window.PolymerGestures = {}; | 
| var adepth = this.depth(a); | 
| var bdepth = this.depth(b); | 
| var d = adepth - bdepth; | 
| -      if (d > 0) { | 
| +      if (d >= 0) { | 
| a = this.walk(a, d); | 
| } else { | 
| b = this.walk(b, -d); | 
| } | 
| -      while(a && b && a !== b) { | 
| -        a = this.walk(a, 1); | 
| -        b = this.walk(b, 1); | 
| +      while (a && b && a !== b) { | 
| +        a = a.parentNode || a.host; | 
| +        b = b.parentNode || b.host; | 
| } | 
| return a; | 
| }, | 
| @@ -229,7 +272,8 @@ window.PolymerGestures = {}; | 
| 'pan-x pan-y', | 
| 'pan-y pan-x' | 
| ] | 
| -    } | 
| +    }, | 
| +    'manipulation' | 
| ]; | 
| var styles = ''; | 
| // only install stylesheet if the browser has touch action support | 
| @@ -377,6 +421,7 @@ window.PolymerGestures = {}; | 
| e.pointerType = inDict.pointerType || ''; | 
| e.hwTimestamp = inDict.hwTimestamp || 0; | 
| e.isPrimary = inDict.isPrimary || false; | 
| +      e._source = inDict._source || ''; | 
| return e; | 
| } | 
| }; | 
| @@ -500,7 +545,8 @@ window.PolymerGestures = {}; | 
| 'timeStamp', | 
| // gesture addons | 
| 'preventTap', | 
| -    'tapPrevented' | 
| +    'tapPrevented', | 
| +    '_source' | 
| ]; | 
|  | 
| var CLONE_DEFAULTS = [ | 
| @@ -545,9 +591,11 @@ window.PolymerGestures = {}; | 
|  | 
| var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); | 
|  | 
| -  var wrap = window.ShadowDOMPolyfill && ShadowDOMPolyfill.wrapIfNeeded || function(e){ return e; }; | 
| - | 
| var eventFactory = scope.eventFactory; | 
| + | 
| +  var hasSDPolyfill = scope.hasSDPolyfill; | 
| +  var wrap = scope.wrap; | 
| + | 
| /** | 
| * This module is for normalizing events. Mouse and Touch events will be | 
| * collected here, and fire PointerEvents that have the same semantics, no | 
| @@ -594,10 +642,6 @@ window.PolymerGestures = {}; | 
| this.gestures.push(source); | 
| }, | 
| register: function(element) { | 
| -      // NOTE: Work around for #4, don't add listeners to individual Polymer elmenets in SD Polyfill | 
| -      if (window.ShadowDOMPolyfill && element !== document) { | 
| -        return; | 
| -      } | 
| var l = this.eventSourceList.length; | 
| for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | 
| // call eventsource register | 
| @@ -644,19 +688,19 @@ window.PolymerGestures = {}; | 
| }, | 
| // set up event listeners | 
| listen: function(target, events) { | 
| -      events.forEach(function(e) { | 
| +      for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { | 
| this.addEvent(target, e); | 
| -      }, this); | 
| +      } | 
| }, | 
| // remove event listeners | 
| unlisten: function(target, events) { | 
| -      events.forEach(function(e) { | 
| +      for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { | 
| this.removeEvent(target, e); | 
| -      }, this); | 
| +      } | 
| }, | 
| addEvent: function(target, eventName) { | 
| // NOTE: Work around for #4, use native event listener in SD Polyfill | 
| -      if (window.ShadowDOMPolyfill) { | 
| +      if (hasSDPolyfill) { | 
| target.addEventListener_(eventName, this.boundHandler); | 
| } else { | 
| target.addEventListener(eventName, this.boundHandler); | 
| @@ -664,7 +708,7 @@ window.PolymerGestures = {}; | 
| }, | 
| removeEvent: function(target, eventName) { | 
| // NOTE: Work around for #4, use native event listener in SD Polyfill | 
| -      if (window.ShadowDOMPolyfill) { | 
| +      if (hasSDPolyfill) { | 
| target.removeEventListener_(eventName, this.boundHandler); | 
| } else { | 
| target.removeEventListener(eventName, this.boundHandler); | 
| @@ -738,10 +782,11 @@ window.PolymerGestures = {}; | 
| // process the gesture queue | 
| for (var i = 0, e; i < this.gestureQueue.length; i++) { | 
| e = this.gestureQueue[i]; | 
| -        for (var j = 0, g; j < this.gestures.length; j++) { | 
| +        for (var j = 0, g, fn; j < this.gestures.length; j++) { | 
| g = this.gestures[j]; | 
| -          if (g.events.indexOf(e.type) >= 0) { | 
| -            g[e.type].call(g, e); | 
| +          fn = g[e.type]; | 
| +          if (fn) { | 
| +            fn.call(g, e); | 
| } | 
| } | 
| } | 
| @@ -758,8 +803,11 @@ window.PolymerGestures = {}; | 
| dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); | 
| dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); | 
| scope.dispatcher = dispatcher; | 
| -  scope.register = dispatcher.register.bind(dispatcher); | 
| +  scope.register = function(root) { | 
| +    dispatcher.register(root); | 
| +  }; | 
| scope.unregister = dispatcher.unregister.bind(dispatcher); | 
| +  scope.wrap = wrap; | 
| })(window.PolymerGestures); | 
|  | 
| /* | 
| @@ -916,9 +964,12 @@ window.PolymerGestures = {}; | 
| events: [ | 
| 'mousedown', | 
| 'mousemove', | 
| -      'mouseup', | 
| +      'mouseup' | 
| ], | 
| register: function(target) { | 
| +      if (target !== document) { | 
| +        return; | 
| +      } | 
| dispatcher.listen(target, this.events); | 
| }, | 
| unregister: function(target) { | 
| @@ -942,6 +993,7 @@ window.PolymerGestures = {}; | 
| e.pointerId = this.POINTER_ID; | 
| e.isPrimary = true; | 
| e.pointerType = this.POINTER_TYPE; | 
| +      e._source = 'mouse'; | 
| if (!HAS_BUTTONS) { | 
| e.buttons = WHICH_TO_BUTTONS[e.which] || 0; | 
| } | 
| @@ -956,6 +1008,7 @@ window.PolymerGestures = {}; | 
| this.mouseup(inEvent); | 
| } | 
| var e = this.prepareEvent(inEvent); | 
| +        e.target = scope.wrap(scope.findTarget(inEvent)); | 
| pointermap.set(this.POINTER_ID, e.target); | 
| dispatcher.down(e); | 
| } | 
| @@ -970,7 +1023,7 @@ window.PolymerGestures = {}; | 
| mouseup: function(inEvent) { | 
| if (!this.isEventSimulatedFromTouch(inEvent)) { | 
| var e = this.prepareEvent(inEvent); | 
| -        e.relatedTarget = e.target; | 
| +        e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); | 
| e.target = pointermap.get(this.POINTER_ID); | 
| dispatcher.up(e); | 
| this.cleanupMouse(); | 
| @@ -1001,9 +1054,12 @@ window.PolymerGestures = {}; | 
| // This should be long enough to ignore compat mouse events made by touch | 
| var DEDUP_TIMEOUT = 2500; | 
| var CLICK_COUNT_TIMEOUT = 200; | 
| +  var HYSTERESIS = 20; | 
| var ATTRIB = 'touch-action'; | 
| var INSTALLER; | 
| -  var HAS_TOUCH_ACTION = typeof document.head.style.touchAction === 'string'; | 
| +  // maybe one day... | 
| +  // var CAN_USE_GLOBAL = ATTRIB in document.head.style; | 
| +  var CAN_USE_GLOBAL = false; | 
|  | 
| // handler block for native touch events | 
| var touchEvents = { | 
| @@ -1014,14 +1070,14 @@ window.PolymerGestures = {}; | 
| 'touchcancel' | 
| ], | 
| register: function(target) { | 
| -      if (HAS_TOUCH_ACTION) { | 
| +      if (CAN_USE_GLOBAL) { | 
| dispatcher.listen(target, this.events); | 
| } else { | 
| INSTALLER.enableOnSubtree(target); | 
| } | 
| }, | 
| unregister: function(target) { | 
| -      if (HAS_TOUCH_ACTION) { | 
| +      if (CAN_USE_GLOBAL) { | 
| dispatcher.unlisten(target, this.events); | 
| } else { | 
| // TODO(dfreedman): is it worth it to disconnect the MO? | 
| @@ -1069,7 +1125,7 @@ window.PolymerGestures = {}; | 
| EMITTER: 'none', | 
| XSCROLLER: 'pan-x', | 
| YSCROLLER: 'pan-y', | 
| -      SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/ | 
| +      SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto|manipulation$/ | 
| }, | 
| touchActionToScrollType: function(touchAction) { | 
| var t = touchAction; | 
| @@ -1094,7 +1150,7 @@ window.PolymerGestures = {}; | 
| if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointermap.has(1))) { | 
| this.firstTouch = inTouch.identifier; | 
| this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; | 
| -        this.scrolling = false; | 
| +        this.scrolling = null; | 
| this.cancelResetClickCount(); | 
| } | 
| }, | 
| @@ -1128,7 +1184,17 @@ window.PolymerGestures = {}; | 
| }, | 
| findTarget: function(touch, id) { | 
| if (this.currentTouchEvent.type === 'touchstart') { | 
| -        return scope.findTarget(touch); | 
| +        if (this.isPrimaryTouch(touch)) { | 
| +          var fastPath = { | 
| +            clientX: touch.clientX, | 
| +            clientY: touch.clientY, | 
| +            path: this.currentTouchEvent.path, | 
| +            target: scope.wrap(this.currentTouchEvent.target) | 
| +          }; | 
| +          return scope.findTarget(fastPath); | 
| +        } else { | 
| +          return scope.findTarget(touch); | 
| +        } | 
| } | 
| // reuse target we found in touchstart | 
| return pointermap.get(id); | 
| @@ -1140,7 +1206,7 @@ window.PolymerGestures = {}; | 
| // Touch identifiers can start at 0. | 
| // Add 2 to the touch identifier for compatibility. | 
| var id = e.pointerId = inTouch.identifier + 2; | 
| -      e.target = this.findTarget(inTouch, id); | 
| +      e.target = scope.wrap(this.findTarget(inTouch, id)); | 
| e.bubbles = true; | 
| e.cancelable = true; | 
| e.detail = this.clickCount; | 
| @@ -1150,6 +1216,7 @@ window.PolymerGestures = {}; | 
| e.pressure = inTouch.webkitForce || inTouch.force || 0.5; | 
| e.isPrimary = this.isPrimaryTouch(inTouch); | 
| e.pointerType = this.POINTER_TYPE; | 
| +      e._source = 'touch'; | 
| // forward touch preventDefaults | 
| var self = this; | 
| e.preventDefault = function() { | 
| @@ -1162,9 +1229,18 @@ window.PolymerGestures = {}; | 
| processTouches: function(inEvent, inFunction) { | 
| var tl = inEvent.changedTouches; | 
| this.currentTouchEvent = inEvent; | 
| -      for (var i = 0, t; i < tl.length; i++) { | 
| +      for (var i = 0, t, p; i < tl.length; i++) { | 
| t = tl[i]; | 
| -        inFunction.call(this, this.touchToPointer(t)); | 
| +        p = this.touchToPointer(t); | 
| +        if (inEvent.type === 'touchstart') { | 
| +          pointermap.set(p.pointerId, p.target); | 
| +        } | 
| +        if (pointermap.has(p.pointerId)) { | 
| +          inFunction.call(this, p); | 
| +        } | 
| +        if (inEvent.type === 'touchend' || inEvent._cancel) { | 
| +          this.cleanUpPointer(p); | 
| +        } | 
| } | 
| }, | 
| // For single axis scrollers, determines whether the element should emit | 
| @@ -1172,7 +1248,7 @@ window.PolymerGestures = {}; | 
| shouldScroll: function(inEvent) { | 
| if (this.firstXY) { | 
| var ret; | 
| -        var scrollAxis = inEvent.currentTarget._scrollType; | 
| +        var scrollAxis = scope.targetFinding.findScrollAxis(inEvent); | 
| if (scrollAxis === 'none') { | 
| // this element is a touch-action: none, should never scroll | 
| ret = false; | 
| @@ -1190,7 +1266,6 @@ window.PolymerGestures = {}; | 
| // making events | 
| ret = da >= doa; | 
| } | 
| -        this.firstXY = null; | 
| return ret; | 
| } | 
| }, | 
| @@ -1218,11 +1293,14 @@ window.PolymerGestures = {}; | 
| // Touch identifiers are 2 smaller than their pointerId, which is the | 
| // index in pointermap. | 
| if (key !== 1 && !this.findTouch(tl, key - 2)) { | 
| -            var p = value.out; | 
| +            var p = value; | 
| d.push(p); | 
| } | 
| }, this); | 
| -        d.forEach(this.cancelOut, this); | 
| +        d.forEach(function(p) { | 
| +          this.cancel(p); | 
| +          pointermap.delete(p.pointerId); | 
| +        }); | 
| } | 
| }, | 
| touchstart: function(inEvent) { | 
| @@ -1235,30 +1313,34 @@ window.PolymerGestures = {}; | 
| } | 
| }, | 
| down: function(inPointer) { | 
| -      var p = pointermap.set(inPointer.pointerId, inPointer.target); | 
| dispatcher.down(inPointer); | 
| }, | 
| touchmove: function(inEvent) { | 
| -      if (HAS_TOUCH_ACTION) { | 
| +      if (CAN_USE_GLOBAL) { | 
| this.processTouches(inEvent, this.move); | 
| } else { | 
| if (!this.scrolling) { | 
| -          if (this.shouldScroll(inEvent)) { | 
| +          if (this.scrolling === null && this.shouldScroll(inEvent)) { | 
| this.scrolling = true; | 
| -            this.touchcancel(inEvent); | 
| } else { | 
| +            this.scrolling = false; | 
| inEvent.preventDefault(); | 
| this.processTouches(inEvent, this.move); | 
| } | 
| +        } else if (this.firstXY) { | 
| +          var t = inEvent.changedTouches[0]; | 
| +          var dx = t.clientX - this.firstXY.X; | 
| +          var dy = t.clientY - this.firstXY.Y; | 
| +          var dd = Math.sqrt(dx * dx + dy * dy); | 
| +          if (dd >= HYSTERESIS) { | 
| +            this.touchcancel(inEvent); | 
| +            this.scrolling = true; | 
| +            this.firstXY = null; | 
| +          } | 
| } | 
| } | 
| }, | 
| move: function(inPointer) { | 
| -      var pointer = pointermap.get(inPointer.pointerId); | 
| -      // a finger drifted off the screen, ignore it | 
| -      if (!pointer) { | 
| -        return; | 
| -      } | 
| dispatcher.move(inPointer); | 
| }, | 
| touchend: function(inEvent) { | 
| @@ -1266,18 +1348,14 @@ window.PolymerGestures = {}; | 
| this.processTouches(inEvent, this.up); | 
| }, | 
| up: function(inPointer) { | 
| -      if (!this.scrolling) { | 
| -        inPointer.relatedTarget = scope.findTarget(inPointer); | 
| -        dispatcher.up(inPointer); | 
| -      } | 
| -      this.cleanUpPointer(inPointer); | 
| +      inPointer.relatedTarget = scope.wrap(scope.findTarget(inPointer)); | 
| +      dispatcher.up(inPointer); | 
| }, | 
| cancel: function(inPointer) { | 
| -      inPointer.relatedTarget = scope.findTarget(inPointer); | 
| dispatcher.cancel(inPointer); | 
| -      this.cleanUpPointer(inPointer); | 
| }, | 
| touchcancel: function(inEvent) { | 
| +      inEvent._cancel = true; | 
| this.processTouches(inEvent, this.cancel); | 
| }, | 
| cleanUpPointer: function(inPointer) { | 
| @@ -1304,7 +1382,7 @@ window.PolymerGestures = {}; | 
| } | 
| }; | 
|  | 
| -  if (!HAS_TOUCH_ACTION) { | 
| +  if (!CAN_USE_GLOBAL) { | 
| INSTALLER = new scope.Installer(touchEvents.elementAdded, touchEvents.elementRemoved, touchEvents.elementChanged, touchEvents); | 
| } | 
|  | 
| @@ -1332,6 +1410,9 @@ window.PolymerGestures = {}; | 
| 'MSPointerCancel', | 
| ], | 
| register: function(target) { | 
| +      if (target !== document) { | 
| +        return; | 
| +      } | 
| dispatcher.listen(target, this.events); | 
| }, | 
| unregister: function(target) { | 
| @@ -1346,10 +1427,11 @@ window.PolymerGestures = {}; | 
| ], | 
| prepareEvent: function(inEvent) { | 
| var e = inEvent; | 
| +      e = dispatcher.cloneEvent(inEvent); | 
| if (HAS_BITMAP_TYPE) { | 
| -        e = dispatcher.cloneEvent(inEvent); | 
| e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; | 
| } | 
| +      e._source = 'ms'; | 
| return e; | 
| }, | 
| cleanup: function(id) { | 
| @@ -1357,6 +1439,7 @@ window.PolymerGestures = {}; | 
| }, | 
| MSPointerDown: function(inEvent) { | 
| var e = this.prepareEvent(inEvent); | 
| +      e.target = scope.wrap(scope.findTarget(inEvent)); | 
| pointermap.set(inEvent.pointerId, e.target); | 
| dispatcher.down(e); | 
| }, | 
| @@ -1367,14 +1450,14 @@ window.PolymerGestures = {}; | 
| }, | 
| MSPointerUp: function(inEvent) { | 
| var e = this.prepareEvent(inEvent); | 
| -      e.relatedTarget = e.target; | 
| +      e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); | 
| e.target = pointermap.get(e.pointerId); | 
| dispatcher.up(e); | 
| this.cleanup(inEvent.pointerId); | 
| }, | 
| MSPointerCancel: function(inEvent) { | 
| var e = this.prepareEvent(inEvent); | 
| -      e.relatedTarget = e.target; | 
| +      e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); | 
| e.target = pointermap.get(e.pointerId); | 
| dispatcher.cancel(e); | 
| this.cleanup(inEvent.pointerId); | 
| @@ -1404,9 +1487,14 @@ window.PolymerGestures = {}; | 
| 'pointercancel' | 
| ], | 
| prepareEvent: function(inEvent) { | 
| -      return dispatcher.cloneEvent(inEvent); | 
| +      var e = dispatcher.cloneEvent(inEvent); | 
| +      e._source = 'pointer'; | 
| +      return e; | 
| }, | 
| register: function(target) { | 
| +      if (target !== document) { | 
| +        return; | 
| +      } | 
| dispatcher.listen(target, this.events); | 
| }, | 
| unregister: function(target) { | 
| @@ -1417,6 +1505,7 @@ window.PolymerGestures = {}; | 
| }, | 
| pointerdown: function(inEvent) { | 
| var e = this.prepareEvent(inEvent); | 
| +      e.target = scope.wrap(scope.findTarget(inEvent)); | 
| pointermap.set(e.pointerId, e.target); | 
| dispatcher.down(e); | 
| }, | 
| @@ -1427,14 +1516,14 @@ window.PolymerGestures = {}; | 
| }, | 
| pointerup: function(inEvent) { | 
| var e = this.prepareEvent(inEvent); | 
| -      e.relatedTarget = e.target; | 
| +      e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); | 
| e.target = pointermap.get(e.pointerId); | 
| dispatcher.up(e); | 
| this.cleanup(inEvent.pointerId); | 
| }, | 
| pointercancel: function(inEvent) { | 
| var e = this.prepareEvent(inEvent); | 
| -      e.relatedTarget = e.target; | 
| +      e.relatedTarget = scope.wrap(scope.findTarget(inEvent)); | 
| e.target = pointermap.get(e.pointerId); | 
| dispatcher.cancel(e); | 
| this.cleanup(inEvent.pointerId); | 
| @@ -1632,7 +1721,8 @@ window.PolymerGestures = {}; | 
| trackInfo: t.trackInfo, | 
| relatedTarget: inEvent.relatedTarget, | 
| pointerType: inEvent.pointerType, | 
| -         pointerId: inEvent.pointerId | 
| +         pointerId: inEvent.pointerId, | 
| +         _source: 'track' | 
| }); | 
| t.downTarget.dispatchEvent(e); | 
| }, | 
| @@ -1731,7 +1821,7 @@ window.PolymerGestures = {}; | 
| /** | 
| * This event is fired when a held pointer is released or moved. | 
| * | 
| - * @class released | 
| + * @class release | 
| */ | 
|  | 
| (function(scope) { | 
| @@ -1793,7 +1883,8 @@ window.PolymerGestures = {}; | 
| pointerType: this.heldPointer.pointerType, | 
| pointerId: this.heldPointer.pointerId, | 
| x: this.heldPointer.clientX, | 
| -        y: this.heldPointer.clientY | 
| +        y: this.heldPointer.clientY, | 
| +        _source: 'hold' | 
| }; | 
| if (inHoldTime) { | 
| p.holdTime = inHoldTime; | 
| @@ -1886,7 +1977,8 @@ window.PolymerGestures = {}; | 
| altKey: inEvent.altKey, | 
| ctrlKey: inEvent.ctrlKey, | 
| metaKey: inEvent.metaKey, | 
| -            shiftKey: inEvent.shiftKey | 
| +            shiftKey: inEvent.shiftKey, | 
| +            _source: 'tap' | 
| }); | 
| t.dispatchEvent(e); | 
| } | 
| @@ -2658,17 +2750,22 @@ window.PolymerGestures = {}; | 
| } | 
|  | 
| function parseLeftHandSideExpression() { | 
| -        var expr, property; | 
| +        var expr, args, property; | 
|  | 
| expr = parsePrimaryExpression(); | 
|  | 
| -        while (match('.') || match('[')) { | 
| +        while (true) { | 
| if (match('[')) { | 
| property = parseComputedMember(); | 
| expr = delegate.createMemberExpression('[', expr, property); | 
| -            } else { | 
| +            } else if (match('.')) { | 
| property = parseNonComputedMember(); | 
| expr = delegate.createMemberExpression('.', expr, property); | 
| +            } else if (match('(')) { | 
| +                args = parseArguments(); | 
| +                expr = delegate.createCallExpression(expr, args); | 
| +            } else { | 
| +                break; | 
| } | 
| } | 
|  | 
| @@ -3041,34 +3138,31 @@ window.PolymerGestures = {}; | 
| }; | 
|  | 
| function MemberExpression(object, property, accessor) { | 
| +    this.computed = accessor == '['; | 
| + | 
| this.dynamicDeps = typeof object == 'function' || | 
| object.dynamicDeps || | 
| -                       (accessor == '[' && !(property instanceof Literal)); | 
| - | 
| -    // convert literal computed property access where literal value is a value | 
| -    // path to ident dot-access. | 
| -    if (accessor == '[' && | 
| -        property instanceof Literal && | 
| -        Path.get(property.value).valid) { | 
| -      accessor = '.'; | 
| -      property = new IdentPath(property.value); | 
| -    } | 
| +                       (this.computed && !(property instanceof Literal)); | 
|  | 
| this.simplePath = | 
| !this.dynamicDeps && | 
| -        property instanceof IdentPath && | 
| +        (property instanceof IdentPath || property instanceof Literal) && | 
| (object instanceof MemberExpression || object instanceof IdentPath); | 
|  | 
| this.object = this.simplePath ? object : getFn(object); | 
| -    this.property = accessor == '.' ? property : getFn(property); | 
| +    this.property = !this.computed || this.simplePath ? | 
| +        property : getFn(property); | 
| } | 
|  | 
| MemberExpression.prototype = { | 
| get fullPath() { | 
| if (!this.fullPath_) { | 
| -        var last = this.object instanceof IdentPath ? | 
| -            this.object.name : this.object.fullPath; | 
| -        this.fullPath_ = Path.get(last + '.' + this.property.name); | 
| + | 
| +        var parts = this.object instanceof MemberExpression ? | 
| +            this.object.fullPath.slice() : [this.object.name]; | 
| +        parts.push(this.property instanceof IdentPath ? | 
| +            this.property.name : this.property.value); | 
| +        this.fullPath_ = Path.get(parts); | 
| } | 
|  | 
| return this.fullPath_; | 
| @@ -3087,11 +3181,11 @@ window.PolymerGestures = {}; | 
|  | 
| return path.getValueFrom(model); | 
| }; | 
| -        } else if (this.property instanceof IdentPath) { | 
| +        } else if (!this.computed) { | 
| var path = Path.get(this.property.name); | 
|  | 
| -          this.valueFn_ = function(model, observer) { | 
| -            var context = object(model, observer); | 
| +          this.valueFn_ = function(model, observer, filterRegistry) { | 
| +            var context = object(model, observer, filterRegistry); | 
|  | 
| if (observer) | 
| observer.addPath(context, path); | 
| @@ -3102,11 +3196,11 @@ window.PolymerGestures = {}; | 
| // Computed property. | 
| var property = this.property; | 
|  | 
| -          this.valueFn_ = function(model, observer) { | 
| -            var context = object(model, observer); | 
| -            var propName = property(model, observer); | 
| +          this.valueFn_ = function(model, observer, filterRegistry) { | 
| +            var context = object(model, observer, filterRegistry); | 
| +            var propName = property(model, observer, filterRegistry); | 
| if (observer) | 
| -              observer.addPath(context, propName); | 
| +              observer.addPath(context, [propName]); | 
|  | 
| return context ? context[propName] : undefined; | 
| }; | 
| @@ -3137,8 +3231,8 @@ window.PolymerGestures = {}; | 
| } | 
|  | 
| Filter.prototype = { | 
| -    transform: function(value, toModelDirection, filterRegistry, model, | 
| -                        observer) { | 
| +    transform: function(model, observer, filterRegistry, toModelDirection, | 
| +                        initialArgs) { | 
| var fn = filterRegistry[this.name]; | 
| var context = model; | 
| if (fn) { | 
| @@ -3146,7 +3240,7 @@ window.PolymerGestures = {}; | 
| } else { | 
| fn = context[this.name]; | 
| if (!fn) { | 
| -          console.error('Cannot find filter: ' + this.name); | 
| +          console.error('Cannot find function or filter: ' + this.name); | 
| return; | 
| } | 
| } | 
| @@ -3161,14 +3255,13 @@ window.PolymerGestures = {}; | 
| } | 
|  | 
| if (typeof fn != 'function') { | 
| -        console.error('No ' + (toModelDirection ? 'toModel' : 'toDOM') + | 
| -                      ' found on' + this.name); | 
| +        console.error('Cannot find function or filter: ' + this.name); | 
| return; | 
| } | 
|  | 
| -      var args = [value]; | 
| +      var args = initialArgs || []; | 
| for (var i = 0; i < this.args.length; i++) { | 
| -        args[i + 1] = getFn(this.args[i])(model, observer); | 
| +        args.push(getFn(this.args[i])(model, observer, filterRegistry)); | 
| } | 
|  | 
| return fn.apply(context, args); | 
| @@ -3222,8 +3315,8 @@ window.PolymerGestures = {}; | 
|  | 
| argument = getFn(argument); | 
|  | 
| -      return function(model, observer) { | 
| -        return unaryOperators[op](argument(model, observer)); | 
| +      return function(model, observer, filterRegistry) { | 
| +        return unaryOperators[op](argument(model, observer, filterRegistry)); | 
| }; | 
| }, | 
|  | 
| @@ -3234,9 +3327,9 @@ window.PolymerGestures = {}; | 
| left = getFn(left); | 
| right = getFn(right); | 
|  | 
| -      return function(model, observer) { | 
| -        return binaryOperators[op](left(model, observer), | 
| -                                   right(model, observer)); | 
| +      return function(model, observer, filterRegistry) { | 
| +        return binaryOperators[op](left(model, observer, filterRegistry), | 
| +                                   right(model, observer, filterRegistry)); | 
| }; | 
| }, | 
|  | 
| @@ -3245,9 +3338,10 @@ window.PolymerGestures = {}; | 
| consequent = getFn(consequent); | 
| alternate = getFn(alternate); | 
|  | 
| -      return function(model, observer) { | 
| -        return test(model, observer) ? | 
| -            consequent(model, observer) : alternate(model, observer); | 
| +      return function(model, observer, filterRegistry) { | 
| +        return test(model, observer, filterRegistry) ? | 
| +            consequent(model, observer, filterRegistry) : | 
| +            alternate(model, observer, filterRegistry); | 
| } | 
| }, | 
|  | 
| @@ -3264,6 +3358,17 @@ window.PolymerGestures = {}; | 
| return ex; | 
| }, | 
|  | 
| +    createCallExpression: function(expression, args) { | 
| +      if (!(expression instanceof IdentPath)) | 
| +        throw Error('Only identifier function invocations are allowed'); | 
| + | 
| +      var filter = new Filter(expression.name, args); | 
| + | 
| +      return function(model, observer, filterRegistry) { | 
| +        return filter.transform(model, observer, filterRegistry, false); | 
| +      }; | 
| +    }, | 
| + | 
| createLiteral: function(token) { | 
| return new Literal(token.value); | 
| }, | 
| @@ -3272,10 +3377,10 @@ window.PolymerGestures = {}; | 
| for (var i = 0; i < elements.length; i++) | 
| elements[i] = getFn(elements[i]); | 
|  | 
| -      return function(model, observer) { | 
| +      return function(model, observer, filterRegistry) { | 
| var arr = [] | 
| for (var i = 0; i < elements.length; i++) | 
| -          arr.push(elements[i](model, observer)); | 
| +          arr.push(elements[i](model, observer, filterRegistry)); | 
| return arr; | 
| } | 
| }, | 
| @@ -3291,10 +3396,11 @@ window.PolymerGestures = {}; | 
| for (var i = 0; i < properties.length; i++) | 
| properties[i].value = getFn(properties[i].value); | 
|  | 
| -      return function(model, observer) { | 
| +      return function(model, observer, filterRegistry) { | 
| var obj = {}; | 
| for (var i = 0; i < properties.length; i++) | 
| -          obj[properties[i].key] = properties[i].value(model, observer); | 
| +          obj[properties[i].key] = | 
| +              properties[i].value(model, observer, filterRegistry); | 
| return obj; | 
| } | 
| }, | 
| @@ -3385,10 +3491,10 @@ window.PolymerGestures = {}; | 
| }, | 
|  | 
| getValue: function(model, observer, filterRegistry) { | 
| -      var value = getFn(this.expression)(model, observer); | 
| +      var value = getFn(this.expression)(model, observer, filterRegistry); | 
| for (var i = 0; i < this.filters.length; i++) { | 
| -        value = this.filters[i].transform(value, false, filterRegistry, model, | 
| -                                          observer); | 
| +        value = this.filters[i].transform(model, observer, filterRegistry, | 
| +            false, [value]); | 
| } | 
|  | 
| return value; | 
| @@ -3397,8 +3503,8 @@ window.PolymerGestures = {}; | 
| setValue: function(model, newValue, filterRegistry) { | 
| var count = this.filters ? this.filters.length : 0; | 
| while (count-- > 0) { | 
| -        newValue = this.filters[count].transform(newValue, true, filterRegistry, | 
| -                                                 model); | 
| +        newValue = this.filters[count].transform(model, undefined, | 
| +            filterRegistry, true, [newValue]); | 
| } | 
|  | 
| if (this.expression.setValue) | 
| @@ -3510,19 +3616,33 @@ window.PolymerGestures = {}; | 
| var indexName = template.polymerExpressionIndexIdent_; | 
|  | 
| return function(model) { | 
| -        var scope = Object.create(parentScope); | 
| -        scope[scopeName] = model; | 
| -        scope[indexName] = undefined; | 
| -        scope[parentScopeName] = parentScope; | 
| -        return scope; | 
| +        return createScopeObject(parentScope, model, scopeName, indexName); | 
| }; | 
| } | 
| }; | 
|  | 
| -  global.PolymerExpressions = PolymerExpressions; | 
| -  if (global.exposeGetExpression) | 
| -    global.getExpression_ = getExpression; | 
| +  var createScopeObject = ('__proto__' in {}) ? | 
| +    function(parentScope, model, scopeName, indexName) { | 
| +      var scope = {}; | 
| +      scope[scopeName] = model; | 
| +      scope[indexName] = undefined; | 
| +      scope[parentScopeName] = parentScope; | 
| +      scope.__proto__ = parentScope; | 
| +      return scope; | 
| +    } : | 
| +    function(parentScope, model, scopeName, indexName) { | 
| +      var scope = Object.create(parentScope); | 
| +      Object.defineProperty(scope, scopeName, | 
| +          { value: model, configurable: true, writable: true }); | 
| +      Object.defineProperty(scope, indexName, | 
| +          { value: undefined, configurable: true, writable: true }); | 
| +      Object.defineProperty(scope, parentScopeName, | 
| +          { value: parentScope, configurable: true, writable: true }); | 
| +      return scope; | 
| +    }; | 
|  | 
| +  global.PolymerExpressions = PolymerExpressions; | 
| +  PolymerExpressions.getExpression = getExpression; | 
| })(this); | 
|  | 
| /* | 
| @@ -3534,7 +3654,7 @@ window.PolymerGestures = {}; | 
| * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt | 
| */ | 
| Polymer = { | 
| -  version: '0.3.1-604ba08' | 
| +  version: '0.3.3-0e73963' | 
| }; | 
|  | 
| /* | 
| @@ -4013,7 +4133,7 @@ if (typeof window.Polymer === 'function') { | 
| this.async("fire", arguments); | 
| }, | 
| /** | 
| -      * Remove class from old, add class to anew, if they exist | 
| +      * Remove class from old, add class to anew, if they exist. | 
| * @param classFollows | 
| * @param anew A node. | 
| * @param old A node | 
| @@ -4026,6 +4146,22 @@ if (typeof window.Polymer === 'function') { | 
| if (anew) { | 
| anew.classList.add(className); | 
| } | 
| +    }, | 
| +    /** | 
| +      * Inject HTML which contains markup bound to this element into | 
| +      * a target element (replacing target element content). | 
| +      * @param String html to inject | 
| +      * @param Element target element | 
| +      */ | 
| +    injectBoundHTML: function(html, element) { | 
| +      var template = document.createElement('template'); | 
| +      template.innerHTML = html; | 
| +      var fragment = this.instanceTemplate(template); | 
| +      if (element) { | 
| +        element.textContent = ''; | 
| +        element.appendChild(fragment); | 
| +      } | 
| +      return fragment; | 
| } | 
| }; | 
|  | 
| @@ -4222,15 +4358,44 @@ if (typeof window.Polymer === 'function') { | 
|  | 
| var empty = []; | 
|  | 
| +  var updateRecord = { | 
| +    object: undefined, | 
| +    type: 'update', | 
| +    name: undefined, | 
| +    oldValue: undefined | 
| +  }; | 
| + | 
| +  var numberIsNaN = Number.isNaN || function(value) { | 
| +    return typeof value === 'number' && isNaN(value); | 
| +  } | 
| + | 
| +  function areSameValue(left, right) { | 
| +    if (left === right) | 
| +      return left !== 0 || 1 / left === 1 / right; | 
| +    if (numberIsNaN(left) && numberIsNaN(right)) | 
| +      return true; | 
| + | 
| +    return left !== left && right !== right; | 
| +  } | 
| + | 
| +  // capture A's value if B's value is null or undefined, | 
| +  // otherwise use B's value | 
| +  function resolveBindingValue(oldValue, value) { | 
| +    if (value === undefined && oldValue === null) { | 
| +      return value; | 
| +    } | 
| +    return (value === null || value === undefined) ? oldValue : value; | 
| +  } | 
| + | 
| var properties = { | 
| createPropertyObserver: function() { | 
| var n$ = this._observeNames; | 
| if (n$ && n$.length) { | 
| var o = this._propertyObserver = new CompoundObserver(true); | 
| -        this.registerObservers([o]); | 
| +        this.registerObserver(o); | 
| // TODO(sorvell): may not be kosher to access the value here (this[n]); | 
| // previously we looked at the descriptor on the prototype | 
| -        // this doesn't work for inheritance and not for accessors without | 
| +        // this doesn't work for inheritance and not for accessors without | 
| // a value property | 
| for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { | 
| o.addPath(this, n); | 
| @@ -4297,12 +4462,83 @@ if (typeof window.Polymer === 'function') { | 
| } | 
| } | 
| }, | 
| +    emitPropertyChangeRecord: function(name, value, oldValue) { | 
| +      var object = this; | 
| +      if (areSameValue(value, oldValue)) | 
| +        return; | 
| + | 
| +      this.propertyChanged_(name, value, oldValue); | 
| + | 
| +      if (!Observer.hasObjectObserve) | 
| +        return; | 
| + | 
| +      var notifier = this.notifier_; | 
| +      if (!notifier) | 
| +        notifier = this.notifier_ = Object.getNotifier(this); | 
| + | 
| +      updateRecord.object = this; | 
| +      updateRecord.name = name; | 
| +      updateRecord.oldValue = oldValue; | 
| + | 
| +      notifier.notify(updateRecord); | 
| +    }, | 
| +    bindToAccessor: function(name, observable, resolveFn) { | 
| +      var privateName = name + '_'; | 
| +      var privateObservable  = name + 'Observable_'; | 
| + | 
| +      this[privateObservable] = observable; | 
| +      var oldValue = this[privateName]; | 
| + | 
| +      var self = this; | 
| +      var value = observable.open(function(value, oldValue) { | 
| +        self[privateName] = value; | 
| +        self.emitPropertyChangeRecord(name, value, oldValue); | 
| +      }); | 
| + | 
| +      if (resolveFn && !areSameValue(oldValue, value)) { | 
| +        var resolvedValue = resolveFn(oldValue, value); | 
| +        if (!areSameValue(value, resolvedValue)) { | 
| +          value = resolvedValue; | 
| +          if (observable.setValue) | 
| +            observable.setValue(value); | 
| +        } | 
| +      } | 
| + | 
| +      this[privateName] = value; | 
| +      this.emitPropertyChangeRecord(name, value, oldValue); | 
| + | 
| +      var observer = { | 
| +        close: function() { | 
| +          observable.close(); | 
| +          self[privateObservable] = undefined; | 
| +        } | 
| +      }; | 
| +      this.registerObserver(observer); | 
| +      return observer; | 
| +    }, | 
| +    createComputedProperties: function() { | 
| +      if (!this._computedNames) { | 
| +        return; | 
| +      } | 
| + | 
| +      for (var i = 0; i < this._computedNames.length; i++) { | 
| +        var name = this._computedNames[i]; | 
| +        var expressionText = this.computed[name]; | 
| +        try { | 
| +          var expression = PolymerExpressions.getExpression(expressionText); | 
| +          var observable = expression.getBinding(this, this.element.syntax); | 
| +          this.bindToAccessor(name, observable); | 
| +        } catch (ex) { | 
| +          console.error('Failed to create computed property', ex); | 
| +        } | 
| +      } | 
| +    }, | 
| bindProperty: function(property, observable, oneTime) { | 
| if (oneTime) { | 
| this[property] = observable; | 
| return; | 
| } | 
| -      return bindProperties(this, property, observable); | 
| +      return this.bindToAccessor(property, observable, resolveBindingValue); | 
| }, | 
| invokeMethod: function(method, args) { | 
| var fn = this[method] || method; | 
| @@ -4310,27 +4546,29 @@ if (typeof window.Polymer === 'function') { | 
| fn.apply(this, args); | 
| } | 
| }, | 
| -    registerObservers: function(observers) { | 
| -      this._observers = this._observers || []; | 
| -      this._observers.push(observers); | 
| +    registerObserver: function(observer) { | 
| +      if (!this._observers) { | 
| +        this._observers = [observer]; | 
| +        return; | 
| +      } | 
| + | 
| +      this._observers.push(observer); | 
| }, | 
| // observer array items are arrays of observers. | 
| closeObservers: function() { | 
| if (!this._observers) { | 
| return; | 
| } | 
| -      for (var i=0, l=this._observers.length; i<l; i++) { | 
| -        this.closeObserverArray(this._observers[i]); | 
| -      } | 
| -      this._observers = []; | 
| -    }, | 
| -    closeObserverArray: function(observerArray) { | 
| -      for (var i=0, l=observerArray.length, o; i<l; i++) { | 
| -        o = observerArray[i]; | 
| -        if (o && o.close) { | 
| -          o.close(); | 
| + | 
| +      var observers = this._observers; | 
| +      for (var i = 0; i < observers.length; i++) { | 
| +        var observer = observers[i]; | 
| +        if (observer && typeof observer.close == 'function') { | 
| +          observer.close(); | 
| } | 
| } | 
| + | 
| +      this._observers = []; | 
| }, | 
| // bookkeeping observers for memory management | 
| registerNamedObserver: function(name, observer) { | 
| @@ -4355,23 +4593,6 @@ if (typeof window.Polymer === 'function') { | 
| } | 
| }; | 
|  | 
| -  // property binding | 
| -  // bind a property in A to a path in B by converting A[property] to a | 
| -  // getter/setter pair that accesses B[...path...] | 
| -  function bindProperties(a, property, observable) { | 
| -    // apply Polymer two-way reference binding | 
| -    return Observer.bindToInstance(a, property, observable, resolveBindingValue); | 
| -  } | 
| - | 
| -  // capture A's value if B's value is null or undefined, | 
| -  // otherwise use B's value | 
| -  function resolveBindingValue(oldValue, value) { | 
| -    if (value === undefined && oldValue === null) { | 
| -      return value; | 
| -    } | 
| -    return (value === null || value === undefined) ? oldValue : value; | 
| -  } | 
| - | 
| // logging | 
| var LOG_OBSERVE = '[%s] watching [%s]'; | 
| var LOG_OBSERVED = '[%s#%s] watch: [%s] now [%s] was [%s]'; | 
| @@ -4405,7 +4626,10 @@ if (typeof window.Polymer === 'function') { | 
| var syntax = this.syntax || (!template.bindingDelegate && | 
| this.element.syntax); | 
| var dom = template.createInstance(this, syntax); | 
| -      this.registerObservers(dom.bindings_); | 
| +      var observers = dom.bindings_; | 
| +      for (var i = 0; i < observers.length; i++) { | 
| +        this.registerObserver(observers[i]); | 
| +      } | 
| return dom; | 
| }, | 
| bind: function(name, observable, oneTime) { | 
| @@ -4558,6 +4782,7 @@ if (typeof window.Polymer === 'function') { | 
| return; | 
| } | 
| this._readied = true; | 
| +      this.createComputedProperties(); | 
| // TODO(sorvell): We could create an entry point here | 
| // for the user to compute property values. | 
| // process declarative resources | 
| @@ -4582,7 +4807,7 @@ if (typeof window.Polymer === 'function') { | 
| if (this.enteredView) { | 
| this.enteredView(); | 
| } | 
| -      // NOTE: domReady can be used to access elements in dom (descendants, | 
| +      // NOTE: domReady can be used to access elements in dom (descendants, | 
| // ancestors, siblings) such that the developer is enured to upgrade | 
| // ordering. If the element definitions have loaded, domReady | 
| // can be used to access upgraded elements. | 
| @@ -4647,7 +4872,7 @@ if (typeof window.Polymer === 'function') { | 
| // make a shadow root | 
| var root = this.createShadowRoot(); | 
| // stamp template | 
| -        // which includes parsing and applying MDV bindings before being | 
| +        // which includes parsing and applying MDV bindings before being | 
| // inserted (to avoid {{}} in attribute values) | 
| // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. | 
| var dom = this.instanceTemplate(template); | 
| @@ -4664,17 +4889,17 @@ if (typeof window.Polymer === 'function') { | 
| if (template) { | 
| // TODO(sorvell): mark this element as an eventController so that | 
| // event listeners on bound nodes inside it will be called on it. | 
| -        // Note, the expectation here is that events on all descendants | 
| +        // Note, the expectation here is that events on all descendants | 
| // should be handled by this element. | 
| this.eventController = this; | 
| // stamp template | 
| -        // which includes parsing and applying MDV bindings before being | 
| +        // which includes parsing and applying MDV bindings before being | 
| // inserted (to avoid {{}} in attribute values) | 
| // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. | 
| var dom = this.instanceTemplate(template); | 
| // append to shadow dom | 
| if (refNode) { | 
| -          this.insertBefore(dom, refNode); | 
| +          this.insertBefore(dom, refNode); | 
| } else { | 
| this.appendChild(dom); | 
| } | 
| @@ -4722,7 +4947,7 @@ if (typeof window.Polymer === 'function') { | 
|  | 
| // true if object has own PolymerBase api | 
| function isBase(object) { | 
| -    return object.hasOwnProperty('PolymerBase') | 
| +    return object.hasOwnProperty('PolymerBase') | 
| } | 
|  | 
| // name a base constructor for dev tools | 
| @@ -4730,13 +4955,13 @@ if (typeof window.Polymer === 'function') { | 
| function PolymerBase() {}; | 
| PolymerBase.prototype = base; | 
| base.constructor = PolymerBase; | 
| - | 
| + | 
| // exports | 
|  | 
| scope.Base = PolymerBase; | 
| scope.isBase = isBase; | 
| scope.api.instance.base = base; | 
| - | 
| + | 
| })(Polymer); | 
|  | 
| /* | 
| @@ -5401,6 +5626,13 @@ scope.api.declaration.path = path; | 
| a.push(n); | 
| } | 
| } | 
| +      if (prototype.computed) { | 
| +        // construct name list | 
| +        var a = prototype._computedNames = []; | 
| +        for (var n in prototype.computed) { | 
| +          a.push(n); | 
| +        } | 
| +      } | 
| }, | 
| publishProperties: function(prototype, base) { | 
| // if we have any properties to publish | 
| @@ -5412,17 +5644,17 @@ scope.api.declaration.path = path; | 
| prototype._publishLC = this.lowerCaseMap(publish); | 
| } | 
| }, | 
| -    // sync prototype to property descriptors; | 
| -    // desriptor format contains default value and optionally a | 
| +    // sync prototype to property descriptors; | 
| +    // desriptor format contains default value and optionally a | 
| // hint for reflecting the property to an attribute. | 
| // e.g. {foo: 5, bar: {value: true, reflect: true}} | 
| // reflect: {foo: true} is also supported | 
| -    // | 
| +    // | 
| requireProperties: function(propertyDescriptors, prototype, base) { | 
| // reflected properties | 
| prototype.reflect = prototype.reflect || {}; | 
| // ensure a prototype value for each property | 
| -      // and update the property's reflect to attribute status | 
| +      // and update the property's reflect to attribute status | 
| for (var n in propertyDescriptors) { | 
| var propertyDescriptor = propertyDescriptors[n]; | 
| var reflects = this.reflectHintForDescriptor(propertyDescriptor); | 
| @@ -5430,12 +5662,12 @@ scope.api.declaration.path = path; | 
| prototype.reflect[n] = reflects; | 
| } | 
| if (prototype[n] === undefined) { | 
| -          prototype[n] = this.valueForDescriptor(propertyDescriptor); | 
| +          prototype[n] = this.valueForDescriptor(propertyDescriptor); | 
| } | 
| } | 
| }, | 
| valueForDescriptor: function(propertyDescriptor) { | 
| -      var value = typeof propertyDescriptor === 'object' && | 
| +      var value = typeof propertyDescriptor === 'object' && | 
| propertyDescriptor ? propertyDescriptor.value : propertyDescriptor; | 
| return value !== undefined ? value : null; | 
| }, | 
| @@ -5453,13 +5685,52 @@ scope.api.declaration.path = path; | 
| } | 
| return map; | 
| }, | 
| +    createPropertyAccessor: function(name) { | 
| +      var proto = this.prototype; | 
| + | 
| +      var privateName = name + '_'; | 
| +      var privateObservable  = name + 'Observable_'; | 
| +      proto[privateName] = proto[name]; | 
| + | 
| +      Object.defineProperty(proto, name, { | 
| +        get: function() { | 
| +          var observable = this[privateObservable]; | 
| +          if (observable) | 
| +            observable.deliver(); | 
| + | 
| +          return this[privateName]; | 
| +        }, | 
| +        set: function(value) { | 
| +          var observable = this[privateObservable]; | 
| +          if (observable) { | 
| +            observable.setValue(value); | 
| +            return; | 
| +          } | 
| + | 
| +          var oldValue = this[privateName]; | 
| +          this[privateName] = value; | 
| +          this.emitPropertyChangeRecord(name, value, oldValue); | 
| + | 
| +          return value; | 
| +        }, | 
| +        configurable: true | 
| +      }); | 
| +    }, | 
| createPropertyAccessors: function(prototype) { | 
| var n$ = prototype._publishNames; | 
| if (n$ && n$.length) { | 
| for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { | 
| -          Observer.createBindablePrototypeAccessor(prototype, n); | 
| +          this.createPropertyAccessor(n); | 
| +        } | 
| +      } | 
| + | 
| +      var n$ = prototype._computedNames; | 
| +      if (n$ && n$.length) { | 
| +        for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { | 
| +          this.createPropertyAccessor(n); | 
| } | 
| } | 
| + | 
| } | 
| }; | 
|  | 
| @@ -5507,11 +5778,24 @@ scope.api.declaration.path = path; | 
| for (var i=0, l=names.length, n; i<l; i++) { | 
| // remove excess ws | 
| n = names[i].trim(); | 
| -          // do not override explicit entries | 
| -          if (n && publish[n] === undefined && base[n] === undefined) { | 
| +          // if the user hasn't specified a value, we want to use the | 
| +          // default, unless a superclass has already chosen one | 
| +          if (n && publish[n] === undefined) { | 
| +            // TODO(sjmiles): querying native properties on IE11 (and possibly | 
| +            // on other browsers) throws an exception because there is no actual | 
| +            // instance. | 
| +            // In fact, trying to publish native properties is known bad for this | 
| +            // and other reasons, and we need to solve this problem writ large. | 
| +            try { | 
| +              var hasValue = (base[n] !== undefined); | 
| +            } catch(x) { | 
| +              hasValue = false; | 
| +            } | 
| // supply an empty 'descriptor' object and let the publishProperties | 
| // code determine a default | 
| -            publish[n] = Polymer.nob; | 
| +            if (!hasValue) { | 
| +              publish[n] = Polymer.nob; | 
| +            } | 
| } | 
| } | 
| } | 
|  |