Index: dart/pkg/web_components/lib/platform.concat.js |
=================================================================== |
--- dart/pkg/web_components/lib/platform.concat.js (revision 37358) |
+++ dart/pkg/web_components/lib/platform.concat.js (working copy) |
@@ -1,3 +1,71 @@ |
+/** |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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.Platform = window.Platform || {}; |
+// prepopulate window.logFlags if necessary |
+window.logFlags = window.logFlags || {}; |
+// process flags |
+(function(scope){ |
+ // import |
+ var flags = scope.flags || {}; |
+ // populate flags from location |
+ location.search.slice(1).split('&').forEach(function(o) { |
+ o = o.split('='); |
+ o[0] && (flags[o[0]] = o[1] || true); |
+ }); |
+ var entryPoint = document.currentScript || |
+ document.querySelector('script[src*="platform.js"]'); |
+ if (entryPoint) { |
+ var a = entryPoint.attributes; |
+ for (var i = 0, n; i < a.length; i++) { |
+ n = a[i]; |
+ if (n.name !== 'src') { |
+ flags[n.name] = n.value || true; |
+ } |
+ } |
+ } |
+ if (flags.log) { |
+ flags.log.split(',').forEach(function(f) { |
+ window.logFlags[f] = true; |
+ }); |
+ } |
+ // If any of these flags match 'native', then force native ShadowDOM; any |
+ // other truthy value, or failure to detect native |
+ // ShadowDOM, results in polyfill |
+ flags.shadow = flags.shadow || flags.shadowdom || flags.polyfill; |
+ if (flags.shadow === 'native') { |
+ flags.shadow = false; |
+ } else { |
+ flags.shadow = flags.shadow || !HTMLElement.prototype.createShadowRoot; |
+ } |
+ |
+ if (flags.shadow && document.querySelectorAll('script').length > 1) { |
+ console.warn('platform.js is not the first script on the page. ' + |
+ 'See http://www.polymer-project.org/docs/start/platform.html#setup ' + |
+ 'for details.'); |
+ } |
+ |
+ // CustomElements polyfill flag |
+ if (flags.register) { |
+ window.CustomElements = window.CustomElements || {flags: {}}; |
+ window.CustomElements.flags.register = flags.register; |
+ } |
+ |
+ if (flags.imports) { |
+ window.HTMLImports = window.HTMLImports || {flags: {}}; |
+ window.HTMLImports.flags.imports = flags.imports; |
+ } |
+ |
+ // export |
+ scope.flags = flags; |
+})(Platform); |
+ |
/* |
* Copyright 2012 The Polymer Authors. All rights reserved. |
* Use of this source code is governed by a BSD-style |
@@ -96,12 +164,9 @@ |
var hasObserve = detectObjectObserve(); |
function detectEval() { |
- // don't test for eval if document has CSP securityPolicy object and we can see that |
- // eval is not supported. This avoids an error message in console even when the exception |
- // is caught |
- if (global.document && |
- 'securityPolicy' in global.document && |
- !global.document.securityPolicy.allowsEval) { |
+ // Don't test for eval if we're running in a Chrome App environment. |
+ // We check for APIs set that only exist in a Chrome App context. |
+ if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { |
return false; |
} |
@@ -249,7 +314,7 @@ |
obj = obj[this[i - 1]]; |
if (!isObject(obj)) |
return; |
- observe(obj); |
+ observe(obj, this[0]); |
} |
}, |
@@ -438,6 +503,28 @@ |
}; |
} |
+ /* |
+ * The observedSet abstraction is a perf optimization which reduces the total |
+ * number of Object.observe observations of a set of objects. The idea is that |
+ * groups of Observers will have some object dependencies in common and this |
+ * observed set ensures that each object in the transitive closure of |
+ * dependencies is only observed once. The observedSet acts as a write barrier |
+ * such that whenever any change comes through, all Observers are checked for |
+ * changed values. |
+ * |
+ * Note that this optimization is explicitly moving work from setup-time to |
+ * change-time. |
+ * |
+ * TODO(rafaelw): Implement "garbage collection". In order to move work off |
+ * the critical path, when Observers are closed, their observed objects are |
+ * not Object.unobserve(d). As a result, it's possible that if the observedSet |
+ * is kept open, but some Observers have been closed, it could cause "leaks" |
+ * (prevent otherwise collectable objects from being collected). At some |
+ * point, we should implement incremental "gc" which keeps a list of |
+ * observedSets which may need clean-up and does small amounts of cleanup on a |
+ * timeout until all is clean. |
+ */ |
+ |
function getObservedObject(observer, object, arrayObserve) { |
var dir = observedObjectCache.pop() || newObservedObject(); |
dir.open(observer); |
@@ -445,106 +532,80 @@ |
return dir; |
} |
- var emptyArray = []; |
var observedSetCache = []; |
function newObservedSet() { |
- var observers = []; |
var observerCount = 0; |
+ var observers = []; |
var objects = []; |
- var toRemove = emptyArray; |
- var resetNeeded = false; |
- var resetScheduled = false; |
+ var rootObj; |
+ var rootObjProps; |
- function observe(obj) { |
+ function observe(obj, prop) { |
if (!obj) |
return; |
- var index = toRemove.indexOf(obj); |
- if (index >= 0) { |
- toRemove[index] = undefined; |
+ if (obj === rootObj) |
+ rootObjProps[prop] = true; |
+ |
+ if (objects.indexOf(obj) < 0) { |
objects.push(obj); |
- } else if (objects.indexOf(obj) < 0) { |
- objects.push(obj); |
Object.observe(obj, callback); |
} |
- observe(Object.getPrototypeOf(obj)); |
+ observe(Object.getPrototypeOf(obj), prop); |
} |
- function reset() { |
- var objs = toRemove === emptyArray ? [] : toRemove; |
- toRemove = objects; |
- objects = objs; |
- |
- var observer; |
- for (var id in observers) { |
- observer = observers[id]; |
- if (!observer || observer.state_ != OPENED) |
- continue; |
- |
- observer.iterateObjects_(observe); |
+ function allRootObjNonObservedProps(recs) { |
+ for (var i = 0; i < recs.length; i++) { |
+ var rec = recs[i]; |
+ if (rec.object !== rootObj || |
+ rootObjProps[rec.name] || |
+ rec.type === 'setPrototype') { |
+ return false; |
+ } |
} |
- |
- for (var i = 0; i < toRemove.length; i++) { |
- var obj = toRemove[i]; |
- if (obj) |
- Object.unobserve(obj, callback); |
- } |
- |
- toRemove.length = 0; |
+ return true; |
} |
- function scheduledReset() { |
- resetScheduled = false; |
- if (!resetNeeded) |
+ function callback(recs) { |
+ if (allRootObjNonObservedProps(recs)) |
return; |
- reset(); |
- } |
- |
- function scheduleReset() { |
- if (resetScheduled) |
- return; |
- |
- resetNeeded = true; |
- resetScheduled = true; |
- runEOM(scheduledReset); |
- } |
- |
- function callback() { |
- reset(); |
- |
var observer; |
+ for (var i = 0; i < observers.length; i++) { |
+ observer = observers[i]; |
+ if (observer.state_ == OPENED) { |
+ observer.iterateObjects_(observe); |
+ } |
+ } |
- for (var id in observers) { |
- observer = observers[id]; |
- if (!observer || observer.state_ != OPENED) |
- continue; |
- |
- observer.check_(); |
+ for (var i = 0; i < observers.length; i++) { |
+ observer = observers[i]; |
+ if (observer.state_ == OPENED) { |
+ observer.check_(); |
+ } |
} |
} |
var record = { |
object: undefined, |
objects: objects, |
- open: function(obs) { |
- observers[obs.id_] = obs; |
+ open: function(obs, object) { |
+ if (!rootObj) { |
+ rootObj = object; |
+ rootObjProps = {}; |
+ } |
+ |
+ observers.push(obs); |
observerCount++; |
obs.iterateObjects_(observe); |
}, |
close: function(obs) { |
- var anyLeft = false; |
- |
- observers[obs.id_] = undefined; |
observerCount--; |
- |
- if (observerCount) { |
- scheduleReset(); |
+ if (observerCount > 0) { |
return; |
} |
- resetNeeded = false; |
for (var i = 0; i < objects.length; i++) { |
Object.unobserve(objects[i], callback); |
@@ -553,9 +614,10 @@ |
observers.length = 0; |
objects.length = 0; |
+ rootObj = undefined; |
+ rootObjProps = undefined; |
observedSetCache.push(this); |
- }, |
- reset: scheduleReset |
+ } |
}; |
return record; |
@@ -568,7 +630,7 @@ |
lastObservedSet = observedSetCache.pop() || newObservedSet(); |
lastObservedSet.object = obj; |
} |
- lastObservedSet.open(observer); |
+ lastObservedSet.open(observer, obj); |
return lastObservedSet; |
} |
@@ -596,8 +658,8 @@ |
addToAll(this); |
this.callback_ = callback; |
this.target_ = target; |
- this.state_ = OPENED; |
this.connect_(); |
+ this.state_ = OPENED; |
return this.value_; |
}, |
@@ -606,11 +668,11 @@ |
return; |
removeFromAll(this); |
- this.state_ = CLOSED; |
this.disconnect_(); |
this.value_ = undefined; |
this.callback_ = undefined; |
this.target_ = undefined; |
+ this.state_ = CLOSED; |
}, |
deliver: function() { |
@@ -866,7 +928,7 @@ |
Observer.call(this); |
this.object_ = object; |
- this.path_ = path instanceof Path ? path : getPath(path); |
+ this.path_ = getPath(path); |
this.directObserver_ = undefined; |
} |
@@ -909,9 +971,10 @@ |
} |
}); |
- function CompoundObserver() { |
+ function CompoundObserver(reportChangesOnOpen) { |
Observer.call(this); |
+ this.reportChangesOnOpen_ = reportChangesOnOpen; |
this.value_ = []; |
this.directObserver_ = undefined; |
this.observed_ = []; |
@@ -923,67 +986,59 @@ |
__proto__: Observer.prototype, |
connect_: function() { |
- this.check_(undefined, true); |
- |
- if (!hasObserve) |
- return; |
- |
- var object; |
- var needsDirectObserver = false; |
- for (var i = 0; i < this.observed_.length; i += 2) { |
- object = this.observed_[i] |
- if (object !== observerSentinel) { |
- needsDirectObserver = true; |
- break; |
+ if (hasObserve) { |
+ var object; |
+ var needsDirectObserver = false; |
+ for (var i = 0; i < this.observed_.length; i += 2) { |
+ object = this.observed_[i] |
+ if (object !== observerSentinel) { |
+ needsDirectObserver = true; |
+ break; |
+ } |
} |
- } |
- if (this.directObserver_) { |
- if (needsDirectObserver) { |
- this.directObserver_.reset(); |
- return; |
- } |
- this.directObserver_.close(); |
- this.directObserver_ = undefined; |
- return; |
+ if (needsDirectObserver) |
+ this.directObserver_ = getObservedSet(this, object); |
} |
- if (needsDirectObserver) |
- this.directObserver_ = getObservedSet(this, object); |
+ this.check_(undefined, !this.reportChangesOnOpen_); |
}, |
- closeObservers_: function() { |
+ disconnect_: function() { |
for (var i = 0; i < this.observed_.length; i += 2) { |
if (this.observed_[i] === observerSentinel) |
this.observed_[i + 1].close(); |
} |
this.observed_.length = 0; |
- }, |
+ this.value_.length = 0; |
- disconnect_: function() { |
- this.value_ = undefined; |
- |
if (this.directObserver_) { |
this.directObserver_.close(this); |
this.directObserver_ = undefined; |
} |
- |
- this.closeObservers_(); |
}, |
addPath: function(object, path) { |
if (this.state_ != UNOPENED && this.state_ != RESETTING) |
throw Error('Cannot add paths once started.'); |
- this.observed_.push(object, path instanceof Path ? path : getPath(path)); |
+ var path = getPath(path); |
+ this.observed_.push(object, path); |
+ if (!this.reportChangesOnOpen_) |
+ return; |
+ var index = this.observed_.length / 2 - 1; |
+ this.value_[index] = path.getValueFrom(object); |
}, |
addObserver: function(observer) { |
if (this.state_ != UNOPENED && this.state_ != RESETTING) |
throw Error('Cannot add observers once started.'); |
- observer.open(this.deliver, this); |
this.observed_.push(observerSentinel, observer); |
+ if (!this.reportChangesOnOpen_) |
+ return; |
+ var index = this.observed_.length / 2 - 1; |
+ this.value_[index] = observer.open(this.deliver, this); |
}, |
startReset: function() { |
@@ -991,7 +1046,7 @@ |
throw Error('Can only reset while open'); |
this.state_ = RESETTING; |
- this.closeObservers_(); |
+ this.disconnect_(); |
}, |
finishReset: function() { |
@@ -1015,11 +1070,17 @@ |
check_: function(changeRecords, skipChanges) { |
var oldValues; |
for (var i = 0; i < this.observed_.length; i += 2) { |
- var pathOrObserver = this.observed_[i+1]; |
var object = this.observed_[i]; |
- var value = object === observerSentinel ? |
- pathOrObserver.discardChanges() : |
- pathOrObserver.getValueFrom(object) |
+ var path = this.observed_[i+1]; |
+ var value; |
+ if (object === observerSentinel) { |
+ var observable = path; |
+ value = this.state_ === UNOPENED ? |
+ observable.open(this.deliver, this) : |
+ observable.discardChanges(); |
+ } else { |
+ value = path.getValueFrom(object); |
+ } |
if (skipChanges) { |
this.value_[i / 2] = value; |
@@ -1110,51 +1171,94 @@ |
delete: true |
}; |
- function notifyFunction(object, name) { |
- if (typeof Object.observe !== 'function') |
+ var updateRecord = { |
+ object: undefined, |
+ type: 'update', |
+ name: undefined, |
+ oldValue: undefined |
+ }; |
+ |
+ function notify(object, name, value, oldValue) { |
+ if (areSameValue(value, oldValue)) |
return; |
- var notifier = Object.getNotifier(object); |
- return function(type, oldValue) { |
- var changeRecord = { |
- object: object, |
- type: type, |
- name: name |
- }; |
- if (arguments.length === 2) |
- changeRecord.oldValue = oldValue; |
- notifier.notify(changeRecord); |
- } |
+ // TODO(rafaelw): Hack hack hack. This entire code really needs to move |
+ // out of observe-js into polymer. |
+ if (typeof object.propertyChanged_ == 'function') |
+ object.propertyChanged_(name, value, oldValue); |
+ |
+ if (!hasObserve) |
+ return; |
+ |
+ var notifier = object.notifier_; |
+ if (!notifier) |
+ notifier = object.notifier_ = Object.getNotifier(object); |
+ |
+ updateRecord.object = object; |
+ updateRecord.name = name; |
+ updateRecord.oldValue = oldValue; |
+ |
+ notifier.notify(updateRecord); |
} |
- Observer.defineComputedProperty = function(target, name, observable) { |
- var notify = notifyFunction(target, name); |
- var value = observable.open(function(newValue, oldValue) { |
- value = newValue; |
- if (notify) |
- notify('update', oldValue); |
- }); |
+ Observer.createBindablePrototypeAccessor = function(proto, name) { |
+ var privateName = name + '_'; |
+ var privateObservable = name + 'Observable_'; |
- Object.defineProperty(target, name, { |
+ proto[privateName] = proto[name]; |
+ |
+ Object.defineProperty(proto, name, { |
get: function() { |
- observable.deliver(); |
+ 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; |
+ notify(this, name, value, oldValue); |
+ |
return value; |
}, |
- set: function(newValue) { |
- observable.setValue(newValue); |
- return newValue; |
- }, |
configurable: true |
}); |
+ } |
+ Observer.bindToInstance = function(instance, name, observable, resolveFn) { |
+ var privateName = name + '_'; |
+ var privateObservable = name + 'Observable_'; |
+ |
+ instance[privateObservable] = observable; |
+ var oldValue = instance[privateName]; |
+ var value = observable.open(function(value, oldValue) { |
+ instance[privateName] = value; |
+ notify(instance, 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); |
+ } |
+ } |
+ |
+ instance[privateName] = value; |
+ notify(instance, name, value, oldValue); |
+ |
return { |
close: function() { |
observable.close(); |
- Object.defineProperty(target, name, { |
- value: value, |
- writable: true, |
- configurable: true |
- }); |
+ instance[privateObservable] = undefined; |
} |
}; |
} |
@@ -1616,6 +1720,7 @@ |
global.Observer = Observer; |
global.Observer.runEOM_ = runEOM; |
+ global.Observer.observerSentinel_ = observerSentinel; // for testing. |
global.Observer.hasObjectObserve = hasObserve; |
global.ArrayObserver = ArrayObserver; |
global.ArrayObserver.calculateSplices = function(current, previous) { |
@@ -1630,66 +1735,6 @@ |
global.ObserverTransform = ObserverTransform; |
})(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window); |
-// prepoulate window.Platform.flags for default controls |
-window.Platform = window.Platform || {}; |
-// prepopulate window.logFlags if necessary |
-window.logFlags = window.logFlags || {}; |
-// process flags |
-(function(scope){ |
- // import |
- var flags = scope.flags || {}; |
- // populate flags from location |
- location.search.slice(1).split('&').forEach(function(o) { |
- o = o.split('='); |
- o[0] && (flags[o[0]] = o[1] || true); |
- }); |
- var entryPoint = document.currentScript || |
- document.querySelector('script[src*="platform.js"]'); |
- if (entryPoint) { |
- var a = entryPoint.attributes; |
- for (var i = 0, n; i < a.length; i++) { |
- n = a[i]; |
- if (n.name !== 'src') { |
- flags[n.name] = n.value || true; |
- } |
- } |
- } |
- if (flags.log) { |
- flags.log.split(',').forEach(function(f) { |
- window.logFlags[f] = true; |
- }); |
- } |
- // If any of these flags match 'native', then force native ShadowDOM; any |
- // other truthy value, or failure to detect native |
- // ShadowDOM, results in polyfill |
- flags.shadow = flags.shadow || flags.shadowdom || flags.polyfill; |
- if (flags.shadow === 'native') { |
- flags.shadow = false; |
- } else { |
- flags.shadow = flags.shadow || !HTMLElement.prototype.createShadowRoot; |
- } |
- |
- if (flags.shadow && document.querySelectorAll('script').length > 1) { |
- console.warn('platform.js is not the first script on the page. ' + |
- 'See http://www.polymer-project.org/docs/start/platform.html#setup ' + |
- 'for details.'); |
- } |
- |
- // CustomElements polyfill flag |
- if (flags.register) { |
- window.CustomElements = window.CustomElements || {flags: {}}; |
- window.CustomElements.flags.register = flags.register; |
- } |
- |
- if (flags.imports) { |
- window.HTMLImports = window.HTMLImports || {flags: {}}; |
- window.HTMLImports.flags.imports = flags.imports; |
- } |
- |
- // export |
- scope.flags = flags; |
-})(Platform); |
- |
// select ShadowDOM impl |
if (Platform.flags.shadow) { |
@@ -1706,20 +1751,23 @@ |
var nativePrototypeTable = new WeakMap(); |
var wrappers = Object.create(null); |
- // Don't test for eval if document has CSP securityPolicy object and we can |
- // see that eval is not supported. This avoids an error message in console |
- // even when the exception is caught |
- var hasEval = !('securityPolicy' in document) || |
- document.securityPolicy.allowsEval; |
- if (hasEval) { |
+ function detectEval() { |
+ // Don't test for eval if we're running in a Chrome App environment. |
+ // We check for APIs set that only exist in a Chrome App context. |
+ if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { |
+ return false; |
+ } |
+ |
try { |
- var f = new Function('', 'return true;'); |
- hasEval = f(); |
+ var f = new Function('return true;'); |
+ return f(); |
} catch (ex) { |
- hasEval = false; |
+ return false; |
} |
} |
+ var hasEval = detectEval(); |
+ |
function assert(b) { |
if (!b) |
throw new Error('Assertion failed'); |
@@ -1730,14 +1778,18 @@ |
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; |
function mixin(to, from) { |
- getOwnPropertyNames(from).forEach(function(name) { |
+ var names = getOwnPropertyNames(from); |
+ for (var i = 0; i < names.length; i++) { |
+ var name = names[i]; |
defineProperty(to, name, getOwnPropertyDescriptor(from, name)); |
- }); |
+ } |
return to; |
}; |
function mixinStatics(to, from) { |
- getOwnPropertyNames(from).forEach(function(name) { |
+ var names = getOwnPropertyNames(from); |
+ for (var i = 0; i < names.length; i++) { |
+ var name = names[i]; |
switch (name) { |
case 'arguments': |
case 'caller': |
@@ -1745,10 +1797,10 @@ |
case 'name': |
case 'prototype': |
case 'toString': |
- return; |
+ continue; |
} |
defineProperty(to, name, getOwnPropertyDescriptor(from, name)); |
- }); |
+ } |
return to; |
}; |
@@ -1759,6 +1811,18 @@ |
} |
} |
+ var nonEnumerableDataDescriptor = { |
+ value: undefined, |
+ configurable: true, |
+ enumerable: false, |
+ writable: true |
+ }; |
+ |
+ function defineNonEnumerableDataProperty(object, name, value) { |
+ nonEnumerableDataDescriptor.value = value; |
+ defineProperty(object, name, nonEnumerableDataDescriptor); |
+ } |
+ |
// Mozilla's old DOM bindings are bretty busted: |
// https://bugzilla.mozilla.org/show_bug.cgi?id=855844 |
// Make sure they are create before we start modifying things. |
@@ -1903,12 +1967,9 @@ |
addForwardingProperties(nativePrototype, wrapperPrototype); |
if (opt_instance) |
registerInstanceProperties(wrapperPrototype, opt_instance); |
- defineProperty(wrapperPrototype, 'constructor', { |
- value: wrapperConstructor, |
- configurable: true, |
- enumerable: false, |
- writable: true |
- }); |
+ |
+ defineNonEnumerableDataProperty( |
+ wrapperPrototype, 'constructor', wrapperConstructor); |
// Set it again. Some VMs optimizes objects that are used as prototypes. |
wrapperConstructor.prototype = wrapperPrototype; |
} |
@@ -2038,12 +2099,15 @@ |
node.polymerWrapper_ = wrapper; |
} |
+ var getterDescriptor = { |
+ get: undefined, |
+ configurable: true, |
+ enumerable: true |
+ }; |
+ |
function defineGetter(constructor, name, getter) { |
- defineProperty(constructor.prototype, name, { |
- get: getter, |
- configurable: true, |
- enumerable: true |
- }); |
+ getterDescriptor.get = getter; |
+ defineProperty(constructor.prototype, name, getterDescriptor); |
} |
function defineWrapGetter(constructor, name) { |
@@ -2527,10 +2591,21 @@ |
* A tree scope represents the root of a tree. All nodes in a tree point to |
* the same TreeScope object. The tree scope of a node get set the first time |
* it is accessed or when a node is added or remove to a tree. |
+ * |
+ * The root is a Node that has no parent. |
+ * |
+ * The parent is another TreeScope. For ShadowRoots, it is the TreeScope of |
+ * the host of the ShadowRoot. |
+ * |
+ * @param {!Node} root |
+ * @param {TreeScope} parent |
* @constructor |
*/ |
function TreeScope(root, parent) { |
+ /** @type {!Node} */ |
this.root = root; |
+ |
+ /** @type {TreeScope} */ |
this.parent = parent; |
} |
@@ -2564,6 +2639,10 @@ |
} |
function getTreeScope(node) { |
+ if (node instanceof scope.wrappers.Window) { |
+ debugger; |
+ } |
+ |
if (node.treeScope_) |
return node.treeScope_; |
var parent = node.parentNode; |
@@ -2613,244 +2692,323 @@ |
return node instanceof wrappers.ShadowRoot; |
} |
- function isInsertionPoint(node) { |
- var localName = node.localName; |
- return localName === 'content' || localName === 'shadow'; |
+ function rootOfNode(node) { |
+ return getTreeScope(node).root; |
} |
- function isShadowHost(node) { |
- return !!node.shadowRoot; |
- } |
+ // http://w3c.github.io/webcomponents/spec/shadow/#event-paths |
+ function getEventPath(node, event) { |
+ var path = []; |
+ var current = node; |
+ path.push(current); |
+ while (current) { |
+ // 4.1. |
+ var destinationInsertionPoints = getDestinationInsertionPoints(current); |
+ if (destinationInsertionPoints && destinationInsertionPoints.length > 0) { |
+ // 4.1.1 |
+ for (var i = 0; i < destinationInsertionPoints.length; i++) { |
+ var insertionPoint = destinationInsertionPoints[i]; |
+ // 4.1.1.1 |
+ if (isShadowInsertionPoint(insertionPoint)) { |
+ var shadowRoot = rootOfNode(insertionPoint); |
+ // 4.1.1.1.2 |
+ var olderShadowRoot = shadowRoot.olderShadowRoot; |
+ if (olderShadowRoot) |
+ path.push(olderShadowRoot); |
+ } |
- function getEventParent(node) { |
- var dv; |
- return node.parentNode || (dv = node.defaultView) && wrap(dv) || null; |
- } |
+ // 4.1.1.2 |
+ path.push(insertionPoint); |
+ } |
- // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-adjusted-parent |
- function calculateParents(node, context, ancestors) { |
- if (ancestors.length) |
- return ancestors.shift(); |
+ // 4.1.2 |
+ current = destinationInsertionPoints[ |
+ destinationInsertionPoints.length - 1]; |
- // 1. |
- if (isShadowRoot(node)) |
- return getInsertionParent(node) || node.host; |
+ // 4.2 |
+ } else { |
+ if (isShadowRoot(current)) { |
+ if (inSameTree(node, current) && eventMustBeStopped(event)) { |
+ // Stop this algorithm |
+ break; |
+ } |
+ current = current.host; |
+ path.push(current); |
- // 2. |
- var eventParents = scope.eventParentsTable.get(node); |
- if (eventParents) { |
- // Copy over the remaining event parents for next iteration. |
- for (var i = 1; i < eventParents.length; i++) { |
- ancestors[i - 1] = eventParents[i]; |
- } |
- return eventParents[0]; |
- } |
- |
- // 3. |
- if (context && isInsertionPoint(node)) { |
- var parentNode = node.parentNode; |
- if (parentNode && isShadowHost(parentNode)) { |
- var trees = scope.getShadowTrees(parentNode); |
- var p = getInsertionParent(context); |
- for (var i = 0; i < trees.length; i++) { |
- if (trees[i].contains(p)) |
- return p; |
+ // 4.2.2 |
+ } else { |
+ current = current.parentNode; |
+ if (current) |
+ path.push(current); |
} |
} |
} |
- return getEventParent(node); |
+ return path; |
} |
- // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#event-retargeting |
- function retarget(node) { |
- var stack = []; // 1. |
- var ancestor = node; // 2. |
- var targets = []; |
- var ancestors = []; |
- while (ancestor) { // 3. |
- var context = null; // 3.2. |
- // TODO(arv): Change order of these. If the stack is empty we always end |
- // up pushing ancestor, no matter what. |
- if (isInsertionPoint(ancestor)) { // 3.1. |
- context = topMostNotInsertionPoint(stack); // 3.1.1. |
- var top = stack[stack.length - 1] || ancestor; // 3.1.2. |
- stack.push(top); |
- } else if (!stack.length) { |
- stack.push(ancestor); // 3.3. |
- } |
- var target = stack[stack.length - 1]; // 3.4. |
- targets.push({target: target, currentTarget: ancestor}); // 3.5. |
- if (isShadowRoot(ancestor)) // 3.6. |
- stack.pop(); // 3.6.1. |
+ // http://w3c.github.io/webcomponents/spec/shadow/#dfn-events-always-stopped |
+ function eventMustBeStopped(event) { |
+ if (!event) |
+ return false; |
- ancestor = calculateParents(ancestor, context, ancestors); // 3.7. |
+ switch (event.type) { |
+ case 'abort': |
+ case 'error': |
+ case 'select': |
+ case 'change': |
+ case 'load': |
+ case 'reset': |
+ case 'resize': |
+ case 'scroll': |
+ case 'selectstart': |
+ return true; |
} |
- return targets; |
+ return false; |
} |
- function topMostNotInsertionPoint(stack) { |
- for (var i = stack.length - 1; i >= 0; i--) { |
- if (!isInsertionPoint(stack[i])) |
- return stack[i]; |
+ // http://w3c.github.io/webcomponents/spec/shadow/#dfn-shadow-insertion-point |
+ function isShadowInsertionPoint(node) { |
+ return node instanceof HTMLShadowElement; |
+ // and make sure that there are no shadow precing this? |
+ // and that there is no content ancestor? |
+ } |
+ |
+ function getDestinationInsertionPoints(node) { |
+ return scope.getDestinationInsertionPoints(node); |
+ } |
+ |
+ // http://w3c.github.io/webcomponents/spec/shadow/#event-retargeting |
+ function eventRetargetting(path, currentTarget) { |
+ if (path.length === 0) |
+ return currentTarget; |
+ |
+ // The currentTarget might be the window object. Use its document for the |
+ // purpose of finding the retargetted node. |
+ if (currentTarget instanceof wrappers.Window) |
+ currentTarget = currentTarget.document; |
+ |
+ var currentTargetTree = getTreeScope(currentTarget); |
+ var originalTarget = path[0]; |
+ var originalTargetTree = getTreeScope(originalTarget); |
+ var relativeTargetTree = |
+ lowestCommonInclusiveAncestor(currentTargetTree, originalTargetTree); |
+ |
+ for (var i = 0; i < path.length; i++) { |
+ var node = path[i]; |
+ if (getTreeScope(node) === relativeTargetTree) |
+ return node; |
} |
- return null; |
+ |
+ return path[path.length - 1]; |
} |
- // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-adjusted-related-target |
- function adjustRelatedTarget(target, related) { |
+ function getTreeScopeAncestors(treeScope) { |
var ancestors = []; |
- while (target) { // 3. |
- var stack = []; // 3.1. |
- var ancestor = related; // 3.2. |
- var last = undefined; // 3.3. Needs to be reset every iteration. |
- while (ancestor) { |
- var context = null; |
- if (!stack.length) { |
- stack.push(ancestor); |
- } else { |
- if (isInsertionPoint(ancestor)) { // 3.4.3. |
- context = topMostNotInsertionPoint(stack); |
- // isDistributed is more general than checking whether last is |
- // assigned into ancestor. |
- if (isDistributed(last)) { // 3.4.3.2. |
- var head = stack[stack.length - 1]; |
- stack.push(head); |
- } |
- } |
- } |
+ for (;treeScope; treeScope = treeScope.parent) { |
+ ancestors.push(treeScope); |
+ } |
+ return ancestors; |
+ } |
- if (inSameTree(ancestor, target)) // 3.4.4. |
- return stack[stack.length - 1]; |
+ function lowestCommonInclusiveAncestor(tsA, tsB) { |
+ var ancestorsA = getTreeScopeAncestors(tsA); |
+ var ancestorsB = getTreeScopeAncestors(tsB); |
- if (isShadowRoot(ancestor)) // 3.4.5. |
- stack.pop(); |
- |
- last = ancestor; // 3.4.6. |
- ancestor = calculateParents(ancestor, context, ancestors); // 3.4.7. |
- } |
- if (isShadowRoot(target)) // 3.5. |
- target = target.host; |
+ var result = null; |
+ while (ancestorsA.length > 0 && ancestorsB.length > 0) { |
+ var a = ancestorsA.pop(); |
+ var b = ancestorsB.pop(); |
+ if (a === b) |
+ result = a; |
else |
- target = target.parentNode; // 3.6. |
+ break; |
} |
+ return result; |
} |
- function getInsertionParent(node) { |
- return scope.insertionParentTable.get(node); |
+ function getTreeScopeRoot(ts) { |
+ if (!ts.parent) |
+ return ts; |
+ return getTreeScopeRoot(ts.parent); |
} |
- function isDistributed(node) { |
- return getInsertionParent(node); |
+ function relatedTargetResolution(event, currentTarget, relatedTarget) { |
+ // In case the current target is a window use its document for the purpose |
+ // of retargetting the related target. |
+ if (currentTarget instanceof wrappers.Window) |
+ currentTarget = currentTarget.document; |
+ |
+ var currentTargetTree = getTreeScope(currentTarget); |
+ var relatedTargetTree = getTreeScope(relatedTarget); |
+ |
+ var relatedTargetEventPath = getEventPath(relatedTarget, event); |
+ |
+ var lowestCommonAncestorTree; |
+ |
+ // 4 |
+ var lowestCommonAncestorTree = |
+ lowestCommonInclusiveAncestor(currentTargetTree, relatedTargetTree); |
+ |
+ // 5 |
+ if (!lowestCommonAncestorTree) |
+ lowestCommonAncestorTree = relatedTargetTree.root; |
+ |
+ // 6 |
+ for (var commonAncestorTree = lowestCommonAncestorTree; |
+ commonAncestorTree; |
+ commonAncestorTree = commonAncestorTree.parent) { |
+ // 6.1 |
+ var adjustedRelatedTarget; |
+ for (var i = 0; i < relatedTargetEventPath.length; i++) { |
+ var node = relatedTargetEventPath[i]; |
+ if (getTreeScope(node) === commonAncestorTree) |
+ return node; |
+ } |
+ } |
+ |
+ return null; |
} |
function inSameTree(a, b) { |
return getTreeScope(a) === getTreeScope(b); |
} |
+ var NONE = 0; |
+ var CAPTURING_PHASE = 1; |
+ var AT_TARGET = 2; |
+ var BUBBLING_PHASE = 3; |
+ |
+ // pendingError is used to rethrow the first error we got during an event |
+ // dispatch. The browser actually reports all errors but to do that we would |
+ // need to rethrow the error asynchronously. |
+ var pendingError; |
+ |
function dispatchOriginalEvent(originalEvent) { |
// Make sure this event is only dispatched once. |
if (handledEventsTable.get(originalEvent)) |
return; |
handledEventsTable.set(originalEvent, true); |
dispatchEvent(wrap(originalEvent), wrap(originalEvent.target)); |
- } |
- |
- function isLoadLikeEvent(event) { |
- switch (event.type) { |
- case 'beforeunload': |
- case 'load': |
- case 'unload': |
- return true; |
+ if (pendingError) { |
+ var err = pendingError; |
+ pendingError = null; |
+ throw err; |
} |
- return false; |
} |
function dispatchEvent(event, originalWrapperTarget) { |
if (currentlyDispatchingEvents.get(event)) |
- throw new Error('InvalidStateError') |
+ throw new Error('InvalidStateError'); |
+ |
currentlyDispatchingEvents.set(event, true); |
// Render to ensure that the event path is correct. |
scope.renderAllPending(); |
- var eventPath = retarget(originalWrapperTarget); |
+ var eventPath; |
- // For window "load" events the "load" event is dispatched at the window but |
- // the target is set to the document. |
- // |
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#events-and-the-window-object |
+ // All events dispatched on Nodes with a default view, except load events, |
+ // should propagate to the Window. |
+ |
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end |
- // |
- // TODO(arv): Find a less hacky way to do this. |
- if (eventPath.length === 2 && |
- eventPath[0].target instanceof wrappers.Document && |
- isLoadLikeEvent(event)) { |
- eventPath.shift(); |
+ var overrideTarget; |
+ var win; |
+ var type = event.type; |
+ |
+ // Should really be not cancelable too but since Firefox has a bug there |
+ // we skip that check. |
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=999456 |
+ if (type === 'load' && !event.bubbles) { |
+ var doc = originalWrapperTarget; |
+ if (doc instanceof wrappers.Document && (win = doc.defaultView)) { |
+ overrideTarget = doc; |
+ eventPath = []; |
+ } |
} |
+ if (!eventPath) { |
+ if (originalWrapperTarget instanceof wrappers.Window) { |
+ win = originalWrapperTarget; |
+ eventPath = []; |
+ } else { |
+ eventPath = getEventPath(originalWrapperTarget, event); |
+ |
+ if (event.type !== 'load') { |
+ var doc = eventPath[eventPath.length - 1]; |
+ if (doc instanceof wrappers.Document) |
+ win = doc.defaultView; |
+ } |
+ } |
+ } |
+ |
eventPathTable.set(event, eventPath); |
- if (dispatchCapturing(event, eventPath)) { |
- if (dispatchAtTarget(event, eventPath)) { |
- dispatchBubbling(event, eventPath); |
+ if (dispatchCapturing(event, eventPath, win, overrideTarget)) { |
+ if (dispatchAtTarget(event, eventPath, win, overrideTarget)) { |
+ dispatchBubbling(event, eventPath, win, overrideTarget); |
} |
} |
- eventPhaseTable.set(event, Event.NONE); |
+ eventPhaseTable.set(event, NONE); |
currentTargetTable.delete(event, null); |
currentlyDispatchingEvents.delete(event); |
return event.defaultPrevented; |
} |
- function dispatchCapturing(event, eventPath) { |
- var phase; |
+ function dispatchCapturing(event, eventPath, win, overrideTarget) { |
+ var phase = CAPTURING_PHASE; |
- for (var i = eventPath.length - 1; i > 0; i--) { |
- var target = eventPath[i].target; |
- var currentTarget = eventPath[i].currentTarget; |
- if (target === currentTarget) |
- continue; |
+ if (win) { |
+ if (!invoke(win, event, phase, eventPath, overrideTarget)) |
+ return false; |
+ } |
- phase = Event.CAPTURING_PHASE; |
- if (!invoke(eventPath[i], event, phase)) |
+ for (var i = eventPath.length - 1; i > 0; i--) { |
+ if (!invoke(eventPath[i], event, phase, eventPath, overrideTarget)) |
return false; |
} |
return true; |
} |
- function dispatchAtTarget(event, eventPath) { |
- var phase = Event.AT_TARGET; |
- return invoke(eventPath[0], event, phase); |
+ function dispatchAtTarget(event, eventPath, win, overrideTarget) { |
+ var phase = AT_TARGET; |
+ var currentTarget = eventPath[0] || win; |
+ return invoke(currentTarget, event, phase, eventPath, overrideTarget); |
} |
- function dispatchBubbling(event, eventPath) { |
- var bubbles = event.bubbles; |
- var phase; |
- |
+ function dispatchBubbling(event, eventPath, win, overrideTarget) { |
+ var phase = BUBBLING_PHASE; |
for (var i = 1; i < eventPath.length; i++) { |
- var target = eventPath[i].target; |
- var currentTarget = eventPath[i].currentTarget; |
- if (target === currentTarget) |
- phase = Event.AT_TARGET; |
- else if (bubbles && !stopImmediatePropagationTable.get(event)) |
- phase = Event.BUBBLING_PHASE; |
- else |
- continue; |
- |
- if (!invoke(eventPath[i], event, phase)) |
+ if (!invoke(eventPath[i], event, phase, eventPath, overrideTarget)) |
return; |
} |
+ |
+ if (win && eventPath.length > 0) { |
+ invoke(win, event, phase, eventPath, overrideTarget); |
+ } |
} |
- function invoke(tuple, event, phase) { |
- var target = tuple.target; |
- var currentTarget = tuple.currentTarget; |
- |
+ function invoke(currentTarget, event, phase, eventPath, overrideTarget) { |
var listeners = listenersTable.get(currentTarget); |
if (!listeners) |
return true; |
+ var target = overrideTarget || eventRetargetting(eventPath, currentTarget); |
+ |
+ if (target === currentTarget) { |
+ if (phase === CAPTURING_PHASE) |
+ return true; |
+ |
+ if (phase === BUBBLING_PHASE) |
+ phase = AT_TARGET; |
+ |
+ } else if (phase === BUBBLING_PHASE && !event.bubbles) { |
+ return true; |
+ } |
+ |
if ('relatedTarget' in event) { |
var originalEvent = unwrap(event); |
var unwrappedRelatedTarget = originalEvent.relatedTarget; |
@@ -2867,7 +3025,8 @@ |
unwrappedRelatedTarget.addEventListener) { |
var relatedTarget = wrap(unwrappedRelatedTarget); |
- var adjusted = adjustRelatedTarget(currentTarget, relatedTarget); |
+ var adjusted = |
+ relatedTargetResolution(event, currentTarget, relatedTarget); |
if (adjusted === target) |
return true; |
} else { |
@@ -2881,6 +3040,7 @@ |
var type = event.type; |
var anyRemoved = false; |
+ // targetTable.set(event, target); |
targetTable.set(event, target); |
currentTargetTable.set(event, currentTarget); |
@@ -2892,8 +3052,8 @@ |
} |
if (listener.type !== type || |
- !listener.capture && phase === Event.CAPTURING_PHASE || |
- listener.capture && phase === Event.BUBBLING_PHASE) { |
+ !listener.capture && phase === CAPTURING_PHASE || |
+ listener.capture && phase === BUBBLING_PHASE) { |
continue; |
} |
@@ -2907,10 +3067,8 @@ |
return false; |
} catch (ex) { |
- if (window.onerror) |
- window.onerror(ex.message); |
- else |
- console.error(ex, ex.stack); |
+ if (!pendingError) |
+ pendingError = ex; |
} |
} |
@@ -2987,7 +3145,7 @@ |
var baseRoot = getTreeScope(currentTargetTable.get(this)); |
for (var i = 0; i <= lastIndex; i++) { |
- var currentTarget = eventPath[i].currentTarget; |
+ var currentTarget = eventPath[i]; |
var currentRoot = getTreeScope(currentTarget); |
if (currentRoot.contains(baseRoot) && |
// Make sure we do not add Window to the path. |
@@ -3330,13 +3488,10 @@ |
scope.renderAllPending(); |
var element = wrap(originalElementFromPoint.call(document.impl, x, y)); |
- var targets = retarget(element, this) |
- for (var i = 0; i < targets.length; i++) { |
- var target = targets[i]; |
- if (target.currentTarget === self) |
- return target.target; |
- } |
- return null; |
+ if (!element) |
+ return null; |
+ var path = getEventPath(element, null); |
+ return eventRetargetting(path, self); |
} |
/** |
@@ -3390,7 +3545,6 @@ |
}; |
} |
- scope.adjustRelatedTarget = adjustRelatedTarget; |
scope.elementFromPoint = elementFromPoint; |
scope.getEventHandlerGetter = getEventHandlerGetter; |
scope.getEventHandlerSetter = getEventHandlerSetter; |
@@ -3405,6 +3559,132 @@ |
})(window.ShadowDOMPolyfill); |
+/* |
+ * Copyright 2014 The Polymer Authors. All rights reserved. |
+ * Use of this source code is goverened by a BSD-style |
+ * license that can be found in the LICENSE file. |
+ */ |
+ |
+(function(scope) { |
+ 'use strict'; |
+ |
+ var UIEvent = scope.wrappers.UIEvent; |
+ var mixin = scope.mixin; |
+ var registerWrapper = scope.registerWrapper; |
+ var unwrap = scope.unwrap; |
+ var wrap = scope.wrap; |
+ |
+ // TouchEvent is WebKit/Blink only. |
+ var OriginalTouchEvent = window.TouchEvent; |
+ if (!OriginalTouchEvent) |
+ return; |
+ |
+ var nativeEvent; |
+ try { |
+ nativeEvent = document.createEvent('TouchEvent'); |
+ } catch (ex) { |
+ // In Chrome creating a TouchEvent fails if the feature is not turned on |
+ // which it isn't on desktop Chrome. |
+ return; |
+ } |
+ |
+ var nonEnumDescriptor = {enumerable: false}; |
+ |
+ function nonEnum(obj, prop) { |
+ Object.defineProperty(obj, prop, nonEnumDescriptor); |
+ } |
+ |
+ function Touch(impl) { |
+ this.impl = impl; |
+ } |
+ |
+ Touch.prototype = { |
+ get target() { |
+ return wrap(this.impl.target); |
+ } |
+ }; |
+ |
+ var descr = { |
+ configurable: true, |
+ enumerable: true, |
+ get: null |
+ }; |
+ |
+ [ |
+ 'clientX', |
+ 'clientY', |
+ 'screenX', |
+ 'screenY', |
+ 'pageX', |
+ 'pageY', |
+ 'identifier', |
+ 'webkitRadiusX', |
+ 'webkitRadiusY', |
+ 'webkitRotationAngle', |
+ 'webkitForce' |
+ ].forEach(function(name) { |
+ descr.get = function() { |
+ return this.impl[name]; |
+ }; |
+ Object.defineProperty(Touch.prototype, name, descr); |
+ }); |
+ |
+ function TouchList() { |
+ this.length = 0; |
+ nonEnum(this, 'length'); |
+ } |
+ |
+ TouchList.prototype = { |
+ item: function(index) { |
+ return this[index]; |
+ } |
+ }; |
+ |
+ function wrapTouchList(nativeTouchList) { |
+ var list = new TouchList(); |
+ for (var i = 0; i < nativeTouchList.length; i++) { |
+ list[i] = new Touch(nativeTouchList[i]); |
+ } |
+ list.length = i; |
+ return list; |
+ } |
+ |
+ function TouchEvent(impl) { |
+ UIEvent.call(this, impl); |
+ } |
+ |
+ TouchEvent.prototype = Object.create(UIEvent.prototype); |
+ |
+ mixin(TouchEvent.prototype, { |
+ get touches() { |
+ return wrapTouchList(unwrap(this).touches); |
+ }, |
+ |
+ get targetTouches() { |
+ return wrapTouchList(unwrap(this).targetTouches); |
+ }, |
+ |
+ get changedTouches() { |
+ return wrapTouchList(unwrap(this).changedTouches); |
+ }, |
+ |
+ initTouchEvent: function() { |
+ // The only way to use this is to reuse the TouchList from an existing |
+ // TouchEvent. Since this is WebKit/Blink proprietary API we will not |
+ // implement this until someone screams. |
+ throw new Error('Not implemented'); |
+ } |
+ }); |
+ |
+ registerWrapper(OriginalTouchEvent, TouchEvent, nativeEvent); |
+ |
+ scope.wrappers.Touch = Touch; |
+ scope.wrappers.TouchEvent = TouchEvent; |
+ scope.wrappers.TouchList = TouchList; |
+ |
+})(window.ShadowDOMPolyfill); |
+ |
+ |
// Copyright 2012 The Polymer Authors. All rights reserved. |
// Use of this source code is goverened by a BSD-style |
// license that can be found in the LICENSE file. |
@@ -3414,8 +3694,10 @@ |
var wrap = scope.wrap; |
+ var nonEnumDescriptor = {enumerable: false}; |
+ |
function nonEnum(obj, prop) { |
- Object.defineProperty(obj, prop, {enumerable: false}); |
+ Object.defineProperty(obj, prop, nonEnumDescriptor); |
} |
function NodeList() { |
@@ -3871,8 +4153,11 @@ |
} else { |
if (!previousNode) |
this.firstChild_ = nodes[0]; |
- if (!refWrapper) |
+ if (!refWrapper) { |
this.lastChild_ = nodes[nodes.length - 1]; |
+ if (this.firstChild_ === undefined) |
+ this.firstChild_ = this.firstChild; |
+ } |
var parentNode = refNode ? refNode.parentNode : this.impl; |
@@ -4208,6 +4493,9 @@ |
(function(scope) { |
'use strict'; |
+ var HTMLCollection = scope.wrappers.HTMLCollection; |
+ var NodeList = scope.wrappers.NodeList; |
+ |
function findOne(node, selector) { |
var m, el = node.firstElementChild; |
while (el) { |
@@ -4221,15 +4509,43 @@ |
return null; |
} |
- function findAll(node, selector, results) { |
+ function matchesSelector(el, selector) { |
+ return el.matches(selector); |
+ } |
+ |
+ var XHTML_NS = 'http://www.w3.org/1999/xhtml'; |
+ |
+ function matchesTagName(el, localName, localNameLowerCase) { |
+ var ln = el.localName; |
+ return ln === localName || |
+ ln === localNameLowerCase && el.namespaceURI === XHTML_NS; |
+ } |
+ |
+ function matchesEveryThing() { |
+ return true; |
+ } |
+ |
+ function matchesLocalName(el, localName) { |
+ return el.localName === localName; |
+ } |
+ |
+ function matchesNameSpace(el, ns) { |
+ return el.namespaceURI === ns; |
+ } |
+ |
+ function matchesLocalNameNS(el, ns, localName) { |
+ return el.namespaceURI === ns && el.localName === localName; |
+ } |
+ |
+ function findElements(node, result, p, arg0, arg1) { |
var el = node.firstElementChild; |
while (el) { |
- if (el.matches(selector)) |
- results[results.length++] = el; |
- findAll(el, selector, results); |
+ if (p(el, arg0, arg1)) |
+ result[result.length++] = el; |
+ findElements(el, result, p, arg0, arg1); |
el = el.nextElementSibling; |
} |
- return results; |
+ return result; |
} |
// find and findAll will only match Simple Selectors, |
@@ -4241,32 +4557,42 @@ |
return findOne(this, selector); |
}, |
querySelectorAll: function(selector) { |
- return findAll(this, selector, new NodeList()) |
+ return findElements(this, new NodeList(), matchesSelector, selector); |
} |
}; |
var GetElementsByInterface = { |
- getElementsByTagName: function(tagName) { |
- // TODO(arv): Check tagName? |
- return this.querySelectorAll(tagName); |
+ getElementsByTagName: function(localName) { |
+ var result = new HTMLCollection(); |
+ if (localName === '*') |
+ return findElements(this, result, matchesEveryThing); |
+ |
+ return findElements(this, result, |
+ matchesTagName, |
+ localName, |
+ localName.toLowerCase()); |
}, |
+ |
getElementsByClassName: function(className) { |
// TODO(arv): Check className? |
return this.querySelectorAll('.' + className); |
}, |
- getElementsByTagNameNS: function(ns, tagName) { |
- if (ns === '*') |
- return this.getElementsByTagName(tagName); |
- // TODO(arv): Check tagName? |
- var result = new NodeList; |
- var els = this.getElementsByTagName(tagName); |
- for (var i = 0, j = 0; i < els.length; i++) { |
- if (els[i].namespaceURI === ns) |
- result[j++] = els[i]; |
+ getElementsByTagNameNS: function(ns, localName) { |
+ var result = new HTMLCollection(); |
+ |
+ if (ns === '') { |
+ ns = null; |
+ } else if (ns === '*') { |
+ if (localName === '*') |
+ return findElements(this, result, matchesEveryThing); |
+ return findElements(this, result, matchesLocalName, localName); |
} |
- result.length = j; |
- return result; |
+ |
+ if (localName === '*') |
+ return findElements(this, result, matchesNameSpace, ns); |
+ |
+ return findElements(this, result, matchesLocalNameNS, ns, localName); |
} |
}; |
@@ -4515,6 +4841,8 @@ |
return this.impl.polymerShadowRoot_ || null; |
}, |
+ // getDestinationInsertionPoints added in ShadowRenderer.js |
+ |
setAttribute: function(name, value) { |
var oldValue = this.impl.getAttribute(name); |
this.impl.setAttribute(name, value); |
@@ -4963,8 +5291,6 @@ |
} |
// getDistributedNodes is added in ShadowRenderer |
- |
- // TODO: attribute boolean resetStyleInheritance; |
}); |
if (OriginalHTMLContentElement) |
@@ -5026,6 +5352,7 @@ |
var HTMLElement = scope.wrappers.HTMLElement; |
var mixin = scope.mixin; |
+ var NodeList = scope.wrappers.NodeList; |
var registerWrapper = scope.registerWrapper; |
var OriginalHTMLShadowElement = window.HTMLShadowElement; |
@@ -5034,10 +5361,9 @@ |
HTMLElement.call(this, node); |
} |
HTMLShadowElement.prototype = Object.create(HTMLElement.prototype); |
- mixin(HTMLShadowElement.prototype, { |
- // TODO: attribute boolean resetStyleInheritance; |
- }); |
+ // getDistributedNodes is added in ShadowRenderer |
+ |
if (OriginalHTMLShadowElement) |
registerWrapper(OriginalHTMLShadowElement, HTMLShadowElement); |
@@ -5844,11 +6170,12 @@ |
// DocumentFragment instance. Override that. |
rewrap(node, this); |
- this.treeScope_ = new TreeScope(this, getTreeScope(hostWrapper)); |
- |
var oldShadowRoot = hostWrapper.shadowRoot; |
nextOlderShadowTreeTable.set(this, oldShadowRoot); |
+ this.treeScope_ = |
+ new TreeScope(this, getTreeScope(oldShadowRoot || hostWrapper)); |
+ |
shadowHostTable.set(this, hostWrapper); |
} |
ShadowRoot.prototype = Object.create(DocumentFragment.prototype); |
@@ -5987,29 +6314,18 @@ |
parentNode.removeChild(node); |
} |
- var distributedChildNodesTable = new WeakMap(); |
- var eventParentsTable = new WeakMap(); |
- var insertionParentTable = new WeakMap(); |
+ var distributedNodesTable = new WeakMap(); |
+ var destinationInsertionPointsTable = new WeakMap(); |
var rendererForHostTable = new WeakMap(); |
- function distributeChildToInsertionPoint(child, insertionPoint) { |
- getDistributedChildNodes(insertionPoint).push(child); |
- assignToInsertionPoint(child, insertionPoint); |
- |
- var eventParents = eventParentsTable.get(child); |
- if (!eventParents) |
- eventParentsTable.set(child, eventParents = []); |
- eventParents.push(insertionPoint); |
+ function resetDistributedNodes(insertionPoint) { |
+ distributedNodesTable.set(insertionPoint, []); |
} |
- function resetDistributedChildNodes(insertionPoint) { |
- distributedChildNodesTable.set(insertionPoint, []); |
- } |
- |
- function getDistributedChildNodes(insertionPoint) { |
- var rv = distributedChildNodesTable.get(insertionPoint); |
+ function getDistributedNodes(insertionPoint) { |
+ var rv = distributedNodesTable.get(insertionPoint); |
if (!rv) |
- distributedChildNodesTable.set(insertionPoint, rv = []); |
+ distributedNodesTable.set(insertionPoint, rv = []); |
return rv; |
} |
@@ -6021,92 +6337,6 @@ |
return result; |
} |
- /** |
- * Visits all nodes in the tree that fulfils the |predicate|. If the |visitor| |
- * function returns |false| the traversal is aborted. |
- * @param {!Node} tree |
- * @param {function(!Node) : boolean} predicate |
- * @param {function(!Node) : *} visitor |
- */ |
- function visit(tree, predicate, visitor) { |
- // This operates on logical DOM. |
- for (var node = tree.firstChild; node; node = node.nextSibling) { |
- if (predicate(node)) { |
- if (visitor(node) === false) |
- return; |
- } else { |
- visit(node, predicate, visitor); |
- } |
- } |
- } |
- |
- // Matching Insertion Points |
- // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#matching-insertion-points |
- |
- // TODO(arv): Verify this... I don't remember why I picked this regexp. |
- var selectorMatchRegExp = /^[*.:#[a-zA-Z_|]/; |
- |
- var allowedPseudoRegExp = new RegExp('^:(' + [ |
- 'link', |
- 'visited', |
- 'target', |
- 'enabled', |
- 'disabled', |
- 'checked', |
- 'indeterminate', |
- 'nth-child', |
- 'nth-last-child', |
- 'nth-of-type', |
- 'nth-last-of-type', |
- 'first-child', |
- 'last-child', |
- 'first-of-type', |
- 'last-of-type', |
- 'only-of-type', |
- ].join('|') + ')'); |
- |
- |
- /** |
- * @param {Element} node |
- * @oaram {Element} point The insertion point element. |
- * @return {boolean} Whether the node matches the insertion point. |
- */ |
- function matchesCriteria(node, point) { |
- var select = point.getAttribute('select'); |
- if (!select) |
- return true; |
- |
- // Here we know the select attribute is a non empty string. |
- select = select.trim(); |
- if (!select) |
- return true; |
- |
- if (!(node instanceof Element)) |
- return false; |
- |
- // The native matches function in IE9 does not correctly work with elements |
- // that are not in the document. |
- // TODO(arv): Implement matching in JS. |
- // https://github.com/Polymer/ShadowDOM/issues/361 |
- if (select === '*' || select === node.localName) |
- return true; |
- |
- // TODO(arv): This does not seem right. Need to check for a simple selector. |
- if (!selectorMatchRegExp.test(select)) |
- return false; |
- |
- // TODO(arv): This no longer matches the spec. |
- if (select[0] === ':' && !allowedPseudoRegExp.test(select)) |
- return false; |
- |
- try { |
- return node.matches(select); |
- } catch (ex) { |
- // Invalid selector. |
- return false; |
- } |
- } |
- |
var request = oneOf(window, [ |
'requestAnimationFrame', |
'mozRequestAnimationFrame', |
@@ -6251,19 +6481,14 @@ |
return; |
this.invalidateAttributes(); |
- this.treeComposition(); |
var host = this.host; |
- var shadowRoot = host.shadowRoot; |
- this.associateNode(host); |
- var topMostRenderer = !renderNode; |
+ this.distribution(host); |
var renderNode = opt_renderNode || new RenderNode(host); |
+ this.buildRenderTree(renderNode, host); |
- for (var node = shadowRoot.firstChild; node; node = node.nextSibling) { |
- this.renderNode(shadowRoot, renderNode, node, false); |
- } |
- |
+ var topMostRenderer = !opt_renderNode; |
if (topMostRenderer) |
renderNode.sync(); |
@@ -6284,77 +6509,154 @@ |
} |
}, |
- renderNode: function(shadowRoot, renderNode, node, isNested) { |
- if (isShadowHost(node)) { |
- renderNode = renderNode.append(node); |
- var renderer = getRendererForHost(node); |
- renderer.dirty = true; // Need to rerender due to reprojection. |
- renderer.render(renderNode); |
- } else if (isInsertionPoint(node)) { |
- this.renderInsertionPoint(shadowRoot, renderNode, node, isNested); |
- } else if (isShadowInsertionPoint(node)) { |
- this.renderShadowInsertionPoint(shadowRoot, renderNode, node); |
- } else { |
- this.renderAsAnyDomTree(shadowRoot, renderNode, node, isNested); |
- } |
+ // http://w3c.github.io/webcomponents/spec/shadow/#distribution-algorithms |
+ distribution: function(root) { |
+ this.resetAll(root); |
+ this.distributionResolution(root); |
}, |
- renderAsAnyDomTree: function(shadowRoot, renderNode, node, isNested) { |
- renderNode = renderNode.append(node); |
+ resetAll: function(node) { |
+ if (isInsertionPoint(node)) |
+ resetDistributedNodes(node); |
+ else |
+ resetDestinationInsertionPoints(node); |
+ for (var child = node.firstChild; child; child = child.nextSibling) { |
+ this.resetAll(child); |
+ } |
+ |
+ if (node.shadowRoot) |
+ this.resetAll(node.shadowRoot); |
+ |
+ if (node.olderShadowRoot) |
+ this.resetAll(node.olderShadowRoot); |
+ }, |
+ |
+ // http://w3c.github.io/webcomponents/spec/shadow/#distribution-results |
+ distributionResolution: function(node) { |
if (isShadowHost(node)) { |
- var renderer = getRendererForHost(node); |
- renderNode.skip = !renderer.dirty; |
- renderer.render(renderNode); |
- } else { |
- for (var child = node.firstChild; child; child = child.nextSibling) { |
- this.renderNode(shadowRoot, renderNode, child, isNested); |
+ var shadowHost = node; |
+ // 1.1 |
+ var pool = poolPopulation(shadowHost); |
+ |
+ var shadowTrees = getShadowTrees(shadowHost); |
+ |
+ // 1.2 |
+ for (var i = 0; i < shadowTrees.length; i++) { |
+ // 1.2.1 |
+ this.poolDistribution(shadowTrees[i], pool); |
} |
+ |
+ // 1.3 |
+ for (var i = shadowTrees.length - 1; i >= 0; i--) { |
+ var shadowTree = shadowTrees[i]; |
+ |
+ // 1.3.1 |
+ // TODO(arv): We should keep the shadow insertion points on the |
+ // shadow root (or renderer) so we don't have to search the tree |
+ // every time. |
+ var shadow = getShadowInsertionPoint(shadowTree); |
+ |
+ // 1.3.2 |
+ if (shadow) { |
+ |
+ // 1.3.2.1 |
+ var olderShadowRoot = shadowTree.olderShadowRoot; |
+ if (olderShadowRoot) { |
+ // 1.3.2.1.1 |
+ pool = poolPopulation(olderShadowRoot); |
+ } |
+ |
+ // 1.3.2.2 |
+ for (var j = 0; j < pool.length; j++) { |
+ // 1.3.2.2.1 |
+ destributeNodeInto(pool[j], shadow); |
+ } |
+ } |
+ |
+ // 1.3.3 |
+ this.distributionResolution(shadowTree); |
+ } |
} |
+ |
+ for (var child = node.firstChild; child; child = child.nextSibling) { |
+ this.distributionResolution(child); |
+ } |
}, |
- renderInsertionPoint: function(shadowRoot, renderNode, insertionPoint, |
- isNested) { |
- var distributedChildNodes = getDistributedChildNodes(insertionPoint); |
- if (distributedChildNodes.length) { |
- this.associateNode(insertionPoint); |
+ // http://w3c.github.io/webcomponents/spec/shadow/#dfn-pool-distribution-algorithm |
+ poolDistribution: function (node, pool) { |
+ if (node instanceof HTMLShadowElement) |
+ return; |
- for (var i = 0; i < distributedChildNodes.length; i++) { |
- var child = distributedChildNodes[i]; |
- if (isInsertionPoint(child) && isNested) |
- this.renderInsertionPoint(shadowRoot, renderNode, child, isNested); |
- else |
- this.renderAsAnyDomTree(shadowRoot, renderNode, child, isNested); |
+ if (node instanceof HTMLContentElement) { |
+ var content = node; |
+ this.updateDependentAttributes(content.getAttribute('select')); |
+ |
+ var anyDistributed = false; |
+ |
+ // 1.1 |
+ for (var i = 0; i < pool.length; i++) { |
+ var node = pool[i]; |
+ if (!node) |
+ continue; |
+ if (matches(node, content)) { |
+ destributeNodeInto(node, content); |
+ pool[i] = undefined; |
+ anyDistributed = true; |
+ } |
} |
- } else { |
- this.renderFallbackContent(shadowRoot, renderNode, insertionPoint); |
+ |
+ // 1.2 |
+ // Fallback content |
+ if (!anyDistributed) { |
+ for (var child = content.firstChild; |
+ child; |
+ child = child.nextSibling) { |
+ destributeNodeInto(child, content); |
+ } |
+ } |
+ |
+ return; |
} |
- this.associateNode(insertionPoint.parentNode); |
+ |
+ for (var child = node.firstChild; child; child = child.nextSibling) { |
+ this.poolDistribution(child, pool); |
+ } |
}, |
- renderShadowInsertionPoint: function(shadowRoot, renderNode, |
- shadowInsertionPoint) { |
- var nextOlderTree = shadowRoot.olderShadowRoot; |
- if (nextOlderTree) { |
- assignToInsertionPoint(nextOlderTree, shadowInsertionPoint); |
- this.associateNode(shadowInsertionPoint.parentNode); |
- for (var node = nextOlderTree.firstChild; |
- node; |
- node = node.nextSibling) { |
- this.renderNode(nextOlderTree, renderNode, node, true); |
- } |
- } else { |
- this.renderFallbackContent(shadowRoot, renderNode, |
- shadowInsertionPoint); |
+ buildRenderTree: function(renderNode, node) { |
+ var children = this.compose(node); |
+ for (var i = 0; i < children.length; i++) { |
+ var child = children[i]; |
+ var childRenderNode = renderNode.append(child); |
+ this.buildRenderTree(childRenderNode, child); |
} |
+ |
+ if (isShadowHost(node)) { |
+ var renderer = getRendererForHost(node); |
+ renderer.dirty = false; |
+ } |
+ |
}, |
- renderFallbackContent: function(shadowRoot, renderNode, fallbackHost) { |
- this.associateNode(fallbackHost); |
- this.associateNode(fallbackHost.parentNode); |
- for (var node = fallbackHost.firstChild; node; node = node.nextSibling) { |
- this.renderAsAnyDomTree(shadowRoot, renderNode, node, false); |
+ compose: function(node) { |
+ var children = []; |
+ var p = node.shadowRoot || node; |
+ for (var child = p.firstChild; child; child = child.nextSibling) { |
+ if (isInsertionPoint(child)) { |
+ this.associateNode(p); |
+ var distributedNodes = getDistributedNodes(child); |
+ for (var j = 0; j < distributedNodes.length; j++) { |
+ var distributedNode = distributedNodes[j]; |
+ if (isFinalDestination(child, distributedNode)) |
+ children.push(distributedNode); |
+ } |
+ } else { |
+ children.push(child); |
+ } |
} |
+ return children; |
}, |
/** |
@@ -6395,102 +6697,103 @@ |
return this.attributes[name]; |
}, |
- // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-distribution-algorithm |
- distribute: function(tree, pool) { |
- var self = this; |
+ associateNode: function(node) { |
+ node.impl.polymerShadowRenderer_ = this; |
+ } |
+ }; |
- visit(tree, isActiveInsertionPoint, |
- function(insertionPoint) { |
- resetDistributedChildNodes(insertionPoint); |
- self.updateDependentAttributes( |
- insertionPoint.getAttribute('select')); |
+ // http://w3c.github.io/webcomponents/spec/shadow/#dfn-pool-population-algorithm |
+ function poolPopulation(node) { |
+ var pool = []; |
+ for (var child = node.firstChild; child; child = child.nextSibling) { |
+ if (isInsertionPoint(child)) { |
+ pool.push.apply(pool, getDistributedNodes(child)); |
+ } else { |
+ pool.push(child); |
+ } |
+ } |
+ return pool; |
+ } |
- for (var i = 0; i < pool.length; i++) { // 1.2 |
- var node = pool[i]; // 1.2.1 |
- if (node === undefined) // removed |
- continue; |
- if (matchesCriteria(node, insertionPoint)) { // 1.2.2 |
- distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2.1 |
- pool[i] = undefined; // 1.2.2.2 |
- } |
- } |
- }); |
- }, |
+ function getShadowInsertionPoint(node) { |
+ if (node instanceof HTMLShadowElement) |
+ return node; |
+ if (node instanceof HTMLContentElement) |
+ return null; |
+ for (var child = node.firstChild; child; child = child.nextSibling) { |
+ var res = getShadowInsertionPoint(child); |
+ if (res) |
+ return res; |
+ } |
+ return null; |
+ } |
- // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-tree-composition |
- treeComposition: function () { |
- var shadowHost = this.host; |
- var tree = shadowHost.shadowRoot; // 1. |
- var pool = []; // 2. |
+ function destributeNodeInto(child, insertionPoint) { |
+ getDistributedNodes(insertionPoint).push(child); |
+ var points = destinationInsertionPointsTable.get(child); |
+ if (!points) |
+ destinationInsertionPointsTable.set(child, [insertionPoint]); |
+ else |
+ points.push(insertionPoint); |
+ } |
- for (var child = shadowHost.firstChild; |
- child; |
- child = child.nextSibling) { // 3. |
- if (isInsertionPoint(child)) { // 3.2. |
- var reprojected = getDistributedChildNodes(child); // 3.2.1. |
- // if reprojected is undef... reset it? |
- if (!reprojected || !reprojected.length) // 3.2.2. |
- reprojected = getChildNodesSnapshot(child); |
- pool.push.apply(pool, reprojected); // 3.2.3. |
- } else { |
- pool.push(child); // 3.3. |
- } |
- } |
+ function getDestinationInsertionPoints(node) { |
+ return destinationInsertionPointsTable.get(node); |
+ } |
- var shadowInsertionPoint, point; |
- while (tree) { // 4. |
- // 4.1. |
- shadowInsertionPoint = undefined; // Reset every iteration. |
- visit(tree, isActiveShadowInsertionPoint, function(point) { |
- shadowInsertionPoint = point; |
- return false; |
- }); |
- point = shadowInsertionPoint; |
+ function resetDestinationInsertionPoints(node) { |
+ // IE11 crashes when delete is used. |
+ destinationInsertionPointsTable.set(node, undefined); |
+ } |
- this.distribute(tree, pool); // 4.2. |
- if (point) { // 4.3. |
- var nextOlderTree = tree.olderShadowRoot; // 4.3.1. |
- if (!nextOlderTree) { |
- break; // 4.3.1.1. |
- } else { |
- tree = nextOlderTree; // 4.3.2.2. |
- assignToInsertionPoint(tree, point); // 4.3.2.2. |
- continue; // 4.3.2.3. |
- } |
- } else { |
- break; // 4.4. |
- } |
- } |
- }, |
+ // AllowedSelectors : |
+ // TypeSelector |
+ // * |
+ // ClassSelector |
+ // IDSelector |
+ // AttributeSelector |
+ var selectorStartCharRe = /^[*.#[a-zA-Z_|]/; |
- associateNode: function(node) { |
- node.impl.polymerShadowRenderer_ = this; |
- } |
- }; |
+ function matches(node, contentElement) { |
+ var select = contentElement.getAttribute('select'); |
+ if (!select) |
+ return true; |
- function isInsertionPoint(node) { |
- // Should this include <shadow>? |
- return node instanceof HTMLContentElement; |
- } |
+ // Here we know the select attribute is a non empty string. |
+ select = select.trim(); |
+ if (!select) |
+ return true; |
- function isActiveInsertionPoint(node) { |
- // <content> inside another <content> or <shadow> is considered inactive. |
- return node instanceof HTMLContentElement; |
+ if (!(node instanceof Element)) |
+ return false; |
+ |
+ if (!selectorStartCharRe.test(select)) |
+ return false; |
+ |
+ try { |
+ return node.matches(select); |
+ } catch (ex) { |
+ // Invalid selector. |
+ return false; |
+ } |
} |
- function isShadowInsertionPoint(node) { |
- return node instanceof HTMLShadowElement; |
+ function isFinalDestination(insertionPoint, node) { |
+ var points = getDestinationInsertionPoints(node); |
+ return points && points[points.length - 1] === insertionPoint; |
} |
- function isActiveShadowInsertionPoint(node) { |
- // <shadow> inside another <content> or <shadow> is considered inactive. |
- return node instanceof HTMLShadowElement; |
+ function isInsertionPoint(node) { |
+ return node instanceof HTMLContentElement || |
+ node instanceof HTMLShadowElement; |
} |
function isShadowHost(shadowHost) { |
return shadowHost.shadowRoot; |
} |
+ // Returns the shadow trees as an array, with the youngest tree at the |
+ // beginning of the array. |
function getShadowTrees(host) { |
var trees = []; |
@@ -6500,11 +6803,6 @@ |
return trees; |
} |
- function assignToInsertionPoint(tree, point) { |
- insertionParentTable.set(tree, point); |
- } |
- |
- // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#rendering-shadow-trees |
function render(host) { |
new ShadowRenderer(host).render(); |
}; |
@@ -6532,15 +6830,21 @@ |
return false; |
}; |
- HTMLContentElement.prototype.getDistributedNodes = function() { |
+ HTMLContentElement.prototype.getDistributedNodes = |
+ HTMLShadowElement.prototype.getDistributedNodes = function() { |
// TODO(arv): We should only rerender the dirty ancestor renderers (from |
// the root and down). |
renderAllPending(); |
- return getDistributedChildNodes(this); |
+ return getDistributedNodes(this); |
}; |
- HTMLShadowElement.prototype.nodeIsInserted_ = |
- HTMLContentElement.prototype.nodeIsInserted_ = function() { |
+ Element.prototype.getDestinationInsertionPoints = function() { |
+ renderAllPending(); |
+ return getDestinationInsertionPoints(this) || []; |
+ }; |
+ |
+ HTMLContentElement.prototype.nodeIsInserted_ = |
+ HTMLShadowElement.prototype.nodeIsInserted_ = function() { |
// Invalidate old renderer if any. |
this.invalidateShadowRenderer(); |
@@ -6553,12 +6857,12 @@ |
renderer.invalidate(); |
}; |
- scope.eventParentsTable = eventParentsTable; |
scope.getRendererForHost = getRendererForHost; |
scope.getShadowTrees = getShadowTrees; |
- scope.insertionParentTable = insertionParentTable; |
scope.renderAllPending = renderAllPending; |
+ scope.getDestinationInsertionPoints = getDestinationInsertionPoints; |
+ |
// Exposed for testing |
scope.visual = { |
insertBefore: insertBefore, |
@@ -6956,6 +7260,10 @@ |
new DOMImplementation(unwrap(this).implementation); |
implementationTable.set(this, implementation); |
return implementation; |
+ }, |
+ |
+ get defaultView() { |
+ return wrap(unwrap(this).defaultView); |
} |
}); |
@@ -7071,9 +7379,13 @@ |
renderAllPending(); |
return new Selection(originalGetSelection.call(unwrap(this))); |
}, |
+ |
+ get document() { |
+ return wrap(unwrap(this).document); |
+ } |
}); |
- registerWrapper(OriginalWindow, Window); |
+ registerWrapper(OriginalWindow, Window, window); |
scope.wrappers.Window = Window; |
@@ -7214,10 +7526,14 @@ |
})(window.ShadowDOMPolyfill); |
/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
+ |
(function() { |
// convenient global |
@@ -7254,9 +7570,12 @@ |
})(); |
/* |
- * Copyright 2012 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
/* |
@@ -7672,13 +7991,16 @@ |
if (this.selectorNeedsScoping(p, scopeSelector)) { |
p = (strict && !p.match(polyfillHostNoCombinator)) ? |
this.applyStrictSelectorScope(p, scopeSelector) : |
- this.applySimpleSelectorScope(p, scopeSelector); |
+ this.applySelectorScope(p, scopeSelector); |
} |
r.push(p); |
}, this); |
return r.join(', '); |
}, |
selectorNeedsScoping: function(selector, scopeSelector) { |
+ if (Array.isArray(scopeSelector)) { |
+ return true; |
+ } |
var re = this.makeScopeMatcher(scopeSelector); |
return !selector.match(re); |
}, |
@@ -7686,6 +8008,19 @@ |
scopeSelector = scopeSelector.replace(/\[/g, '\\[').replace(/\[/g, '\\]'); |
return new RegExp('^(' + scopeSelector + ')' + selectorReSuffix, 'm'); |
}, |
+ applySelectorScope: function(selector, selectorScope) { |
+ return Array.isArray(selectorScope) ? |
+ this.applySelectorScopeList(selector, selectorScope) : |
+ this.applySimpleSelectorScope(selector, selectorScope); |
+ }, |
+ // apply an array of selectors |
+ applySelectorScopeList: function(selector, scopeSelectorList) { |
+ var r = []; |
+ for (var i=0, s; (s=scopeSelectorList[i]); i++) { |
+ r.push(this.applySimpleSelectorScope(selector, s)); |
+ } |
+ return r.join(', '); |
+ }, |
// scope via name and [is=name] |
applySimpleSelectorScope: function(selector, scopeSelector) { |
if (selector.match(polyfillHostRe)) { |
@@ -7991,25 +8326,24 @@ |
scope.ShadowCSS = ShadowCSS; |
})(window.Platform); |
+ |
} else { |
/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
-(function() { |
- // poor man's adapter for template.content on various platform scenarios |
- window.templateContent = window.templateContent || function(inTemplate) { |
- return inTemplate.content; |
- }; |
+(function(scope) { |
// so we can call wrap/unwrap without testing for ShadowDOMPolyfill |
- |
window.wrap = window.unwrap = function(n){ |
return n; |
} |
- |
+ |
addEventListener('DOMContentLoaded', function() { |
if (CustomElements.useNative === false) { |
var originalCreateShadowRoot = Element.prototype.createShadowRoot; |
@@ -8020,8 +8354,8 @@ |
}; |
} |
}); |
- |
- window.templateContent = function(inTemplate) { |
+ |
+ Platform.templateContent = function(inTemplate) { |
// if MDV exists, it may need to boostrap this template to reveal content |
if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { |
HTMLTemplateElement.bootstrap(inTemplate); |
@@ -8038,7 +8372,8 @@ |
return inTemplate.content || inTemplate._content; |
}; |
-})(); |
+})(window.Platform); |
+ |
} |
/* Any copyright is dedicated to the Public Domain. |
* http://creativecommons.org/publicdomain/zero/1.0/ */ |
@@ -8607,9 +8942,12 @@ |
})(window); |
/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
(function(scope) { |
@@ -8665,20 +9003,16 @@ |
scope.mixin = mixin; |
})(window.Platform); |
-// Copyright 2011 Google Inc. |
-// |
-// Licensed under the Apache License, Version 2.0 (the "License"); |
-// you may not use this file except in compliance with the License. |
-// You may obtain a copy of the License at |
-// |
-// http://www.apache.org/licenses/LICENSE-2.0 |
-// |
-// Unless required by applicable law or agreed to in writing, software |
-// distributed under the License is distributed on an "AS IS" BASIS, |
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
-// See the License for the specific language governing permissions and |
-// limitations under the License. |
+/* |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
+ */ |
+ |
(function(scope) { |
'use strict'; |
@@ -8813,16 +9147,31 @@ |
})(window.Platform); |
/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
// poor man's adapter for template.content on various platform scenarios |
-window.templateContent = window.templateContent || function(inTemplate) { |
- return inTemplate.content; |
-}; |
(function(scope) { |
+ scope.templateContent = scope.templateContent || function(inTemplate) { |
+ return inTemplate.content; |
+ }; |
+})(window.Platform); |
+ |
+/* |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
+ */ |
+ |
+(function(scope) { |
scope = scope || (window.Inspector = {}); |
@@ -9011,13 +9360,15 @@ |
})(window.Inspector); |
- |
- |
/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
+ |
(function(scope) { |
// TODO(sorvell): It's desireable to provide a default stylesheet |
@@ -9041,6 +9392,15 @@ |
})(Platform); |
+/* |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
+ */ |
+ |
(function(scope) { |
function withDependencies(task, depends) { |
@@ -9088,11 +9448,16 @@ |
scope.using = using; |
})(window); |
+ |
/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
+ |
(function(scope) { |
var iterations = 0; |
@@ -9122,9 +9487,12 @@ |
/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
(function(scope) { |
@@ -9159,9 +9527,9 @@ |
url = url || style.ownerDocument.baseURI; |
style.textContent = this.resolveCssText(style.textContent, url); |
}, |
- resolveCssText: function(cssText, baseUrl) { |
- cssText = replaceUrlsInCssText(cssText, baseUrl, CSS_URL_REGEXP); |
- return replaceUrlsInCssText(cssText, baseUrl, CSS_IMPORT_REGEXP); |
+ resolveCssText: function(cssText, baseUrl, keepAbsolute) { |
+ cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEXP); |
+ return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEXP); |
}, |
resolveAttributes: function(root, url) { |
if (root.hasAttributes && root.hasAttributes()) { |
@@ -9179,10 +9547,15 @@ |
url = url || node.ownerDocument.baseURI; |
URL_ATTRS.forEach(function(v) { |
var attr = node.attributes[v]; |
- if (attr && attr.value && |
- (attr.value.search(URL_TEMPLATE_SEARCH) < 0)) { |
- var urlPath = resolveRelativeUrl(url, attr.value); |
- attr.value = urlPath; |
+ var value = attr && attr.value; |
+ var replacement; |
+ if (value && value.search(URL_TEMPLATE_SEARCH) < 0) { |
+ if (v === 'style') { |
+ replacement = replaceUrlsInCssText(value, url, CSS_URL_REGEXP); |
+ } else { |
+ replacement = resolveRelativeUrl(url, value); |
+ } |
+ attr.value = replacement; |
} |
}); |
} |
@@ -9190,36 +9563,42 @@ |
var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; |
var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; |
-var URL_ATTRS = ['href', 'src', 'action']; |
+var URL_ATTRS = ['href', 'src', 'action', 'style']; |
var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; |
var URL_TEMPLATE_SEARCH = '{{.*}}'; |
-function replaceUrlsInCssText(cssText, baseUrl, regexp) { |
+function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) { |
return cssText.replace(regexp, function(m, pre, url, post) { |
var urlPath = url.replace(/["']/g, ''); |
- urlPath = resolveRelativeUrl(baseUrl, urlPath); |
+ urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute); |
return pre + '\'' + urlPath + '\'' + post; |
}); |
} |
-function resolveRelativeUrl(baseUrl, url) { |
+function resolveRelativeUrl(baseUrl, url, keepAbsolute) { |
+ // do not resolve '/' absolute urls |
+ if (url && url[0] === '/') { |
+ return url; |
+ } |
var u = new URL(url, baseUrl); |
- return makeDocumentRelPath(u.href); |
+ return keepAbsolute ? u.href : makeDocumentRelPath(u.href); |
} |
function makeDocumentRelPath(url) { |
- var root = document.baseURI; |
+ var root = new URL(document.baseURI); |
var u = new URL(url, root); |
if (u.host === root.host && u.port === root.port && |
u.protocol === root.protocol) { |
- return makeRelPath(root.pathname, u.pathname); |
+ return makeRelPath(root, u); |
} else { |
return url; |
} |
} |
// make a relative path from source to target |
-function makeRelPath(source, target) { |
+function makeRelPath(sourceUrl, targetUrl) { |
+ var source = sourceUrl.pathname; |
+ var target = targetUrl.pathname; |
var s = source.split('/'); |
var t = target.split('/'); |
while (s.length && s[0] === t[0]){ |
@@ -9229,7 +9608,7 @@ |
for (var i = 0, l = s.length - 1; i < l; i++) { |
t.unshift('..'); |
} |
- return t.join('/'); |
+ return t.join('/') + targetUrl.search + targetUrl.hash; |
} |
// exports |
@@ -10467,8 +10846,7 @@ |
var loaded = 0, l = imports.length; |
function checkDone(d) { |
if (loaded == l) { |
- // go async to ensure parser isn't stuck on a script tag |
- requestAnimationFrame(callback); |
+ callback && callback(); |
} |
} |
function loadedImport(e) { |
@@ -10490,10 +10868,50 @@ |
} |
function isImportLoaded(link) { |
- return useNative ? (link.import && (link.import.readyState !== 'loading')) : |
+ return useNative ? (link.import && (link.import.readyState !== 'loading')) || link.__loaded : |
link.__importParsed; |
} |
+// TODO(sorvell): install a mutation observer to see if HTMLImports have loaded |
+// this is a workaround for https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007 |
+// and should be removed when this bug is addressed. |
+if (useNative) { |
+ new MutationObserver(function(mxns) { |
+ for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) { |
+ if (m.addedNodes) { |
+ handleImports(m.addedNodes); |
+ } |
+ } |
+ }).observe(document.head, {childList: true}); |
+ |
+ function handleImports(nodes) { |
+ for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { |
+ if (isImport(n)) { |
+ handleImport(n); |
+ } |
+ } |
+ } |
+ |
+ function isImport(element) { |
+ return element.localName === 'link' && element.rel === 'import'; |
+ } |
+ |
+ function handleImport(element) { |
+ var loaded = element.import; |
+ if (loaded) { |
+ markTargetLoaded({target: element}); |
+ } else { |
+ element.addEventListener('load', markTargetLoaded); |
+ element.addEventListener('error', markTargetLoaded); |
+ } |
+ } |
+ |
+ function markTargetLoaded(event) { |
+ event.target.__loaded = true; |
+ } |
+ |
+} |
+ |
// exports |
scope.hasNative = hasNative; |
scope.useNative = useNative; |
@@ -11175,7 +11593,11 @@ |
// work out prototype when using type-extension |
if (definition.is) { |
var inst = document.createElement(definition.tag); |
- nativePrototype = Object.getPrototypeOf(inst); |
+ var expectedPrototype = Object.getPrototypeOf(inst); |
+ // only set nativePrototype if it will actually appear in the definition's chain |
+ if (expectedPrototype === definition.prototype) { |
+ nativePrototype = expectedPrototype; |
+ } |
} |
// ensure __proto__ reference is installed at each point on the prototype |
// chain. |
@@ -11184,13 +11606,13 @@ |
// limited support for prototype traversal. |
var proto = definition.prototype, ancestor; |
while (proto && (proto !== nativePrototype)) { |
- var ancestor = Object.getPrototypeOf(proto); |
+ ancestor = Object.getPrototypeOf(proto); |
proto.__proto__ = ancestor; |
proto = ancestor; |
} |
+ // cache this in case of mixin |
+ definition.native = nativePrototype; |
} |
- // cache this in case of mixin |
- definition.native = nativePrototype; |
} |
// SECTION 4 |
@@ -11581,10 +12003,14 @@ |
})(window.CustomElements); |
/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
+ |
(function() { |
if (window.ShadowDOMPolyfill) { |
@@ -11611,18 +12037,26 @@ |
})(); |
/* |
- * Copyright 2014 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
+ |
(function(scope) { |
var endOfMicrotask = scope.endOfMicrotask; |
// Generic url loader |
function Loader(regex) { |
+ this.cache = Object.create(null); |
+ this.map = Object.create(null); |
+ this.requests = 0; |
this.regex = regex; |
} |
Loader.prototype = { |
+ |
// TODO(dfreedm): there may be a better factoring here |
// extract absolute urls from the text (full of relative urls) |
extractUrls: function(text, base) { |
@@ -11638,63 +12072,78 @@ |
// returns a map of absolute url to text |
process: function(text, root, callback) { |
var matches = this.extractUrls(text, root); |
- this.fetch(matches, {}, callback); |
+ |
+ // every call to process returns all the text this loader has ever received |
+ var done = callback.bind(null, this.map); |
+ this.fetch(matches, done); |
}, |
// build a mapping of url -> text from matches |
- fetch: function(matches, map, callback) { |
+ fetch: function(matches, callback) { |
var inflight = matches.length; |
// return early if there is no fetching to be done |
if (!inflight) { |
- return callback(map); |
+ return callback(); |
} |
+ // wait for all subrequests to return |
var done = function() { |
if (--inflight === 0) { |
- callback(map); |
+ callback(); |
} |
}; |
- // map url -> responseText |
- var handleXhr = function(err, request) { |
- var match = request.match; |
- var key = match.url; |
- // handle errors with an empty string |
- if (err) { |
- map[key] = ''; |
- return done(); |
- } |
- var response = request.response || request.responseText; |
- map[key] = response; |
- this.fetch(this.extractUrls(response, key), map, done); |
- }; |
- |
+ // start fetching all subrequests |
var m, req, url; |
for (var i = 0; i < inflight; i++) { |
m = matches[i]; |
url = m.url; |
+ req = this.cache[url]; |
// if this url has already been requested, skip requesting it again |
- if (map[url]) { |
- // Async call to done to simplify the inflight logic |
- endOfMicrotask(done); |
- continue; |
+ if (!req) { |
+ req = this.xhr(url); |
+ req.match = m; |
+ this.cache[url] = req; |
} |
- req = this.xhr(url, handleXhr, this); |
- req.match = m; |
- // tag the map with an XHR request to deduplicate at the same level |
- map[url] = req; |
+ // wait for the request to process its subrequests |
+ req.wait(done); |
} |
}, |
- xhr: function(url, callback, scope) { |
+ handleXhr: function(request) { |
+ var match = request.match; |
+ var url = match.url; |
+ |
+ // handle errors with an empty string |
+ var response = request.response || request.responseText || ''; |
+ this.map[url] = response; |
+ this.fetch(this.extractUrls(response, url), request.resolve); |
+ }, |
+ xhr: function(url) { |
+ this.requests++; |
var request = new XMLHttpRequest(); |
request.open('GET', url, true); |
request.send(); |
- request.onload = function() { |
- callback.call(scope, null, request); |
+ request.onerror = request.onload = this.handleXhr.bind(this, request); |
+ |
+ // queue of tasks to run after XHR returns |
+ request.pending = []; |
+ request.resolve = function() { |
+ var pending = request.pending; |
+ for(var i = 0; i < pending.length; i++) { |
+ pending[i](); |
+ } |
+ request.pending = null; |
}; |
- request.onerror = function() { |
- callback.call(scope, null, request); |
+ |
+ // if we have already resolved, pending is null, async call the callback |
+ request.wait = function(fn) { |
+ if (request.pending) { |
+ request.pending.push(fn); |
+ } else { |
+ endOfMicrotask(fn); |
+ } |
}; |
+ |
return request; |
} |
}; |
@@ -11703,10 +12152,14 @@ |
})(window.Platform); |
/* |
- * Copyright 2014 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
+ |
(function(scope) { |
var urlResolver = scope.urlResolver; |
@@ -11725,9 +12178,8 @@ |
this.loader.process(text, url, done); |
}, |
// resolve the textContent of a style node |
- resolveNode: function(style, callback) { |
+ resolveNode: function(style, url, callback) { |
var text = style.textContent; |
- var url = style.ownerDocument.baseURI; |
var done = function(text) { |
style.textContent = text; |
callback(style); |
@@ -11741,15 +12193,15 @@ |
for (var i = 0; i < matches.length; i++) { |
match = matches[i]; |
url = match.url; |
- // resolve any css text to be relative to the importer |
- intermediate = urlResolver.resolveCssText(map[url], url); |
+ // resolve any css text to be relative to the importer, keep absolute url |
+ intermediate = urlResolver.resolveCssText(map[url], url, true); |
// flatten intermediate @imports |
- intermediate = this.flatten(intermediate, url, map); |
+ intermediate = this.flatten(intermediate, base, map); |
text = text.replace(match.matched, intermediate); |
} |
return text; |
}, |
- loadStyles: function(styles, callback) { |
+ loadStyles: function(styles, base, callback) { |
var loaded=0, l = styles.length; |
// called in the context of the style |
function loadedStyle(style) { |
@@ -11759,7 +12211,7 @@ |
} |
} |
for (var i=0, s; (i<l) && (s=styles[i]); i++) { |
- this.resolveNode(s, loadedStyle); |
+ this.resolveNode(s, base, loadedStyle); |
} |
} |
}; |
@@ -11771,2582 +12223,6 @@ |
})(window.Platform); |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-(function(scope) { |
- scope = scope || {}; |
- scope.external = scope.external || {}; |
- var target = { |
- shadow: function(inEl) { |
- if (inEl) { |
- return inEl.shadowRoot || inEl.webkitShadowRoot; |
- } |
- }, |
- canTarget: function(shadow) { |
- return shadow && Boolean(shadow.elementFromPoint); |
- }, |
- targetingShadow: function(inEl) { |
- var s = this.shadow(inEl); |
- if (this.canTarget(s)) { |
- return s; |
- } |
- }, |
- olderShadow: function(shadow) { |
- var os = shadow.olderShadowRoot; |
- if (!os) { |
- var se = shadow.querySelector('shadow'); |
- if (se) { |
- os = se.olderShadowRoot; |
- } |
- } |
- return os; |
- }, |
- allShadows: function(element) { |
- var shadows = [], s = this.shadow(element); |
- while(s) { |
- shadows.push(s); |
- s = this.olderShadow(s); |
- } |
- return shadows; |
- }, |
- searchRoot: function(inRoot, x, y) { |
- 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; |
- } |
- } |
- // light dom element is the target |
- return t; |
- } |
- }, |
- owner: function(element) { |
- var s = element; |
- // walk up until you hit the shadow root or document |
- while (s.parentNode) { |
- s = s.parentNode; |
- } |
- // the owner element is expected to be a Document or ShadowRoot |
- if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGMENT_NODE) { |
- s = document; |
- } |
- return s; |
- }, |
- findTarget: function(inEvent) { |
- 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); |
- // if x, y is not in this root, fall back to document search |
- if (!s.elementFromPoint(x, y)) { |
- s = document; |
- } |
- return this.searchRoot(s, x, y); |
- } |
- }; |
- scope.targetFinding = target; |
- scope.findTarget = target.findTarget.bind(target); |
- |
- window.PointerEventsPolyfill = scope; |
-})(window.PointerEventsPolyfill); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
-(function() { |
- function shadowSelector(v) { |
- return 'body /shadow-deep/ ' + selector(v); |
- } |
- function selector(v) { |
- return '[touch-action="' + v + '"]'; |
- } |
- function rule(v) { |
- return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + '; touch-action-delay: none; }'; |
- } |
- var attrib2css = [ |
- 'none', |
- 'auto', |
- 'pan-x', |
- 'pan-y', |
- { |
- rule: 'pan-x pan-y', |
- selectors: [ |
- 'pan-x pan-y', |
- 'pan-y pan-x' |
- ] |
- } |
- ]; |
- var styles = ''; |
- // only install stylesheet if the browser has touch action support |
- var head = document.head; |
- var hasNativePE = window.PointerEvent || window.MSPointerEvent; |
- // only add shadow selectors if shadowdom is supported |
- var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoot; |
- |
- if (hasNativePE) { |
- attrib2css.forEach(function(r) { |
- if (String(r) === r) { |
- styles += selector(r) + rule(r) + '\n'; |
- if (hasShadowRoot) { |
- styles += shadowSelector(r) + rule(r) + '\n'; |
- } |
- } else { |
- styles += r.selectors.map(selector) + rule(r.rule) + '\n'; |
- if (hasShadowRoot) { |
- styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; |
- } |
- } |
- }); |
- |
- var el = document.createElement('style'); |
- el.textContent = styles; |
- document.head.appendChild(el); |
- } |
-})(); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * This is the constructor for new PointerEvents. |
- * |
- * New Pointer Events must be given a type, and an optional dictionary of |
- * initialization properties. |
- * |
- * Due to certain platform requirements, events returned from the constructor |
- * identify as MouseEvents. |
- * |
- * @constructor |
- * @param {String} inType The type of the event to create. |
- * @param {Object} [inDict] An optional dictionary of initial event properties. |
- * @return {Event} A new PointerEvent of type `inType` and initialized with properties from `inDict`. |
- */ |
-(function(scope) { |
- |
- var MOUSE_PROPS = [ |
- 'bubbles', |
- 'cancelable', |
- 'view', |
- 'detail', |
- 'screenX', |
- 'screenY', |
- 'clientX', |
- 'clientY', |
- 'ctrlKey', |
- 'altKey', |
- 'shiftKey', |
- 'metaKey', |
- 'button', |
- 'relatedTarget', |
- 'pageX', |
- 'pageY' |
- ]; |
- |
- var MOUSE_DEFAULTS = [ |
- false, |
- false, |
- null, |
- null, |
- 0, |
- 0, |
- 0, |
- 0, |
- false, |
- false, |
- false, |
- false, |
- 0, |
- null, |
- 0, |
- 0 |
- ]; |
- |
- function PointerEvent(inType, inDict) { |
- inDict = inDict || Object.create(null); |
- |
- var e = document.createEvent('Event'); |
- e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); |
- |
- // define inherited MouseEvent properties |
- for(var i = 0, p; i < MOUSE_PROPS.length; i++) { |
- p = MOUSE_PROPS[i]; |
- e[p] = inDict[p] || MOUSE_DEFAULTS[i]; |
- } |
- e.buttons = inDict.buttons || 0; |
- |
- // Spec requires that pointers without pressure specified use 0.5 for down |
- // state and 0 for up state. |
- var pressure = 0; |
- if (inDict.pressure) { |
- pressure = inDict.pressure; |
- } else { |
- pressure = e.buttons ? 0.5 : 0; |
- } |
- |
- // add x/y properties aliased to clientX/Y |
- e.x = e.clientX; |
- e.y = e.clientY; |
- |
- // define the properties of the PointerEvent interface |
- e.pointerId = inDict.pointerId || 0; |
- e.width = inDict.width || 0; |
- e.height = inDict.height || 0; |
- e.pressure = pressure; |
- e.tiltX = inDict.tiltX || 0; |
- e.tiltY = inDict.tiltY || 0; |
- e.pointerType = inDict.pointerType || ''; |
- e.hwTimestamp = inDict.hwTimestamp || 0; |
- e.isPrimary = inDict.isPrimary || false; |
- return e; |
- } |
- |
- // attach to window |
- if (!scope.PointerEvent) { |
- scope.PointerEvent = PointerEvent; |
- } |
-})(window); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * This module implements an map of pointer states |
- */ |
-(function(scope) { |
- var USE_MAP = window.Map && window.Map.prototype.forEach; |
- var POINTERS_FN = function(){ return this.size; }; |
- function PointerMap() { |
- if (USE_MAP) { |
- var m = new Map(); |
- m.pointers = POINTERS_FN; |
- return m; |
- } else { |
- this.keys = []; |
- this.values = []; |
- } |
- } |
- |
- PointerMap.prototype = { |
- set: function(inId, inEvent) { |
- var i = this.keys.indexOf(inId); |
- if (i > -1) { |
- this.values[i] = inEvent; |
- } else { |
- this.keys.push(inId); |
- this.values.push(inEvent); |
- } |
- }, |
- has: function(inId) { |
- return this.keys.indexOf(inId) > -1; |
- }, |
- 'delete': function(inId) { |
- var i = this.keys.indexOf(inId); |
- if (i > -1) { |
- this.keys.splice(i, 1); |
- this.values.splice(i, 1); |
- } |
- }, |
- get: function(inId) { |
- var i = this.keys.indexOf(inId); |
- return this.values[i]; |
- }, |
- clear: function() { |
- this.keys.length = 0; |
- this.values.length = 0; |
- }, |
- // return value, key, map |
- forEach: function(callback, thisArg) { |
- this.values.forEach(function(v, i) { |
- callback.call(thisArg, v, this.keys[i], this); |
- }, this); |
- }, |
- pointers: function() { |
- return this.keys.length; |
- } |
- }; |
- |
- scope.PointerMap = PointerMap; |
-})(window.PointerEventsPolyfill); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-(function(scope) { |
- var CLONE_PROPS = [ |
- // MouseEvent |
- 'bubbles', |
- 'cancelable', |
- 'view', |
- 'detail', |
- 'screenX', |
- 'screenY', |
- 'clientX', |
- 'clientY', |
- 'ctrlKey', |
- 'altKey', |
- 'shiftKey', |
- 'metaKey', |
- 'button', |
- 'relatedTarget', |
- // DOM Level 3 |
- 'buttons', |
- // PointerEvent |
- 'pointerId', |
- 'width', |
- 'height', |
- 'pressure', |
- 'tiltX', |
- 'tiltY', |
- 'pointerType', |
- 'hwTimestamp', |
- 'isPrimary', |
- // event instance |
- 'type', |
- 'target', |
- 'currentTarget', |
- 'which', |
- 'pageX', |
- 'pageY' |
- ]; |
- |
- var CLONE_DEFAULTS = [ |
- // MouseEvent |
- false, |
- false, |
- null, |
- null, |
- 0, |
- 0, |
- 0, |
- 0, |
- false, |
- false, |
- false, |
- false, |
- 0, |
- null, |
- // DOM Level 3 |
- 0, |
- // PointerEvent |
- 0, |
- 0, |
- 0, |
- 0, |
- 0, |
- 0, |
- '', |
- 0, |
- false, |
- // event instance |
- '', |
- null, |
- null, |
- 0, |
- 0, |
- 0 |
- ]; |
- |
- var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); |
- |
- /** |
- * This module is for normalizing events. Mouse and Touch events will be |
- * collected here, and fire PointerEvents that have the same semantics, no |
- * matter the source. |
- * Events fired: |
- * - pointerdown: a pointing is added |
- * - pointerup: a pointer is removed |
- * - pointermove: a pointer is moved |
- * - pointerover: a pointer crosses into an element |
- * - pointerout: a pointer leaves an element |
- * - pointercancel: a pointer will no longer generate events |
- */ |
- var dispatcher = { |
- pointermap: new scope.PointerMap(), |
- eventMap: Object.create(null), |
- captureInfo: Object.create(null), |
- // Scope objects for native events. |
- // This exists for ease of testing. |
- eventSources: Object.create(null), |
- eventSourceList: [], |
- /** |
- * Add a new event source that will generate pointer events. |
- * |
- * `inSource` must contain an array of event names named `events`, and |
- * functions with the names specified in the `events` array. |
- * @param {string} name A name for the event source |
- * @param {Object} source A new source of platform events. |
- */ |
- registerSource: function(name, source) { |
- var s = source; |
- var newEvents = s.events; |
- if (newEvents) { |
- newEvents.forEach(function(e) { |
- if (s[e]) { |
- this.eventMap[e] = s[e].bind(s); |
- } |
- }, this); |
- this.eventSources[name] = s; |
- this.eventSourceList.push(s); |
- } |
- }, |
- register: function(element) { |
- var l = this.eventSourceList.length; |
- for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
- // call eventsource register |
- es.register.call(es, element); |
- } |
- }, |
- unregister: function(element) { |
- var l = this.eventSourceList.length; |
- for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
- // call eventsource register |
- es.unregister.call(es, element); |
- } |
- }, |
- contains: scope.external.contains || function(container, contained) { |
- return container.contains(contained); |
- }, |
- // EVENTS |
- down: function(inEvent) { |
- inEvent.bubbles = true; |
- this.fireEvent('pointerdown', inEvent); |
- }, |
- move: function(inEvent) { |
- inEvent.bubbles = true; |
- this.fireEvent('pointermove', inEvent); |
- }, |
- up: function(inEvent) { |
- inEvent.bubbles = true; |
- this.fireEvent('pointerup', inEvent); |
- }, |
- enter: function(inEvent) { |
- inEvent.bubbles = false; |
- this.fireEvent('pointerenter', inEvent); |
- }, |
- leave: function(inEvent) { |
- inEvent.bubbles = false; |
- this.fireEvent('pointerleave', inEvent); |
- }, |
- over: function(inEvent) { |
- inEvent.bubbles = true; |
- this.fireEvent('pointerover', inEvent); |
- }, |
- out: function(inEvent) { |
- inEvent.bubbles = true; |
- this.fireEvent('pointerout', inEvent); |
- }, |
- cancel: function(inEvent) { |
- inEvent.bubbles = true; |
- this.fireEvent('pointercancel', inEvent); |
- }, |
- leaveOut: function(event) { |
- this.out(event); |
- if (!this.contains(event.target, event.relatedTarget)) { |
- this.leave(event); |
- } |
- }, |
- enterOver: function(event) { |
- this.over(event); |
- if (!this.contains(event.target, event.relatedTarget)) { |
- this.enter(event); |
- } |
- }, |
- // LISTENER LOGIC |
- eventHandler: function(inEvent) { |
- // This is used to prevent multiple dispatch of pointerevents from |
- // platform events. This can happen when two elements in different scopes |
- // are set up to create pointer events, which is relevant to Shadow DOM. |
- if (inEvent._handledByPE) { |
- return; |
- } |
- var type = inEvent.type; |
- var fn = this.eventMap && this.eventMap[type]; |
- if (fn) { |
- fn(inEvent); |
- } |
- inEvent._handledByPE = true; |
- }, |
- // set up event listeners |
- listen: function(target, events) { |
- events.forEach(function(e) { |
- this.addEvent(target, e); |
- }, this); |
- }, |
- // remove event listeners |
- unlisten: function(target, events) { |
- events.forEach(function(e) { |
- this.removeEvent(target, e); |
- }, this); |
- }, |
- addEvent: scope.external.addEvent || function(target, eventName) { |
- target.addEventListener(eventName, this.boundHandler); |
- }, |
- removeEvent: scope.external.removeEvent || function(target, eventName) { |
- target.removeEventListener(eventName, this.boundHandler); |
- }, |
- // EVENT CREATION AND TRACKING |
- /** |
- * Creates a new Event of type `inType`, based on the information in |
- * `inEvent`. |
- * |
- * @param {string} inType A string representing the type of event to create |
- * @param {Event} inEvent A platform event with a target |
- * @return {Event} A PointerEvent of type `inType` |
- */ |
- makeEvent: function(inType, inEvent) { |
- // relatedTarget must be null if pointer is captured |
- if (this.captureInfo[inEvent.pointerId]) { |
- inEvent.relatedTarget = null; |
- } |
- var e = new PointerEvent(inType, inEvent); |
- if (inEvent.preventDefault) { |
- e.preventDefault = inEvent.preventDefault; |
- } |
- e._target = e._target || inEvent.target; |
- return e; |
- }, |
- // make and dispatch an event in one call |
- fireEvent: function(inType, inEvent) { |
- var e = this.makeEvent(inType, inEvent); |
- return this.dispatchEvent(e); |
- }, |
- /** |
- * Returns a snapshot of inEvent, with writable properties. |
- * |
- * @param {Event} inEvent An event that contains properties to copy. |
- * @return {Object} An object containing shallow copies of `inEvent`'s |
- * properties. |
- */ |
- cloneEvent: function(inEvent) { |
- var eventCopy = Object.create(null), p; |
- for (var i = 0; i < CLONE_PROPS.length; i++) { |
- p = CLONE_PROPS[i]; |
- eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; |
- // Work around SVGInstanceElement shadow tree |
- // Return the <use> element that is represented by the instance for Safari, Chrome, IE. |
- // This is the behavior implemented by Firefox. |
- if (HAS_SVG_INSTANCE && (p === 'target' || p === 'relatedTarget')) { |
- if (eventCopy[p] instanceof SVGElementInstance) { |
- eventCopy[p] = eventCopy[p].correspondingUseElement; |
- } |
- } |
- } |
- // keep the semantics of preventDefault |
- if (inEvent.preventDefault) { |
- eventCopy.preventDefault = function() { |
- inEvent.preventDefault(); |
- }; |
- } |
- return eventCopy; |
- }, |
- getTarget: function(inEvent) { |
- // if pointer capture is set, route all events for the specified pointerId |
- // to the capture target |
- return this.captureInfo[inEvent.pointerId] || inEvent._target; |
- }, |
- setCapture: function(inPointerId, inTarget) { |
- if (this.captureInfo[inPointerId]) { |
- this.releaseCapture(inPointerId); |
- } |
- this.captureInfo[inPointerId] = inTarget; |
- var e = document.createEvent('Event'); |
- e.initEvent('gotpointercapture', true, false); |
- e.pointerId = inPointerId; |
- this.implicitRelease = this.releaseCapture.bind(this, inPointerId); |
- document.addEventListener('pointerup', this.implicitRelease); |
- document.addEventListener('pointercancel', this.implicitRelease); |
- e._target = inTarget; |
- this.asyncDispatchEvent(e); |
- }, |
- releaseCapture: function(inPointerId) { |
- var t = this.captureInfo[inPointerId]; |
- if (t) { |
- var e = document.createEvent('Event'); |
- e.initEvent('lostpointercapture', true, false); |
- e.pointerId = inPointerId; |
- this.captureInfo[inPointerId] = undefined; |
- document.removeEventListener('pointerup', this.implicitRelease); |
- document.removeEventListener('pointercancel', this.implicitRelease); |
- e._target = t; |
- this.asyncDispatchEvent(e); |
- } |
- }, |
- /** |
- * Dispatches the event to its target. |
- * |
- * @param {Event} inEvent The event to be dispatched. |
- * @return {Boolean} True if an event handler returns true, false otherwise. |
- */ |
- dispatchEvent: scope.external.dispatchEvent || function(inEvent) { |
- var t = this.getTarget(inEvent); |
- if (t) { |
- return t.dispatchEvent(inEvent); |
- } |
- }, |
- asyncDispatchEvent: function(inEvent) { |
- requestAnimationFrame(this.dispatchEvent.bind(this, inEvent)); |
- } |
- }; |
- dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); |
- scope.dispatcher = dispatcher; |
- scope.register = dispatcher.register.bind(dispatcher); |
- scope.unregister = dispatcher.unregister.bind(dispatcher); |
-})(window.PointerEventsPolyfill); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * This module uses Mutation Observers to dynamically adjust which nodes will |
- * generate Pointer Events. |
- * |
- * All nodes that wish to generate Pointer Events must have the attribute |
- * `touch-action` set to `none`. |
- */ |
-(function(scope) { |
- var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
- var map = Array.prototype.map.call.bind(Array.prototype.map); |
- var toArray = Array.prototype.slice.call.bind(Array.prototype.slice); |
- var filter = Array.prototype.filter.call.bind(Array.prototype.filter); |
- var MO = window.MutationObserver || window.WebKitMutationObserver; |
- var SELECTOR = '[touch-action]'; |
- var OBSERVER_INIT = { |
- subtree: true, |
- childList: true, |
- attributes: true, |
- attributeOldValue: true, |
- attributeFilter: ['touch-action'] |
- }; |
- |
- function Installer(add, remove, changed, binder) { |
- this.addCallback = add.bind(binder); |
- this.removeCallback = remove.bind(binder); |
- this.changedCallback = changed.bind(binder); |
- if (MO) { |
- this.observer = new MO(this.mutationWatcher.bind(this)); |
- } |
- } |
- |
- Installer.prototype = { |
- watchSubtree: function(target) { |
- // Only watch scopes that can target find, as these are top-level. |
- // Otherwise we can see duplicate additions and removals that add noise. |
- // |
- // TODO(dfreedman): For some instances with ShadowDOMPolyfill, we can see |
- // a removal without an insertion when a node is redistributed among |
- // shadows. Since it all ends up correct in the document, watching only |
- // the document will yield the correct mutations to watch. |
- if (scope.targetFinding.canTarget(target)) { |
- this.observer.observe(target, OBSERVER_INIT); |
- } |
- }, |
- enableOnSubtree: function(target) { |
- this.watchSubtree(target); |
- if (target === document && document.readyState !== 'complete') { |
- this.installOnLoad(); |
- } else { |
- this.installNewSubtree(target); |
- } |
- }, |
- installNewSubtree: function(target) { |
- forEach(this.findElements(target), this.addElement, this); |
- }, |
- findElements: function(target) { |
- if (target.querySelectorAll) { |
- return target.querySelectorAll(SELECTOR); |
- } |
- return []; |
- }, |
- removeElement: function(el) { |
- this.removeCallback(el); |
- }, |
- addElement: function(el) { |
- this.addCallback(el); |
- }, |
- elementChanged: function(el, oldValue) { |
- this.changedCallback(el, oldValue); |
- }, |
- concatLists: function(accum, list) { |
- return accum.concat(toArray(list)); |
- }, |
- // register all touch-action = none nodes on document load |
- installOnLoad: function() { |
- document.addEventListener('readystatechange', function() { |
- if (document.readyState === 'complete') { |
- this.installNewSubtree(document); |
- } |
- }.bind(this)); |
- }, |
- isElement: function(n) { |
- return n.nodeType === Node.ELEMENT_NODE; |
- }, |
- flattenMutationTree: function(inNodes) { |
- // find children with touch-action |
- var tree = map(inNodes, this.findElements, this); |
- // make sure the added nodes are accounted for |
- tree.push(filter(inNodes, this.isElement)); |
- // flatten the list |
- return tree.reduce(this.concatLists, []); |
- }, |
- mutationWatcher: function(mutations) { |
- mutations.forEach(this.mutationHandler, this); |
- }, |
- mutationHandler: function(m) { |
- if (m.type === 'childList') { |
- var added = this.flattenMutationTree(m.addedNodes); |
- added.forEach(this.addElement, this); |
- var removed = this.flattenMutationTree(m.removedNodes); |
- removed.forEach(this.removeElement, this); |
- } else if (m.type === 'attributes') { |
- this.elementChanged(m.target, m.oldValue); |
- } |
- } |
- }; |
- |
- if (!MO) { |
- Installer.prototype.watchSubtree = function(){ |
- console.warn('PointerEventsPolyfill: MutationObservers not found, touch-action will not be dynamically detected'); |
- }; |
- } |
- |
- scope.Installer = Installer; |
-})(window.PointerEventsPolyfill); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-(function (scope) { |
- var dispatcher = scope.dispatcher; |
- var pointermap = dispatcher.pointermap; |
- // radius around touchend that swallows mouse events |
- var DEDUP_DIST = 25; |
- |
- var WHICH_TO_BUTTONS = [0, 1, 4, 2]; |
- |
- var HAS_BUTTONS = false; |
- try { |
- HAS_BUTTONS = new MouseEvent('test', {buttons: 1}).buttons === 1; |
- } catch (e) {} |
- |
- // handler block for native mouse events |
- var mouseEvents = { |
- POINTER_ID: 1, |
- POINTER_TYPE: 'mouse', |
- events: [ |
- 'mousedown', |
- 'mousemove', |
- 'mouseup', |
- 'mouseover', |
- 'mouseout' |
- ], |
- register: function(target) { |
- dispatcher.listen(target, this.events); |
- }, |
- unregister: function(target) { |
- dispatcher.unlisten(target, this.events); |
- }, |
- lastTouches: [], |
- // collide with the global mouse listener |
- isEventSimulatedFromTouch: function(inEvent) { |
- var lts = this.lastTouches; |
- var x = inEvent.clientX, y = inEvent.clientY; |
- for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { |
- // simulated mouse events will be swallowed near a primary touchend |
- var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); |
- if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { |
- return true; |
- } |
- } |
- }, |
- prepareEvent: function(inEvent) { |
- var e = dispatcher.cloneEvent(inEvent); |
- // forward mouse preventDefault |
- var pd = e.preventDefault; |
- e.preventDefault = function() { |
- inEvent.preventDefault(); |
- pd(); |
- }; |
- e.pointerId = this.POINTER_ID; |
- e.isPrimary = true; |
- e.pointerType = this.POINTER_TYPE; |
- if (!HAS_BUTTONS) { |
- e.buttons = WHICH_TO_BUTTONS[e.which] || 0; |
- } |
- return e; |
- }, |
- mousedown: function(inEvent) { |
- if (!this.isEventSimulatedFromTouch(inEvent)) { |
- var p = pointermap.has(this.POINTER_ID); |
- // TODO(dfreedman) workaround for some elements not sending mouseup |
- // http://crbug/149091 |
- if (p) { |
- this.cancel(inEvent); |
- } |
- var e = this.prepareEvent(inEvent); |
- pointermap.set(this.POINTER_ID, inEvent); |
- dispatcher.down(e); |
- } |
- }, |
- mousemove: function(inEvent) { |
- if (!this.isEventSimulatedFromTouch(inEvent)) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.move(e); |
- } |
- }, |
- mouseup: function(inEvent) { |
- if (!this.isEventSimulatedFromTouch(inEvent)) { |
- var p = pointermap.get(this.POINTER_ID); |
- if (p && p.button === inEvent.button) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.up(e); |
- this.cleanupMouse(); |
- } |
- } |
- }, |
- mouseover: function(inEvent) { |
- if (!this.isEventSimulatedFromTouch(inEvent)) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.enterOver(e); |
- } |
- }, |
- mouseout: function(inEvent) { |
- if (!this.isEventSimulatedFromTouch(inEvent)) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.leaveOut(e); |
- } |
- }, |
- cancel: function(inEvent) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.cancel(e); |
- this.cleanupMouse(); |
- }, |
- cleanupMouse: function() { |
- pointermap['delete'](this.POINTER_ID); |
- } |
- }; |
- |
- scope.mouseEvents = mouseEvents; |
-})(window.PointerEventsPolyfill); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-(function(scope) { |
- var dispatcher = scope.dispatcher; |
- var captureInfo = dispatcher.captureInfo; |
- var findTarget = scope.findTarget; |
- var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); |
- var pointermap = dispatcher.pointermap; |
- var touchMap = Array.prototype.map.call.bind(Array.prototype.map); |
- // This should be long enough to ignore compat mouse events made by touch |
- var DEDUP_TIMEOUT = 2500; |
- var CLICK_COUNT_TIMEOUT = 200; |
- var ATTRIB = 'touch-action'; |
- var INSTALLER; |
- // The presence of touch event handlers blocks scrolling, and so we must be careful to |
- // avoid adding handlers unnecessarily. Chrome plans to add a touch-action-delay property |
- // (crbug.com/329559) to address this, and once we have that we can opt-in to a simpler |
- // handler registration mechanism. Rather than try to predict how exactly to opt-in to |
- // that we'll just leave this disabled until there is a build of Chrome to test. |
- var HAS_TOUCH_ACTION_DELAY = false; |
- |
- // handler block for native touch events |
- var touchEvents = { |
- events: [ |
- 'touchstart', |
- 'touchmove', |
- 'touchend', |
- 'touchcancel' |
- ], |
- register: function(target) { |
- if (HAS_TOUCH_ACTION_DELAY) { |
- dispatcher.listen(target, this.events); |
- } else { |
- INSTALLER.enableOnSubtree(target); |
- } |
- }, |
- unregister: function(target) { |
- if (HAS_TOUCH_ACTION_DELAY) { |
- dispatcher.unlisten(target, this.events); |
- } else { |
- // TODO(dfreedman): is it worth it to disconnect the MO? |
- } |
- }, |
- elementAdded: function(el) { |
- var a = el.getAttribute(ATTRIB); |
- var st = this.touchActionToScrollType(a); |
- if (st) { |
- el._scrollType = st; |
- dispatcher.listen(el, this.events); |
- // set touch-action on shadows as well |
- allShadows(el).forEach(function(s) { |
- s._scrollType = st; |
- dispatcher.listen(s, this.events); |
- }, this); |
- } |
- }, |
- elementRemoved: function(el) { |
- el._scrollType = undefined; |
- dispatcher.unlisten(el, this.events); |
- // remove touch-action from shadow |
- allShadows(el).forEach(function(s) { |
- s._scrollType = undefined; |
- dispatcher.unlisten(s, this.events); |
- }, this); |
- }, |
- elementChanged: function(el, oldValue) { |
- var a = el.getAttribute(ATTRIB); |
- var st = this.touchActionToScrollType(a); |
- var oldSt = this.touchActionToScrollType(oldValue); |
- // simply update scrollType if listeners are already established |
- if (st && oldSt) { |
- el._scrollType = st; |
- allShadows(el).forEach(function(s) { |
- s._scrollType = st; |
- }, this); |
- } else if (oldSt) { |
- this.elementRemoved(el); |
- } else if (st) { |
- this.elementAdded(el); |
- } |
- }, |
- scrollTypes: { |
- EMITTER: 'none', |
- XSCROLLER: 'pan-x', |
- YSCROLLER: 'pan-y', |
- SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/ |
- }, |
- touchActionToScrollType: function(touchAction) { |
- var t = touchAction; |
- var st = this.scrollTypes; |
- if (t === 'none') { |
- return 'none'; |
- } else if (t === st.XSCROLLER) { |
- return 'X'; |
- } else if (t === st.YSCROLLER) { |
- return 'Y'; |
- } else if (st.SCROLLER.exec(t)) { |
- return 'XY'; |
- } |
- }, |
- POINTER_TYPE: 'touch', |
- firstTouch: null, |
- isPrimaryTouch: function(inTouch) { |
- return this.firstTouch === inTouch.identifier; |
- }, |
- setPrimaryTouch: function(inTouch) { |
- // set primary touch if there no pointers, or the only pointer is the mouse |
- 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.cancelResetClickCount(); |
- } |
- }, |
- removePrimaryPointer: function(inPointer) { |
- if (inPointer.isPrimary) { |
- this.firstTouch = null; |
- this.firstXY = null; |
- this.resetClickCount(); |
- } |
- }, |
- clickCount: 0, |
- resetId: null, |
- resetClickCount: function() { |
- var fn = function() { |
- this.clickCount = 0; |
- this.resetId = null; |
- }.bind(this); |
- this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); |
- }, |
- cancelResetClickCount: function() { |
- if (this.resetId) { |
- clearTimeout(this.resetId); |
- } |
- }, |
- typeToButtons: function(type) { |
- var ret = 0; |
- if (type === 'touchstart' || type === 'touchmove') { |
- ret = 1; |
- } |
- return ret; |
- }, |
- touchToPointer: function(inTouch) { |
- var cte = this.currentTouchEvent; |
- var e = dispatcher.cloneEvent(inTouch); |
- // Spec specifies that pointerId 1 is reserved for Mouse. |
- // Touch identifiers can start at 0. |
- // Add 2 to the touch identifier for compatibility. |
- var id = e.pointerId = inTouch.identifier + 2; |
- e.target = captureInfo[id] || findTarget(e); |
- e.bubbles = true; |
- e.cancelable = true; |
- e.detail = this.clickCount; |
- e.button = 0; |
- e.buttons = this.typeToButtons(cte.type); |
- e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; |
- e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; |
- e.pressure = inTouch.webkitForce || inTouch.force || 0.5; |
- e.isPrimary = this.isPrimaryTouch(inTouch); |
- e.pointerType = this.POINTER_TYPE; |
- // forward touch preventDefaults |
- var self = this; |
- e.preventDefault = function() { |
- self.scrolling = false; |
- self.firstXY = null; |
- cte.preventDefault(); |
- }; |
- return e; |
- }, |
- processTouches: function(inEvent, inFunction) { |
- var tl = inEvent.changedTouches; |
- this.currentTouchEvent = inEvent; |
- for (var i = 0, t; i < tl.length; i++) { |
- t = tl[i]; |
- inFunction.call(this, this.touchToPointer(t)); |
- } |
- }, |
- // For single axis scrollers, determines whether the element should emit |
- // pointer events or behave as a scroller |
- shouldScroll: function(inEvent) { |
- if (this.firstXY) { |
- var ret; |
- var scrollAxis = inEvent.currentTarget._scrollType; |
- if (scrollAxis === 'none') { |
- // this element is a touch-action: none, should never scroll |
- ret = false; |
- } else if (scrollAxis === 'XY') { |
- // this element should always scroll |
- ret = true; |
- } else { |
- var t = inEvent.changedTouches[0]; |
- // check the intended scroll axis, and other axis |
- var a = scrollAxis; |
- var oa = scrollAxis === 'Y' ? 'X' : 'Y'; |
- var da = Math.abs(t['client' + a] - this.firstXY[a]); |
- var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); |
- // if delta in the scroll axis > delta other axis, scroll instead of |
- // making events |
- ret = da >= doa; |
- } |
- this.firstXY = null; |
- return ret; |
- } |
- }, |
- findTouch: function(inTL, inId) { |
- for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { |
- if (t.identifier === inId) { |
- return true; |
- } |
- } |
- }, |
- // In some instances, a touchstart can happen without a touchend. This |
- // leaves the pointermap in a broken state. |
- // Therefore, on every touchstart, we remove the touches that did not fire a |
- // touchend event. |
- // To keep state globally consistent, we fire a |
- // pointercancel for this "abandoned" touch |
- vacuumTouches: function(inEvent) { |
- var tl = inEvent.touches; |
- // pointermap.pointers() should be < tl.length here, as the touchstart has not |
- // been processed yet. |
- if (pointermap.pointers() >= tl.length) { |
- var d = []; |
- pointermap.forEach(function(value, key) { |
- // Never remove pointerId == 1, which is mouse. |
- // 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; |
- d.push(p); |
- } |
- }, this); |
- d.forEach(this.cancelOut, this); |
- } |
- }, |
- touchstart: function(inEvent) { |
- this.vacuumTouches(inEvent); |
- this.setPrimaryTouch(inEvent.changedTouches[0]); |
- this.dedupSynthMouse(inEvent); |
- if (!this.scrolling) { |
- this.clickCount++; |
- this.processTouches(inEvent, this.overDown); |
- } |
- }, |
- overDown: function(inPointer) { |
- var p = pointermap.set(inPointer.pointerId, { |
- target: inPointer.target, |
- out: inPointer, |
- outTarget: inPointer.target |
- }); |
- dispatcher.over(inPointer); |
- dispatcher.enter(inPointer); |
- dispatcher.down(inPointer); |
- }, |
- touchmove: function(inEvent) { |
- if (!this.scrolling) { |
- if (this.shouldScroll(inEvent)) { |
- this.scrolling = true; |
- this.touchcancel(inEvent); |
- } else { |
- inEvent.preventDefault(); |
- this.processTouches(inEvent, this.moveOverOut); |
- } |
- } |
- }, |
- moveOverOut: function(inPointer) { |
- var event = inPointer; |
- var pointer = pointermap.get(event.pointerId); |
- // a finger drifted off the screen, ignore it |
- if (!pointer) { |
- return; |
- } |
- var outEvent = pointer.out; |
- var outTarget = pointer.outTarget; |
- dispatcher.move(event); |
- if (outEvent && outTarget !== event.target) { |
- outEvent.relatedTarget = event.target; |
- event.relatedTarget = outTarget; |
- // recover from retargeting by shadow |
- outEvent.target = outTarget; |
- if (event.target) { |
- dispatcher.leaveOut(outEvent); |
- dispatcher.enterOver(event); |
- } else { |
- // clean up case when finger leaves the screen |
- event.target = outTarget; |
- event.relatedTarget = null; |
- this.cancelOut(event); |
- } |
- } |
- pointer.out = event; |
- pointer.outTarget = event.target; |
- }, |
- touchend: function(inEvent) { |
- this.dedupSynthMouse(inEvent); |
- this.processTouches(inEvent, this.upOut); |
- }, |
- upOut: function(inPointer) { |
- if (!this.scrolling) { |
- dispatcher.up(inPointer); |
- dispatcher.out(inPointer); |
- dispatcher.leave(inPointer); |
- } |
- this.cleanUpPointer(inPointer); |
- }, |
- touchcancel: function(inEvent) { |
- this.processTouches(inEvent, this.cancelOut); |
- }, |
- cancelOut: function(inPointer) { |
- dispatcher.cancel(inPointer); |
- dispatcher.out(inPointer); |
- dispatcher.leave(inPointer); |
- this.cleanUpPointer(inPointer); |
- }, |
- cleanUpPointer: function(inPointer) { |
- pointermap['delete'](inPointer.pointerId); |
- this.removePrimaryPointer(inPointer); |
- }, |
- // prevent synth mouse events from creating pointer events |
- dedupSynthMouse: function(inEvent) { |
- var lts = scope.mouseEvents.lastTouches; |
- var t = inEvent.changedTouches[0]; |
- // only the primary finger will synth mouse events |
- if (this.isPrimaryTouch(t)) { |
- // remember x/y of last touch |
- var lt = {x: t.clientX, y: t.clientY}; |
- lts.push(lt); |
- var fn = (function(lts, lt){ |
- var i = lts.indexOf(lt); |
- if (i > -1) { |
- lts.splice(i, 1); |
- } |
- }).bind(null, lts, lt); |
- setTimeout(fn, DEDUP_TIMEOUT); |
- } |
- } |
- }; |
- |
- if (!HAS_TOUCH_ACTION_DELAY) { |
- INSTALLER = new scope.Installer(touchEvents.elementAdded, touchEvents.elementRemoved, touchEvents.elementChanged, touchEvents); |
- } |
- |
- scope.touchEvents = touchEvents; |
-})(window.PointerEventsPolyfill); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-(function(scope) { |
- var dispatcher = scope.dispatcher; |
- var pointermap = dispatcher.pointermap; |
- var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number'; |
- var msEvents = { |
- events: [ |
- 'MSPointerDown', |
- 'MSPointerMove', |
- 'MSPointerUp', |
- 'MSPointerOut', |
- 'MSPointerOver', |
- 'MSPointerCancel', |
- 'MSGotPointerCapture', |
- 'MSLostPointerCapture' |
- ], |
- register: function(target) { |
- dispatcher.listen(target, this.events); |
- }, |
- unregister: function(target) { |
- dispatcher.unlisten(target, this.events); |
- }, |
- POINTER_TYPES: [ |
- '', |
- 'unavailable', |
- 'touch', |
- 'pen', |
- 'mouse' |
- ], |
- prepareEvent: function(inEvent) { |
- var e = inEvent; |
- if (HAS_BITMAP_TYPE) { |
- e = dispatcher.cloneEvent(inEvent); |
- e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; |
- } |
- return e; |
- }, |
- cleanup: function(id) { |
- pointermap['delete'](id); |
- }, |
- MSPointerDown: function(inEvent) { |
- pointermap.set(inEvent.pointerId, inEvent); |
- var e = this.prepareEvent(inEvent); |
- dispatcher.down(e); |
- }, |
- MSPointerMove: function(inEvent) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.move(e); |
- }, |
- MSPointerUp: function(inEvent) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.up(e); |
- this.cleanup(inEvent.pointerId); |
- }, |
- MSPointerOut: function(inEvent) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.leaveOut(e); |
- }, |
- MSPointerOver: function(inEvent) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.enterOver(e); |
- }, |
- MSPointerCancel: function(inEvent) { |
- var e = this.prepareEvent(inEvent); |
- dispatcher.cancel(e); |
- this.cleanup(inEvent.pointerId); |
- }, |
- MSLostPointerCapture: function(inEvent) { |
- var e = dispatcher.makeEvent('lostpointercapture', inEvent); |
- dispatcher.dispatchEvent(e); |
- }, |
- MSGotPointerCapture: function(inEvent) { |
- var e = dispatcher.makeEvent('gotpointercapture', inEvent); |
- dispatcher.dispatchEvent(e); |
- } |
- }; |
- |
- scope.msEvents = msEvents; |
-})(window.PointerEventsPolyfill); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * This module contains the handlers for native platform events. |
- * From here, the dispatcher is called to create unified pointer events. |
- * Included are touch events (v1), mouse events, and MSPointerEvents. |
- */ |
-(function(scope) { |
- var dispatcher = scope.dispatcher; |
- |
- // only activate if this platform does not have pointer events |
- if (window.PointerEvent !== scope.PointerEvent) { |
- |
- if (window.navigator.msPointerEnabled) { |
- var tp = window.navigator.msMaxTouchPoints; |
- Object.defineProperty(window.navigator, 'maxTouchPoints', { |
- value: tp, |
- enumerable: true |
- }); |
- dispatcher.registerSource('ms', scope.msEvents); |
- } else { |
- dispatcher.registerSource('mouse', scope.mouseEvents); |
- if (window.ontouchstart !== undefined) { |
- dispatcher.registerSource('touch', scope.touchEvents); |
- } |
- } |
- |
- dispatcher.register(document); |
- } |
-})(window.PointerEventsPolyfill); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-(function(scope) { |
- var dispatcher = scope.dispatcher; |
- var n = window.navigator; |
- var s, r; |
- function assertDown(id) { |
- if (!dispatcher.pointermap.has(id)) { |
- throw new Error('InvalidPointerId'); |
- } |
- } |
- if (n.msPointerEnabled) { |
- s = function(pointerId) { |
- assertDown(pointerId); |
- this.msSetPointerCapture(pointerId); |
- }; |
- r = function(pointerId) { |
- assertDown(pointerId); |
- this.msReleasePointerCapture(pointerId); |
- }; |
- } else { |
- s = function setPointerCapture(pointerId) { |
- assertDown(pointerId); |
- dispatcher.setCapture(pointerId, this); |
- }; |
- r = function releasePointerCapture(pointerId) { |
- assertDown(pointerId); |
- dispatcher.releaseCapture(pointerId, this); |
- }; |
- } |
- if (window.Element && !Element.prototype.setPointerCapture) { |
- Object.defineProperties(Element.prototype, { |
- 'setPointerCapture': { |
- value: s |
- }, |
- 'releasePointerCapture': { |
- value: r |
- } |
- }); |
- } |
-})(window.PointerEventsPolyfill); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * PointerGestureEvent is the constructor for all PointerGesture events. |
- * |
- * @module PointerGestures |
- * @class PointerGestureEvent |
- * @extends UIEvent |
- * @constructor |
- * @param {String} inType Event type |
- * @param {Object} [inDict] Dictionary of properties to initialize on the event |
- */ |
- |
-function PointerGestureEvent(inType, inDict) { |
- var dict = inDict || {}; |
- var e = document.createEvent('Event'); |
- var props = { |
- bubbles: Boolean(dict.bubbles) === dict.bubbles || true, |
- cancelable: Boolean(dict.cancelable) === dict.cancelable || true |
- }; |
- |
- e.initEvent(inType, props.bubbles, props.cancelable); |
- |
- var keys = Object.keys(dict), k; |
- for (var i = 0; i < keys.length; i++) { |
- k = keys[i]; |
- e[k] = dict[k]; |
- } |
- |
- e.preventTap = this.preventTap; |
- |
- return e; |
-} |
- |
-/** |
- * Allows for any gesture to prevent the tap gesture. |
- * |
- * @method preventTap |
- */ |
-PointerGestureEvent.prototype.preventTap = function() { |
- this.tapPrevented = true; |
-}; |
- |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-(function(scope) { |
- /** |
- * This class contains the gesture recognizers that create the PointerGesture |
- * events. |
- * |
- * @class PointerGestures |
- * @static |
- */ |
- scope = scope || {}; |
- scope.utils = { |
- LCA: { |
- // Determines the lowest node in the ancestor chain of a and b |
- find: function(a, b) { |
- if (a === b) { |
- return a; |
- } |
- // fast case, a is a direct descendant of b or vice versa |
- if (a.contains) { |
- if (a.contains(b)) { |
- return a; |
- } |
- if (b.contains(a)) { |
- return b; |
- } |
- } |
- var adepth = this.depth(a); |
- var bdepth = this.depth(b); |
- var d = adepth - bdepth; |
- 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); |
- } |
- return a; |
- }, |
- walk: function(n, u) { |
- for (var i = 0; i < u; i++) { |
- n = n.parentNode; |
- } |
- return n; |
- }, |
- depth: function(n) { |
- var d = 0; |
- while(n) { |
- d++; |
- n = n.parentNode; |
- } |
- return d; |
- } |
- } |
- }; |
- scope.findLCA = function(a, b) { |
- return scope.utils.LCA.find(a, b); |
- } |
- window.PointerGestures = scope; |
-})(window.PointerGestures); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * This module implements an map of pointer states |
- */ |
-(function(scope) { |
- var USE_MAP = window.Map && window.Map.prototype.forEach; |
- var POINTERS_FN = function(){ return this.size; }; |
- function PointerMap() { |
- if (USE_MAP) { |
- var m = new Map(); |
- m.pointers = POINTERS_FN; |
- return m; |
- } else { |
- this.keys = []; |
- this.values = []; |
- } |
- } |
- |
- PointerMap.prototype = { |
- set: function(inId, inEvent) { |
- var i = this.keys.indexOf(inId); |
- if (i > -1) { |
- this.values[i] = inEvent; |
- } else { |
- this.keys.push(inId); |
- this.values.push(inEvent); |
- } |
- }, |
- has: function(inId) { |
- return this.keys.indexOf(inId) > -1; |
- }, |
- 'delete': function(inId) { |
- var i = this.keys.indexOf(inId); |
- if (i > -1) { |
- this.keys.splice(i, 1); |
- this.values.splice(i, 1); |
- } |
- }, |
- get: function(inId) { |
- var i = this.keys.indexOf(inId); |
- return this.values[i]; |
- }, |
- clear: function() { |
- this.keys.length = 0; |
- this.values.length = 0; |
- }, |
- // return value, key, map |
- forEach: function(callback, thisArg) { |
- this.values.forEach(function(v, i) { |
- callback.call(thisArg, v, this.keys[i], this); |
- }, this); |
- }, |
- pointers: function() { |
- return this.keys.length; |
- } |
- }; |
- |
- scope.PointerMap = PointerMap; |
-})(window.PointerGestures); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-(function(scope) { |
- var CLONE_PROPS = [ |
- // MouseEvent |
- 'bubbles', |
- 'cancelable', |
- 'view', |
- 'detail', |
- 'screenX', |
- 'screenY', |
- 'clientX', |
- 'clientY', |
- 'ctrlKey', |
- 'altKey', |
- 'shiftKey', |
- 'metaKey', |
- 'button', |
- 'relatedTarget', |
- // DOM Level 3 |
- 'buttons', |
- // PointerEvent |
- 'pointerId', |
- 'width', |
- 'height', |
- 'pressure', |
- 'tiltX', |
- 'tiltY', |
- 'pointerType', |
- 'hwTimestamp', |
- 'isPrimary', |
- // event instance |
- 'type', |
- 'target', |
- 'currentTarget', |
- 'screenX', |
- 'screenY', |
- 'pageX', |
- 'pageY', |
- 'tapPrevented' |
- ]; |
- |
- var CLONE_DEFAULTS = [ |
- // MouseEvent |
- false, |
- false, |
- null, |
- null, |
- 0, |
- 0, |
- 0, |
- 0, |
- false, |
- false, |
- false, |
- false, |
- 0, |
- null, |
- // DOM Level 3 |
- 0, |
- // PointerEvent |
- 0, |
- 0, |
- 0, |
- 0, |
- 0, |
- 0, |
- '', |
- 0, |
- false, |
- // event instance |
- '', |
- null, |
- null, |
- 0, |
- 0, |
- 0, |
- 0 |
- ]; |
- |
- var dispatcher = { |
- handledEvents: new WeakMap(), |
- targets: new WeakMap(), |
- handlers: {}, |
- recognizers: {}, |
- events: {}, |
- // Add a new gesture recognizer to the event listeners. |
- // Recognizer needs an `events` property. |
- registerRecognizer: function(inName, inRecognizer) { |
- var r = inRecognizer; |
- this.recognizers[inName] = r; |
- r.events.forEach(function(e) { |
- if (r[e]) { |
- this.events[e] = true; |
- var f = r[e].bind(r); |
- this.addHandler(e, f); |
- } |
- }, this); |
- }, |
- addHandler: function(inEvent, inFn) { |
- var e = inEvent; |
- if (!this.handlers[e]) { |
- this.handlers[e] = []; |
- } |
- this.handlers[e].push(inFn); |
- }, |
- // add event listeners for inTarget |
- registerTarget: function(inTarget) { |
- this.listen(Object.keys(this.events), inTarget); |
- }, |
- // remove event listeners for inTarget |
- unregisterTarget: function(inTarget) { |
- this.unlisten(Object.keys(this.events), inTarget); |
- }, |
- // LISTENER LOGIC |
- eventHandler: function(inEvent) { |
- if (this.handledEvents.get(inEvent)) { |
- return; |
- } |
- var type = inEvent.type, fns = this.handlers[type]; |
- if (fns) { |
- this.makeQueue(fns, inEvent); |
- } |
- this.handledEvents.set(inEvent, true); |
- }, |
- // queue event for async dispatch |
- makeQueue: function(inHandlerFns, inEvent) { |
- // must clone events to keep the (possibly shadowed) target correct for |
- // async dispatching |
- var e = this.cloneEvent(inEvent); |
- requestAnimationFrame(this.runQueue.bind(this, inHandlerFns, e)); |
- }, |
- // Dispatch the queued events |
- runQueue: function(inHandlers, inEvent) { |
- this.currentPointerId = inEvent.pointerId; |
- for (var i = 0, f, l = inHandlers.length; (i < l) && (f = inHandlers[i]); i++) { |
- f(inEvent); |
- } |
- this.currentPointerId = 0; |
- }, |
- // set up event listeners |
- listen: function(inEvents, inTarget) { |
- inEvents.forEach(function(e) { |
- this.addEvent(e, this.boundHandler, false, inTarget); |
- }, this); |
- }, |
- // remove event listeners |
- unlisten: function(inEvents) { |
- inEvents.forEach(function(e) { |
- this.removeEvent(e, this.boundHandler, false, inTarget); |
- }, this); |
- }, |
- addEvent: function(inEventName, inEventHandler, inCapture, inTarget) { |
- inTarget.addEventListener(inEventName, inEventHandler, inCapture); |
- }, |
- removeEvent: function(inEventName, inEventHandler, inCapture, inTarget) { |
- inTarget.removeEventListener(inEventName, inEventHandler, inCapture); |
- }, |
- // EVENT CREATION AND TRACKING |
- // Creates a new Event of type `inType`, based on the information in |
- // `inEvent`. |
- makeEvent: function(inType, inDict) { |
- return new PointerGestureEvent(inType, inDict); |
- }, |
- /* |
- * Returns a snapshot of inEvent, with writable properties. |
- * |
- * @method cloneEvent |
- * @param {Event} inEvent An event that contains properties to copy. |
- * @return {Object} An object containing shallow copies of `inEvent`'s |
- * properties. |
- */ |
- cloneEvent: function(inEvent) { |
- var eventCopy = {}, p; |
- for (var i = 0; i < CLONE_PROPS.length; i++) { |
- p = CLONE_PROPS[i]; |
- eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; |
- } |
- return eventCopy; |
- }, |
- // Dispatches the event to its target. |
- dispatchEvent: function(inEvent, inTarget) { |
- var t = inTarget || this.targets.get(inEvent); |
- if (t) { |
- t.dispatchEvent(inEvent); |
- if (inEvent.tapPrevented) { |
- this.preventTap(this.currentPointerId); |
- } |
- } |
- }, |
- asyncDispatchEvent: function(inEvent, inTarget) { |
- requestAnimationFrame(this.dispatchEvent.bind(this, inEvent, inTarget)); |
- }, |
- preventTap: function(inPointerId) { |
- var t = this.recognizers.tap; |
- if (t){ |
- t.preventTap(inPointerId); |
- } |
- } |
- }; |
- dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); |
- // recognizers call into the dispatcher and load later |
- // solve the chicken and egg problem by having registerScopes module run last |
- dispatcher.registerQueue = []; |
- dispatcher.immediateRegister = false; |
- scope.dispatcher = dispatcher; |
- /** |
- * Enable gesture events for a given scope, typically |
- * [ShadowRoots](https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#shadow-root-object). |
- * |
- * @for PointerGestures |
- * @method register |
- * @param {ShadowRoot} scope A top level scope to enable gesture |
- * support on. |
- */ |
- scope.register = function(inScope) { |
- if (dispatcher.immediateRegister) { |
- var pe = window.PointerEventsPolyfill; |
- if (pe) { |
- pe.register(inScope); |
- } |
- scope.dispatcher.registerTarget(inScope); |
- } else { |
- dispatcher.registerQueue.push(inScope); |
- } |
- }; |
- scope.register(document); |
-})(window.PointerGestures); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * This event is fired when a pointer is held down for 200ms. |
- * |
- * @module PointerGestures |
- * @submodule Events |
- * @class hold |
- */ |
-/** |
- * Type of pointer that made the holding event. |
- * @type String |
- * @property pointerType |
- */ |
-/** |
- * Screen X axis position of the held pointer |
- * @type Number |
- * @property clientX |
- */ |
-/** |
- * Screen Y axis position of the held pointer |
- * @type Number |
- * @property clientY |
- */ |
-/** |
- * Type of pointer that made the holding event. |
- * @type String |
- * @property pointerType |
- */ |
-/** |
- * This event is fired every 200ms while a pointer is held down. |
- * |
- * @class holdpulse |
- * @extends hold |
- */ |
-/** |
- * Milliseconds pointer has been held down. |
- * @type Number |
- * @property holdTime |
- */ |
-/** |
- * This event is fired when a held pointer is released or moved. |
- * |
- * @class released |
- */ |
- |
-(function(scope) { |
- var dispatcher = scope.dispatcher; |
- var hold = { |
- // wait at least HOLD_DELAY ms between hold and pulse events |
- HOLD_DELAY: 200, |
- // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold |
- WIGGLE_THRESHOLD: 16, |
- events: [ |
- 'pointerdown', |
- 'pointermove', |
- 'pointerup', |
- 'pointercancel' |
- ], |
- heldPointer: null, |
- holdJob: null, |
- pulse: function() { |
- var hold = Date.now() - this.heldPointer.timeStamp; |
- var type = this.held ? 'holdpulse' : 'hold'; |
- this.fireHold(type, hold); |
- this.held = true; |
- }, |
- cancel: function() { |
- clearInterval(this.holdJob); |
- if (this.held) { |
- this.fireHold('release'); |
- } |
- this.held = false; |
- this.heldPointer = null; |
- this.target = null; |
- this.holdJob = null; |
- }, |
- pointerdown: function(inEvent) { |
- if (inEvent.isPrimary && !this.heldPointer) { |
- this.heldPointer = inEvent; |
- this.target = inEvent.target; |
- this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); |
- } |
- }, |
- pointerup: function(inEvent) { |
- if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) { |
- this.cancel(); |
- } |
- }, |
- pointercancel: function(inEvent) { |
- this.cancel(); |
- }, |
- pointermove: function(inEvent) { |
- if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) { |
- var x = inEvent.clientX - this.heldPointer.clientX; |
- var y = inEvent.clientY - this.heldPointer.clientY; |
- if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { |
- this.cancel(); |
- } |
- } |
- }, |
- fireHold: function(inType, inHoldTime) { |
- var p = { |
- pointerType: this.heldPointer.pointerType, |
- clientX: this.heldPointer.clientX, |
- clientY: this.heldPointer.clientY |
- }; |
- if (inHoldTime) { |
- p.holdTime = inHoldTime; |
- } |
- var e = dispatcher.makeEvent(inType, p); |
- dispatcher.dispatchEvent(e, this.target); |
- if (e.tapPrevented) { |
- dispatcher.preventTap(this.heldPointer.pointerId); |
- } |
- } |
- }; |
- dispatcher.registerRecognizer('hold', hold); |
-})(window.PointerGestures); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * This event denotes the beginning of a series of tracking events. |
- * |
- * @module PointerGestures |
- * @submodule Events |
- * @class trackstart |
- */ |
-/** |
- * Pixels moved in the x direction since trackstart. |
- * @type Number |
- * @property dx |
- */ |
-/** |
- * Pixes moved in the y direction since trackstart. |
- * @type Number |
- * @property dy |
- */ |
-/** |
- * Pixels moved in the x direction since the last track. |
- * @type Number |
- * @property ddx |
- */ |
-/** |
- * Pixles moved in the y direction since the last track. |
- * @type Number |
- * @property ddy |
- */ |
-/** |
- * The clientX position of the track gesture. |
- * @type Number |
- * @property clientX |
- */ |
-/** |
- * The clientY position of the track gesture. |
- * @type Number |
- * @property clientY |
- */ |
-/** |
- * The pageX position of the track gesture. |
- * @type Number |
- * @property pageX |
- */ |
-/** |
- * The pageY position of the track gesture. |
- * @type Number |
- * @property pageY |
- */ |
-/** |
- * The screenX position of the track gesture. |
- * @type Number |
- * @property screenX |
- */ |
-/** |
- * The screenY position of the track gesture. |
- * @type Number |
- * @property screenY |
- */ |
-/** |
- * The last x axis direction of the pointer. |
- * @type Number |
- * @property xDirection |
- */ |
-/** |
- * The last y axis direction of the pointer. |
- * @type Number |
- * @property yDirection |
- */ |
-/** |
- * A shared object between all tracking events. |
- * @type Object |
- * @property trackInfo |
- */ |
-/** |
- * The element currently under the pointer. |
- * @type Element |
- * @property relatedTarget |
- */ |
-/** |
- * The type of pointer that make the track gesture. |
- * @type String |
- * @property pointerType |
- */ |
-/** |
- * |
- * This event fires for all pointer movement being tracked. |
- * |
- * @class track |
- * @extends trackstart |
- */ |
-/** |
- * This event fires when the pointer is no longer being tracked. |
- * |
- * @class trackend |
- * @extends trackstart |
- */ |
- |
- (function(scope) { |
- var dispatcher = scope.dispatcher; |
- var pointermap = new scope.PointerMap(); |
- var track = { |
- events: [ |
- 'pointerdown', |
- 'pointermove', |
- 'pointerup', |
- 'pointercancel' |
- ], |
- WIGGLE_THRESHOLD: 4, |
- clampDir: function(inDelta) { |
- return inDelta > 0 ? 1 : -1; |
- }, |
- calcPositionDelta: function(inA, inB) { |
- var x = 0, y = 0; |
- if (inA && inB) { |
- x = inB.pageX - inA.pageX; |
- y = inB.pageY - inA.pageY; |
- } |
- return {x: x, y: y}; |
- }, |
- fireTrack: function(inType, inEvent, inTrackingData) { |
- var t = inTrackingData; |
- var d = this.calcPositionDelta(t.downEvent, inEvent); |
- var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); |
- if (dd.x) { |
- t.xDirection = this.clampDir(dd.x); |
- } |
- if (dd.y) { |
- t.yDirection = this.clampDir(dd.y); |
- } |
- var trackData = { |
- dx: d.x, |
- dy: d.y, |
- ddx: dd.x, |
- ddy: dd.y, |
- clientX: inEvent.clientX, |
- clientY: inEvent.clientY, |
- pageX: inEvent.pageX, |
- pageY: inEvent.pageY, |
- screenX: inEvent.screenX, |
- screenY: inEvent.screenY, |
- xDirection: t.xDirection, |
- yDirection: t.yDirection, |
- trackInfo: t.trackInfo, |
- relatedTarget: inEvent.target, |
- pointerType: inEvent.pointerType |
- }; |
- var e = dispatcher.makeEvent(inType, trackData); |
- t.lastMoveEvent = inEvent; |
- dispatcher.dispatchEvent(e, t.downTarget); |
- }, |
- pointerdown: function(inEvent) { |
- if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.buttons === 1 : true)) { |
- var p = { |
- downEvent: inEvent, |
- downTarget: inEvent.target, |
- trackInfo: {}, |
- lastMoveEvent: null, |
- xDirection: 0, |
- yDirection: 0, |
- tracking: false |
- }; |
- pointermap.set(inEvent.pointerId, p); |
- } |
- }, |
- pointermove: function(inEvent) { |
- var p = pointermap.get(inEvent.pointerId); |
- if (p) { |
- if (!p.tracking) { |
- var d = this.calcPositionDelta(p.downEvent, inEvent); |
- var move = d.x * d.x + d.y * d.y; |
- // start tracking only if finger moves more than WIGGLE_THRESHOLD |
- if (move > this.WIGGLE_THRESHOLD) { |
- p.tracking = true; |
- this.fireTrack('trackstart', p.downEvent, p); |
- this.fireTrack('track', inEvent, p); |
- } |
- } else { |
- this.fireTrack('track', inEvent, p); |
- } |
- } |
- }, |
- pointerup: function(inEvent) { |
- var p = pointermap.get(inEvent.pointerId); |
- if (p) { |
- if (p.tracking) { |
- this.fireTrack('trackend', inEvent, p); |
- } |
- pointermap.delete(inEvent.pointerId); |
- } |
- }, |
- pointercancel: function(inEvent) { |
- this.pointerup(inEvent); |
- } |
- }; |
- dispatcher.registerRecognizer('track', track); |
- })(window.PointerGestures); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * This event denotes a rapid down/move/up sequence from a pointer. |
- * |
- * The event is sent to the first element the pointer went down on. |
- * |
- * @module PointerGestures |
- * @submodule Events |
- * @class flick |
- */ |
-/** |
- * Signed velocity of the flick in the x direction. |
- * @property xVelocity |
- * @type Number |
- */ |
-/** |
- * Signed velocity of the flick in the y direction. |
- * @type Number |
- * @property yVelocity |
- */ |
-/** |
- * Unsigned total velocity of the flick. |
- * @type Number |
- * @property velocity |
- */ |
-/** |
- * Angle of the flick in degrees, with 0 along the |
- * positive x axis. |
- * @type Number |
- * @property angle |
- */ |
-/** |
- * Axis with the greatest absolute velocity. Denoted |
- * with 'x' or 'y'. |
- * @type String |
- * @property majorAxis |
- */ |
-/** |
- * Type of the pointer that made the flick. |
- * @type String |
- * @property pointerType |
- */ |
- |
-(function(scope) { |
- var dispatcher = scope.dispatcher; |
- var flick = { |
- // TODO(dfreedman): value should be low enough for low speed flicks, but |
- // high enough to remove accidental flicks |
- MIN_VELOCITY: 0.5 /* px/ms */, |
- MAX_QUEUE: 4, |
- moveQueue: [], |
- target: null, |
- pointerId: null, |
- events: [ |
- 'pointerdown', |
- 'pointermove', |
- 'pointerup', |
- 'pointercancel' |
- ], |
- pointerdown: function(inEvent) { |
- if (inEvent.isPrimary && !this.pointerId) { |
- this.pointerId = inEvent.pointerId; |
- this.target = inEvent.target; |
- this.addMove(inEvent); |
- } |
- }, |
- pointermove: function(inEvent) { |
- if (inEvent.pointerId === this.pointerId) { |
- this.addMove(inEvent); |
- } |
- }, |
- pointerup: function(inEvent) { |
- if (inEvent.pointerId === this.pointerId) { |
- this.fireFlick(inEvent); |
- } |
- this.cleanup(); |
- }, |
- pointercancel: function(inEvent) { |
- this.cleanup(); |
- }, |
- cleanup: function() { |
- this.moveQueue = []; |
- this.target = null; |
- this.pointerId = null; |
- }, |
- addMove: function(inEvent) { |
- if (this.moveQueue.length >= this.MAX_QUEUE) { |
- this.moveQueue.shift(); |
- } |
- this.moveQueue.push(inEvent); |
- }, |
- fireFlick: function(inEvent) { |
- var e = inEvent; |
- var l = this.moveQueue.length; |
- var dt, dx, dy, tx, ty, tv, x = 0, y = 0, v = 0; |
- // flick based off the fastest segment of movement |
- for (var i = 0, m; i < l && (m = this.moveQueue[i]); i++) { |
- dt = e.timeStamp - m.timeStamp; |
- dx = e.clientX - m.clientX, dy = e.clientY - m.clientY; |
- tx = dx / dt, ty = dy / dt, tv = Math.sqrt(tx * tx + ty * ty); |
- if (tv > v) { |
- x = tx, y = ty, v = tv; |
- } |
- } |
- var ma = Math.abs(x) > Math.abs(y) ? 'x' : 'y'; |
- var a = this.calcAngle(x, y); |
- if (Math.abs(v) >= this.MIN_VELOCITY) { |
- var ev = dispatcher.makeEvent('flick', { |
- xVelocity: x, |
- yVelocity: y, |
- velocity: v, |
- angle: a, |
- majorAxis: ma, |
- pointerType: inEvent.pointerType |
- }); |
- dispatcher.dispatchEvent(ev, this.target); |
- } |
- }, |
- calcAngle: function(inX, inY) { |
- return (Math.atan2(inY, inX) * 180 / Math.PI); |
- } |
- }; |
- dispatcher.registerRecognizer('flick', flick); |
-})(window.PointerGestures); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/* |
- * Basic strategy: find the farthest apart points, use as diameter of circle |
- * react to size change and rotation of the chord |
- */ |
- |
-/** |
- * @module PointerGestures |
- * @submodule Events |
- * @class pinch |
- */ |
-/** |
- * Scale of the pinch zoom gesture |
- * @property scale |
- * @type Number |
- */ |
-/** |
- * Center X position of pointers causing pinch |
- * @property centerX |
- * @type Number |
- */ |
-/** |
- * Center Y position of pointers causing pinch |
- * @property centerY |
- * @type Number |
- */ |
- |
-/** |
- * @module PointerGestures |
- * @submodule Events |
- * @class rotate |
- */ |
-/** |
- * Angle (in degrees) of rotation. Measured from starting positions of pointers. |
- * @property angle |
- * @type Number |
- */ |
-/** |
- * Center X position of pointers causing rotation |
- * @property centerX |
- * @type Number |
- */ |
-/** |
- * Center Y position of pointers causing rotation |
- * @property centerY |
- * @type Number |
- */ |
-(function(scope) { |
- var dispatcher = scope.dispatcher; |
- var pointermap = new scope.PointerMap(); |
- var RAD_TO_DEG = 180 / Math.PI; |
- var pinch = { |
- events: [ |
- 'pointerdown', |
- 'pointermove', |
- 'pointerup', |
- 'pointercancel' |
- ], |
- reference: {}, |
- pointerdown: function(ev) { |
- pointermap.set(ev.pointerId, ev); |
- if (pointermap.pointers() == 2) { |
- var points = this.calcChord(); |
- var angle = this.calcAngle(points); |
- this.reference = { |
- angle: angle, |
- diameter: points.diameter, |
- target: scope.findLCA(points.a.target, points.b.target) |
- }; |
- } |
- }, |
- pointerup: function(ev) { |
- pointermap.delete(ev.pointerId); |
- }, |
- pointermove: function(ev) { |
- if (pointermap.has(ev.pointerId)) { |
- pointermap.set(ev.pointerId, ev); |
- if (pointermap.pointers() > 1) { |
- this.calcPinchRotate(); |
- } |
- } |
- }, |
- pointercancel: function(ev) { |
- this.pointerup(ev); |
- }, |
- dispatchPinch: function(diameter, points) { |
- var zoom = diameter / this.reference.diameter; |
- var ev = dispatcher.makeEvent('pinch', { |
- scale: zoom, |
- centerX: points.center.x, |
- centerY: points.center.y |
- }); |
- dispatcher.dispatchEvent(ev, this.reference.target); |
- }, |
- dispatchRotate: function(angle, points) { |
- var diff = Math.round((angle - this.reference.angle) % 360); |
- var ev = dispatcher.makeEvent('rotate', { |
- angle: diff, |
- centerX: points.center.x, |
- centerY: points.center.y |
- }); |
- dispatcher.dispatchEvent(ev, this.reference.target); |
- }, |
- calcPinchRotate: function() { |
- var points = this.calcChord(); |
- var diameter = points.diameter; |
- var angle = this.calcAngle(points); |
- if (diameter != this.reference.diameter) { |
- this.dispatchPinch(diameter, points); |
- } |
- if (angle != this.reference.angle) { |
- this.dispatchRotate(angle, points); |
- } |
- }, |
- calcChord: function() { |
- var pointers = []; |
- pointermap.forEach(function(p) { |
- pointers.push(p); |
- }); |
- var dist = 0; |
- // start with at least two pointers |
- var points = {a: pointers[0], b: pointers[1]}; |
- var x, y, d; |
- for (var i = 0; i < pointers.length; i++) { |
- var a = pointers[i]; |
- for (var j = i + 1; j < pointers.length; j++) { |
- var b = pointers[j]; |
- x = Math.abs(a.clientX - b.clientX); |
- y = Math.abs(a.clientY - b.clientY); |
- d = x + y; |
- if (d > dist) { |
- dist = d; |
- points = {a: a, b: b}; |
- } |
- } |
- } |
- x = Math.abs(points.a.clientX + points.b.clientX) / 2; |
- y = Math.abs(points.a.clientY + points.b.clientY) / 2; |
- points.center = { x: x, y: y }; |
- points.diameter = dist; |
- return points; |
- }, |
- calcAngle: function(points) { |
- var x = points.a.clientX - points.b.clientX; |
- var y = points.a.clientY - points.b.clientY; |
- return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; |
- }, |
- }; |
- dispatcher.registerRecognizer('pinch', pinch); |
-})(window.PointerGestures); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * This event is fired when a pointer quickly goes down and up, and is used to |
- * denote activation. |
- * |
- * Any gesture event can prevent the tap event from being created by calling |
- * `event.preventTap`. |
- * |
- * Any pointer event can prevent the tap by setting the `tapPrevented` property |
- * on itself. |
- * |
- * @module PointerGestures |
- * @submodule Events |
- * @class tap |
- */ |
-/** |
- * X axis position of the tap. |
- * @property x |
- * @type Number |
- */ |
-/** |
- * Y axis position of the tap. |
- * @property y |
- * @type Number |
- */ |
-/** |
- * Type of the pointer that made the tap. |
- * @property pointerType |
- * @type String |
- */ |
-(function(scope) { |
- var dispatcher = scope.dispatcher; |
- var pointermap = new scope.PointerMap(); |
- var tap = { |
- events: [ |
- 'pointerdown', |
- 'pointermove', |
- 'pointerup', |
- 'pointercancel', |
- 'keyup' |
- ], |
- pointerdown: function(inEvent) { |
- if (inEvent.isPrimary && !inEvent.tapPrevented) { |
- pointermap.set(inEvent.pointerId, { |
- target: inEvent.target, |
- buttons: inEvent.buttons, |
- x: inEvent.clientX, |
- y: inEvent.clientY |
- }); |
- } |
- }, |
- pointermove: function(inEvent) { |
- if (inEvent.isPrimary) { |
- var start = pointermap.get(inEvent.pointerId); |
- if (start) { |
- if (inEvent.tapPrevented) { |
- pointermap.delete(inEvent.pointerId); |
- } |
- } |
- } |
- }, |
- shouldTap: function(e, downState) { |
- if (!e.tapPrevented) { |
- if (e.pointerType === 'mouse') { |
- // only allow left click to tap for mouse |
- return downState.buttons === 1; |
- } else { |
- return true; |
- } |
- } |
- }, |
- pointerup: function(inEvent) { |
- var start = pointermap.get(inEvent.pointerId); |
- if (start && this.shouldTap(inEvent, start)) { |
- var t = scope.findLCA(start.target, inEvent.target); |
- if (t) { |
- var e = dispatcher.makeEvent('tap', { |
- x: inEvent.clientX, |
- y: inEvent.clientY, |
- detail: inEvent.detail, |
- pointerType: inEvent.pointerType |
- }); |
- dispatcher.dispatchEvent(e, t); |
- } |
- } |
- pointermap.delete(inEvent.pointerId); |
- }, |
- pointercancel: function(inEvent) { |
- pointermap.delete(inEvent.pointerId); |
- }, |
- keyup: function(inEvent) { |
- var code = inEvent.keyCode; |
- // 32 == spacebar |
- if (code === 32) { |
- var t = inEvent.target; |
- if (!(t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement)) { |
- dispatcher.dispatchEvent(dispatcher.makeEvent('tap', { |
- x: 0, |
- y: 0, |
- detail: 0, |
- pointerType: 'unavailable' |
- }), t); |
- } |
- } |
- }, |
- preventTap: function(inPointerId) { |
- pointermap.delete(inPointerId); |
- } |
- }; |
- dispatcher.registerRecognizer('tap', tap); |
-})(window.PointerGestures); |
- |
-/* |
- * Copyright 2014 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
- */ |
- |
-/** |
- * Because recognizers are loaded after dispatcher, we have to wait to register |
- * scopes until after all the recognizers. |
- */ |
-(function(scope) { |
- var dispatcher = scope.dispatcher; |
- function registerScopes() { |
- dispatcher.immediateRegister = true; |
- var rq = dispatcher.registerQueue; |
- rq.forEach(scope.register); |
- rq.length = 0; |
- } |
- if (document.readyState === 'complete') { |
- registerScopes(); |
- } else { |
- // register scopes after a steadystate is reached |
- // less MutationObserver churn |
- document.addEventListener('readystatechange', function() { |
- if (document.readyState === 'complete') { |
- registerScopes(); |
- } |
- }); |
- } |
-})(window.PointerGestures); |
- |
// Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
@@ -14371,6 +12247,8 @@ |
console.error('Unhandled binding to Node: ', this, name, observable); |
}; |
+ Node.prototype.bindFinished = function() {}; |
+ |
function updateBindings(node, name, binding) { |
var bindings = node.bindings_; |
if (!bindings) |
@@ -14955,7 +12833,7 @@ |
var owner = template.ownerDocument; |
if (!owner.stagingDocument_) { |
owner.stagingDocument_ = owner.implementation.createHTMLDocument(''); |
- |
+ owner.stagingDocument_.isStagingDocument = true; |
// TODO(rafaelw): Remove when fix for |
// https://codereview.chromium.org/164803002/ |
// makes it to Chrome release. |
@@ -15198,6 +13076,8 @@ |
createInstance: function(model, bindingDelegate, delegate_) { |
if (bindingDelegate) |
delegate_ = this.newDelegate_(bindingDelegate); |
+ else if (!delegate_) |
+ delegate_ = this.delegate_; |
if (!this.refContent_) |
this.refContent_ = this.ref_.content; |
@@ -15205,16 +13085,7 @@ |
if (content.firstChild === null) |
return emptyInstance; |
- var map = this.bindingMap_; |
- if (!map || map.content !== content) { |
- // TODO(rafaelw): Setup a MutationObserver on content to detect |
- // when the instanceMap is invalid. |
- map = createInstanceBindingMap(content, |
- delegate_ && delegate_.prepareBinding) || []; |
- map.content = content; |
- this.bindingMap_ = map; |
- } |
- |
+ var map = getInstanceBindingMap(content, delegate_); |
var stagingDocument = getTemplateStagingDocument(this); |
var instance = stagingDocument.createDocumentFragment(); |
instance.templateCreator_ = this; |
@@ -15300,7 +13171,7 @@ |
newDelegate_: function(bindingDelegate) { |
if (!bindingDelegate) |
- return {}; |
+ return; |
function delegateFn(name) { |
var fn = bindingDelegate && bindingDelegate[name]; |
@@ -15313,6 +13184,7 @@ |
} |
return { |
+ bindingMaps: {}, |
raw: bindingDelegate, |
prepareBinding: delegateFn('prepareBinding'), |
prepareInstanceModel: delegateFn('prepareInstanceModel'), |
@@ -15491,6 +13363,7 @@ |
instanceBindings.push(binding); |
} |
+ node.bindFinished(); |
if (!bindings.isTemplate) |
return; |
@@ -15602,6 +13475,43 @@ |
return map; |
} |
+ var contentUidCounter = 1; |
+ |
+ // TODO(rafaelw): Setup a MutationObserver on content which clears the id |
+ // so that bindingMaps regenerate when the template.content changes. |
+ function getContentUid(content) { |
+ var id = content.id_; |
+ if (!id) |
+ id = content.id_ = contentUidCounter++; |
+ return id; |
+ } |
+ |
+ // Each delegate is associated with a set of bindingMaps, one for each |
+ // content which may be used by a template. The intent is that each binding |
+ // delegate gets the opportunity to prepare the instance (via the prepare* |
+ // delegate calls) once across all uses. |
+ // TODO(rafaelw): Separate out the parse map from the binding map. In the |
+ // current implementation, if two delegates need a binding map for the same |
+ // content, the second will have to reparse. |
+ function getInstanceBindingMap(content, delegate_) { |
+ var contentId = getContentUid(content); |
+ if (delegate_) { |
+ var map = delegate_.bindingMaps[contentId]; |
+ if (!map) { |
+ map = delegate_.bindingMaps[contentId] = |
+ createInstanceBindingMap(content, delegate_.prepareBinding) || []; |
+ } |
+ return map; |
+ } |
+ |
+ var map = content.bindingMap_; |
+ if (!map) { |
+ map = content.bindingMap_ = |
+ createInstanceBindingMap(content, undefined) || []; |
+ } |
+ return map; |
+ } |
+ |
Object.defineProperty(Node.prototype, 'templateInstance', { |
get: function() { |
var instance = this.templateInstance_; |
@@ -15917,1739 +13827,14 @@ |
})(this); |
/* |
- Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com> |
- Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com> |
- Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> |
- Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be> |
- Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl> |
- Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com> |
- Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com> |
- Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com> |
- Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com> |
- |
- Redistribution and use in source and binary forms, with or without |
- modification, are permitted provided that the following conditions are met: |
- |
- * Redistributions of source code must retain the above copyright |
- notice, this list of conditions and the following disclaimer. |
- * Redistributions in binary form must reproduce the above copyright |
- notice, this list of conditions and the following disclaimer in the |
- documentation and/or other materials provided with the distribution. |
- |
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
- ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
- THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
-*/ |
- |
-(function (global) { |
- 'use strict'; |
- |
- var Token, |
- TokenName, |
- Syntax, |
- Messages, |
- source, |
- index, |
- length, |
- delegate, |
- lookahead, |
- state; |
- |
- Token = { |
- BooleanLiteral: 1, |
- EOF: 2, |
- Identifier: 3, |
- Keyword: 4, |
- NullLiteral: 5, |
- NumericLiteral: 6, |
- Punctuator: 7, |
- StringLiteral: 8 |
- }; |
- |
- TokenName = {}; |
- TokenName[Token.BooleanLiteral] = 'Boolean'; |
- TokenName[Token.EOF] = '<end>'; |
- TokenName[Token.Identifier] = 'Identifier'; |
- TokenName[Token.Keyword] = 'Keyword'; |
- TokenName[Token.NullLiteral] = 'Null'; |
- TokenName[Token.NumericLiteral] = 'Numeric'; |
- TokenName[Token.Punctuator] = 'Punctuator'; |
- TokenName[Token.StringLiteral] = 'String'; |
- |
- Syntax = { |
- ArrayExpression: 'ArrayExpression', |
- BinaryExpression: 'BinaryExpression', |
- CallExpression: 'CallExpression', |
- ConditionalExpression: 'ConditionalExpression', |
- EmptyStatement: 'EmptyStatement', |
- ExpressionStatement: 'ExpressionStatement', |
- Identifier: 'Identifier', |
- Literal: 'Literal', |
- LabeledStatement: 'LabeledStatement', |
- LogicalExpression: 'LogicalExpression', |
- MemberExpression: 'MemberExpression', |
- ObjectExpression: 'ObjectExpression', |
- Program: 'Program', |
- Property: 'Property', |
- ThisExpression: 'ThisExpression', |
- UnaryExpression: 'UnaryExpression' |
- }; |
- |
- // Error messages should be identical to V8. |
- Messages = { |
- UnexpectedToken: 'Unexpected token %0', |
- UnknownLabel: 'Undefined label \'%0\'', |
- Redeclaration: '%0 \'%1\' has already been declared' |
- }; |
- |
- // Ensure the condition is true, otherwise throw an error. |
- // This is only to have a better contract semantic, i.e. another safety net |
- // to catch a logic error. The condition shall be fulfilled in normal case. |
- // Do NOT use this to enforce a certain condition on any user input. |
- |
- function assert(condition, message) { |
- if (!condition) { |
- throw new Error('ASSERT: ' + message); |
- } |
- } |
- |
- function isDecimalDigit(ch) { |
- return (ch >= 48 && ch <= 57); // 0..9 |
- } |
- |
- |
- // 7.2 White Space |
- |
- function isWhiteSpace(ch) { |
- return (ch === 32) || // space |
- (ch === 9) || // tab |
- (ch === 0xB) || |
- (ch === 0xC) || |
- (ch === 0xA0) || |
- (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0); |
- } |
- |
- // 7.3 Line Terminators |
- |
- function isLineTerminator(ch) { |
- return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); |
- } |
- |
- // 7.6 Identifier Names and Identifiers |
- |
- function isIdentifierStart(ch) { |
- return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
- (ch >= 65 && ch <= 90) || // A..Z |
- (ch >= 97 && ch <= 122); // a..z |
- } |
- |
- function isIdentifierPart(ch) { |
- return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
- (ch >= 65 && ch <= 90) || // A..Z |
- (ch >= 97 && ch <= 122) || // a..z |
- (ch >= 48 && ch <= 57); // 0..9 |
- } |
- |
- // 7.6.1.1 Keywords |
- |
- function isKeyword(id) { |
- return (id === 'this') |
- } |
- |
- // 7.4 Comments |
- |
- function skipWhitespace() { |
- while (index < length && isWhiteSpace(source.charCodeAt(index))) { |
- ++index; |
- } |
- } |
- |
- function getIdentifier() { |
- var start, ch; |
- |
- start = index++; |
- while (index < length) { |
- ch = source.charCodeAt(index); |
- if (isIdentifierPart(ch)) { |
- ++index; |
- } else { |
- break; |
- } |
- } |
- |
- return source.slice(start, index); |
- } |
- |
- function scanIdentifier() { |
- var start, id, type; |
- |
- start = index; |
- |
- id = getIdentifier(); |
- |
- // There is no keyword or literal with only one character. |
- // Thus, it must be an identifier. |
- if (id.length === 1) { |
- type = Token.Identifier; |
- } else if (isKeyword(id)) { |
- type = Token.Keyword; |
- } else if (id === 'null') { |
- type = Token.NullLiteral; |
- } else if (id === 'true' || id === 'false') { |
- type = Token.BooleanLiteral; |
- } else { |
- type = Token.Identifier; |
- } |
- |
- return { |
- type: type, |
- value: id, |
- range: [start, index] |
- }; |
- } |
- |
- |
- // 7.7 Punctuators |
- |
- function scanPunctuator() { |
- var start = index, |
- code = source.charCodeAt(index), |
- code2, |
- ch1 = source[index], |
- ch2; |
- |
- switch (code) { |
- |
- // Check for most common single-character punctuators. |
- case 46: // . dot |
- case 40: // ( open bracket |
- case 41: // ) close bracket |
- case 59: // ; semicolon |
- case 44: // , comma |
- case 123: // { open curly brace |
- case 125: // } close curly brace |
- case 91: // [ |
- case 93: // ] |
- case 58: // : |
- case 63: // ? |
- ++index; |
- return { |
- type: Token.Punctuator, |
- value: String.fromCharCode(code), |
- range: [start, index] |
- }; |
- |
- default: |
- code2 = source.charCodeAt(index + 1); |
- |
- // '=' (char #61) marks an assignment or comparison operator. |
- if (code2 === 61) { |
- switch (code) { |
- case 37: // % |
- case 38: // & |
- case 42: // *: |
- case 43: // + |
- case 45: // - |
- case 47: // / |
- case 60: // < |
- case 62: // > |
- case 124: // | |
- index += 2; |
- return { |
- type: Token.Punctuator, |
- value: String.fromCharCode(code) + String.fromCharCode(code2), |
- range: [start, index] |
- }; |
- |
- case 33: // ! |
- case 61: // = |
- index += 2; |
- |
- // !== and === |
- if (source.charCodeAt(index) === 61) { |
- ++index; |
- } |
- return { |
- type: Token.Punctuator, |
- value: source.slice(start, index), |
- range: [start, index] |
- }; |
- default: |
- break; |
- } |
- } |
- break; |
- } |
- |
- // Peek more characters. |
- |
- ch2 = source[index + 1]; |
- |
- // Other 2-character punctuators: && || |
- |
- if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { |
- index += 2; |
- return { |
- type: Token.Punctuator, |
- value: ch1 + ch2, |
- range: [start, index] |
- }; |
- } |
- |
- if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { |
- ++index; |
- return { |
- type: Token.Punctuator, |
- value: ch1, |
- range: [start, index] |
- }; |
- } |
- |
- throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
- } |
- |
- // 7.8.3 Numeric Literals |
- function scanNumericLiteral() { |
- var number, start, ch; |
- |
- ch = source[index]; |
- assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), |
- 'Numeric literal must start with a decimal digit or a decimal point'); |
- |
- start = index; |
- number = ''; |
- if (ch !== '.') { |
- number = source[index++]; |
- ch = source[index]; |
- |
- // Hex number starts with '0x'. |
- // Octal number starts with '0'. |
- if (number === '0') { |
- // decimal number starts with '0' such as '09' is illegal. |
- if (ch && isDecimalDigit(ch.charCodeAt(0))) { |
- throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
- } |
- } |
- |
- while (isDecimalDigit(source.charCodeAt(index))) { |
- number += source[index++]; |
- } |
- ch = source[index]; |
- } |
- |
- if (ch === '.') { |
- number += source[index++]; |
- while (isDecimalDigit(source.charCodeAt(index))) { |
- number += source[index++]; |
- } |
- ch = source[index]; |
- } |
- |
- if (ch === 'e' || ch === 'E') { |
- number += source[index++]; |
- |
- ch = source[index]; |
- if (ch === '+' || ch === '-') { |
- number += source[index++]; |
- } |
- if (isDecimalDigit(source.charCodeAt(index))) { |
- while (isDecimalDigit(source.charCodeAt(index))) { |
- number += source[index++]; |
- } |
- } else { |
- throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
- } |
- } |
- |
- if (isIdentifierStart(source.charCodeAt(index))) { |
- throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
- } |
- |
- return { |
- type: Token.NumericLiteral, |
- value: parseFloat(number), |
- range: [start, index] |
- }; |
- } |
- |
- // 7.8.4 String Literals |
- |
- function scanStringLiteral() { |
- var str = '', quote, start, ch, octal = false; |
- |
- quote = source[index]; |
- assert((quote === '\'' || quote === '"'), |
- 'String literal must starts with a quote'); |
- |
- start = index; |
- ++index; |
- |
- while (index < length) { |
- ch = source[index++]; |
- |
- if (ch === quote) { |
- quote = ''; |
- break; |
- } else if (ch === '\\') { |
- ch = source[index++]; |
- if (!ch || !isLineTerminator(ch.charCodeAt(0))) { |
- switch (ch) { |
- case 'n': |
- str += '\n'; |
- break; |
- case 'r': |
- str += '\r'; |
- break; |
- case 't': |
- str += '\t'; |
- break; |
- case 'b': |
- str += '\b'; |
- break; |
- case 'f': |
- str += '\f'; |
- break; |
- case 'v': |
- str += '\x0B'; |
- break; |
- |
- default: |
- str += ch; |
- break; |
- } |
- } else { |
- if (ch === '\r' && source[index] === '\n') { |
- ++index; |
- } |
- } |
- } else if (isLineTerminator(ch.charCodeAt(0))) { |
- break; |
- } else { |
- str += ch; |
- } |
- } |
- |
- if (quote !== '') { |
- throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
- } |
- |
- return { |
- type: Token.StringLiteral, |
- value: str, |
- octal: octal, |
- range: [start, index] |
- }; |
- } |
- |
- function isIdentifierName(token) { |
- return token.type === Token.Identifier || |
- token.type === Token.Keyword || |
- token.type === Token.BooleanLiteral || |
- token.type === Token.NullLiteral; |
- } |
- |
- function advance() { |
- var ch; |
- |
- skipWhitespace(); |
- |
- if (index >= length) { |
- return { |
- type: Token.EOF, |
- range: [index, index] |
- }; |
- } |
- |
- ch = source.charCodeAt(index); |
- |
- // Very common: ( and ) and ; |
- if (ch === 40 || ch === 41 || ch === 58) { |
- return scanPunctuator(); |
- } |
- |
- // String literal starts with single quote (#39) or double quote (#34). |
- if (ch === 39 || ch === 34) { |
- return scanStringLiteral(); |
- } |
- |
- if (isIdentifierStart(ch)) { |
- return scanIdentifier(); |
- } |
- |
- // Dot (.) char #46 can also start a floating-point number, hence the need |
- // to check the next character. |
- if (ch === 46) { |
- if (isDecimalDigit(source.charCodeAt(index + 1))) { |
- return scanNumericLiteral(); |
- } |
- return scanPunctuator(); |
- } |
- |
- if (isDecimalDigit(ch)) { |
- return scanNumericLiteral(); |
- } |
- |
- return scanPunctuator(); |
- } |
- |
- function lex() { |
- var token; |
- |
- token = lookahead; |
- index = token.range[1]; |
- |
- lookahead = advance(); |
- |
- index = token.range[1]; |
- |
- return token; |
- } |
- |
- function peek() { |
- var pos; |
- |
- pos = index; |
- lookahead = advance(); |
- index = pos; |
- } |
- |
- // Throw an exception |
- |
- function throwError(token, messageFormat) { |
- var error, |
- args = Array.prototype.slice.call(arguments, 2), |
- msg = messageFormat.replace( |
- /%(\d)/g, |
- function (whole, index) { |
- assert(index < args.length, 'Message reference must be in range'); |
- return args[index]; |
- } |
- ); |
- |
- error = new Error(msg); |
- error.index = index; |
- error.description = msg; |
- throw error; |
- } |
- |
- // Throw an exception because of the token. |
- |
- function throwUnexpected(token) { |
- throwError(token, Messages.UnexpectedToken, token.value); |
- } |
- |
- // Expect the next token to match the specified punctuator. |
- // If not, an exception will be thrown. |
- |
- function expect(value) { |
- var token = lex(); |
- if (token.type !== Token.Punctuator || token.value !== value) { |
- throwUnexpected(token); |
- } |
- } |
- |
- // Return true if the next token matches the specified punctuator. |
- |
- function match(value) { |
- return lookahead.type === Token.Punctuator && lookahead.value === value; |
- } |
- |
- // Return true if the next token matches the specified keyword |
- |
- function matchKeyword(keyword) { |
- return lookahead.type === Token.Keyword && lookahead.value === keyword; |
- } |
- |
- function consumeSemicolon() { |
- // Catch the very common case first: immediately a semicolon (char #59). |
- if (source.charCodeAt(index) === 59) { |
- lex(); |
- return; |
- } |
- |
- skipWhitespace(); |
- |
- if (match(';')) { |
- lex(); |
- return; |
- } |
- |
- if (lookahead.type !== Token.EOF && !match('}')) { |
- throwUnexpected(lookahead); |
- } |
- } |
- |
- // 11.1.4 Array Initialiser |
- |
- function parseArrayInitialiser() { |
- var elements = []; |
- |
- expect('['); |
- |
- while (!match(']')) { |
- if (match(',')) { |
- lex(); |
- elements.push(null); |
- } else { |
- elements.push(parseExpression()); |
- |
- if (!match(']')) { |
- expect(','); |
- } |
- } |
- } |
- |
- expect(']'); |
- |
- return delegate.createArrayExpression(elements); |
- } |
- |
- // 11.1.5 Object Initialiser |
- |
- function parseObjectPropertyKey() { |
- var token; |
- |
- skipWhitespace(); |
- token = lex(); |
- |
- // Note: This function is called only from parseObjectProperty(), where |
- // EOF and Punctuator tokens are already filtered out. |
- if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) { |
- return delegate.createLiteral(token); |
- } |
- |
- return delegate.createIdentifier(token.value); |
- } |
- |
- function parseObjectProperty() { |
- var token, key; |
- |
- token = lookahead; |
- skipWhitespace(); |
- |
- if (token.type === Token.EOF || token.type === Token.Punctuator) { |
- throwUnexpected(token); |
- } |
- |
- key = parseObjectPropertyKey(); |
- expect(':'); |
- return delegate.createProperty('init', key, parseExpression()); |
- } |
- |
- function parseObjectInitialiser() { |
- var properties = []; |
- |
- expect('{'); |
- |
- while (!match('}')) { |
- properties.push(parseObjectProperty()); |
- |
- if (!match('}')) { |
- expect(','); |
- } |
- } |
- |
- expect('}'); |
- |
- return delegate.createObjectExpression(properties); |
- } |
- |
- // 11.1.6 The Grouping Operator |
- |
- function parseGroupExpression() { |
- var expr; |
- |
- expect('('); |
- |
- expr = parseExpression(); |
- |
- expect(')'); |
- |
- return expr; |
- } |
- |
- |
- // 11.1 Primary Expressions |
- |
- function parsePrimaryExpression() { |
- var type, token, expr; |
- |
- if (match('(')) { |
- return parseGroupExpression(); |
- } |
- |
- type = lookahead.type; |
- |
- if (type === Token.Identifier) { |
- expr = delegate.createIdentifier(lex().value); |
- } else if (type === Token.StringLiteral || type === Token.NumericLiteral) { |
- expr = delegate.createLiteral(lex()); |
- } else if (type === Token.Keyword) { |
- if (matchKeyword('this')) { |
- lex(); |
- expr = delegate.createThisExpression(); |
- } |
- } else if (type === Token.BooleanLiteral) { |
- token = lex(); |
- token.value = (token.value === 'true'); |
- expr = delegate.createLiteral(token); |
- } else if (type === Token.NullLiteral) { |
- token = lex(); |
- token.value = null; |
- expr = delegate.createLiteral(token); |
- } else if (match('[')) { |
- expr = parseArrayInitialiser(); |
- } else if (match('{')) { |
- expr = parseObjectInitialiser(); |
- } |
- |
- if (expr) { |
- return expr; |
- } |
- |
- throwUnexpected(lex()); |
- } |
- |
- // 11.2 Left-Hand-Side Expressions |
- |
- function parseArguments() { |
- var args = []; |
- |
- expect('('); |
- |
- if (!match(')')) { |
- while (index < length) { |
- args.push(parseExpression()); |
- if (match(')')) { |
- break; |
- } |
- expect(','); |
- } |
- } |
- |
- expect(')'); |
- |
- return args; |
- } |
- |
- function parseNonComputedProperty() { |
- var token; |
- |
- token = lex(); |
- |
- if (!isIdentifierName(token)) { |
- throwUnexpected(token); |
- } |
- |
- return delegate.createIdentifier(token.value); |
- } |
- |
- function parseNonComputedMember() { |
- expect('.'); |
- |
- return parseNonComputedProperty(); |
- } |
- |
- function parseComputedMember() { |
- var expr; |
- |
- expect('['); |
- |
- expr = parseExpression(); |
- |
- expect(']'); |
- |
- return expr; |
- } |
- |
- function parseLeftHandSideExpression() { |
- var expr, property; |
- |
- expr = parsePrimaryExpression(); |
- |
- while (match('.') || match('[')) { |
- if (match('[')) { |
- property = parseComputedMember(); |
- expr = delegate.createMemberExpression('[', expr, property); |
- } else { |
- property = parseNonComputedMember(); |
- expr = delegate.createMemberExpression('.', expr, property); |
- } |
- } |
- |
- return expr; |
- } |
- |
- // 11.3 Postfix Expressions |
- |
- var parsePostfixExpression = parseLeftHandSideExpression; |
- |
- // 11.4 Unary Operators |
- |
- function parseUnaryExpression() { |
- var token, expr; |
- |
- if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) { |
- expr = parsePostfixExpression(); |
- } else if (match('+') || match('-') || match('!')) { |
- token = lex(); |
- expr = parseUnaryExpression(); |
- expr = delegate.createUnaryExpression(token.value, expr); |
- } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { |
- throwError({}, Messages.UnexpectedToken); |
- } else { |
- expr = parsePostfixExpression(); |
- } |
- |
- return expr; |
- } |
- |
- function binaryPrecedence(token) { |
- var prec = 0; |
- |
- if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { |
- return 0; |
- } |
- |
- switch (token.value) { |
- case '||': |
- prec = 1; |
- break; |
- |
- case '&&': |
- prec = 2; |
- break; |
- |
- case '==': |
- case '!=': |
- case '===': |
- case '!==': |
- prec = 6; |
- break; |
- |
- case '<': |
- case '>': |
- case '<=': |
- case '>=': |
- case 'instanceof': |
- prec = 7; |
- break; |
- |
- case 'in': |
- prec = 7; |
- break; |
- |
- case '+': |
- case '-': |
- prec = 9; |
- break; |
- |
- case '*': |
- case '/': |
- case '%': |
- prec = 11; |
- break; |
- |
- default: |
- break; |
- } |
- |
- return prec; |
- } |
- |
- // 11.5 Multiplicative Operators |
- // 11.6 Additive Operators |
- // 11.7 Bitwise Shift Operators |
- // 11.8 Relational Operators |
- // 11.9 Equality Operators |
- // 11.10 Binary Bitwise Operators |
- // 11.11 Binary Logical Operators |
- |
- function parseBinaryExpression() { |
- var expr, token, prec, stack, right, operator, left, i; |
- |
- left = parseUnaryExpression(); |
- |
- token = lookahead; |
- prec = binaryPrecedence(token); |
- if (prec === 0) { |
- return left; |
- } |
- token.prec = prec; |
- lex(); |
- |
- right = parseUnaryExpression(); |
- |
- stack = [left, token, right]; |
- |
- while ((prec = binaryPrecedence(lookahead)) > 0) { |
- |
- // Reduce: make a binary expression from the three topmost entries. |
- while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { |
- right = stack.pop(); |
- operator = stack.pop().value; |
- left = stack.pop(); |
- expr = delegate.createBinaryExpression(operator, left, right); |
- stack.push(expr); |
- } |
- |
- // Shift. |
- token = lex(); |
- token.prec = prec; |
- stack.push(token); |
- expr = parseUnaryExpression(); |
- stack.push(expr); |
- } |
- |
- // Final reduce to clean-up the stack. |
- i = stack.length - 1; |
- expr = stack[i]; |
- while (i > 1) { |
- expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr); |
- i -= 2; |
- } |
- |
- return expr; |
- } |
- |
- |
- // 11.12 Conditional Operator |
- |
- function parseConditionalExpression() { |
- var expr, consequent, alternate; |
- |
- expr = parseBinaryExpression(); |
- |
- if (match('?')) { |
- lex(); |
- consequent = parseConditionalExpression(); |
- expect(':'); |
- alternate = parseConditionalExpression(); |
- |
- expr = delegate.createConditionalExpression(expr, consequent, alternate); |
- } |
- |
- return expr; |
- } |
- |
- // Simplification since we do not support AssignmentExpression. |
- var parseExpression = parseConditionalExpression; |
- |
- // Polymer Syntax extensions |
- |
- // Filter :: |
- // Identifier |
- // Identifier "(" ")" |
- // Identifier "(" FilterArguments ")" |
- |
- function parseFilter() { |
- var identifier, args; |
- |
- identifier = lex(); |
- |
- if (identifier.type !== Token.Identifier) { |
- throwUnexpected(identifier); |
- } |
- |
- args = match('(') ? parseArguments() : []; |
- |
- return delegate.createFilter(identifier.value, args); |
- } |
- |
- // Filters :: |
- // "|" Filter |
- // Filters "|" Filter |
- |
- function parseFilters() { |
- while (match('|')) { |
- lex(); |
- parseFilter(); |
- } |
- } |
- |
- // TopLevel :: |
- // LabelledExpressions |
- // AsExpression |
- // InExpression |
- // FilterExpression |
- |
- // AsExpression :: |
- // FilterExpression as Identifier |
- |
- // InExpression :: |
- // Identifier, Identifier in FilterExpression |
- // Identifier in FilterExpression |
- |
- // FilterExpression :: |
- // Expression |
- // Expression Filters |
- |
- function parseTopLevel() { |
- skipWhitespace(); |
- peek(); |
- |
- var expr = parseExpression(); |
- if (expr) { |
- if (lookahead.value === ',' || lookahead.value == 'in' && |
- expr.type === Syntax.Identifier) { |
- parseInExpression(expr); |
- } else { |
- parseFilters(); |
- if (lookahead.value === 'as') { |
- parseAsExpression(expr); |
- } else { |
- delegate.createTopLevel(expr); |
- } |
- } |
- } |
- |
- if (lookahead.type !== Token.EOF) { |
- throwUnexpected(lookahead); |
- } |
- } |
- |
- function parseAsExpression(expr) { |
- lex(); // as |
- var identifier = lex().value; |
- delegate.createAsExpression(expr, identifier); |
- } |
- |
- function parseInExpression(identifier) { |
- var indexName; |
- if (lookahead.value === ',') { |
- lex(); |
- if (lookahead.type !== Token.Identifier) |
- throwUnexpected(lookahead); |
- indexName = lex().value; |
- } |
- |
- lex(); // in |
- var expr = parseExpression(); |
- parseFilters(); |
- delegate.createInExpression(identifier.name, indexName, expr); |
- } |
- |
- function parse(code, inDelegate) { |
- delegate = inDelegate; |
- source = code; |
- index = 0; |
- length = source.length; |
- lookahead = null; |
- state = { |
- labelSet: {} |
- }; |
- |
- return parseTopLevel(); |
- } |
- |
- global.esprima = { |
- parse: parse |
- }; |
-})(this); |
- |
-// Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
-// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
-// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
-// The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
-// 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 |
- |
-(function (global) { |
- 'use strict'; |
- |
- // JScript does not have __proto__. We wrap all object literals with |
- // createObject which uses Object.create, Object.defineProperty and |
- // Object.getOwnPropertyDescriptor to create a new object that does the exact |
- // same thing. The main downside to this solution is that we have to extract |
- // all those property descriptors for IE. |
- var createObject = ('__proto__' in {}) ? |
- function(obj) { return obj; } : |
- function(obj) { |
- var proto = obj.__proto__; |
- if (!proto) |
- return obj; |
- var newObject = Object.create(proto); |
- Object.getOwnPropertyNames(obj).forEach(function(name) { |
- Object.defineProperty(newObject, name, |
- Object.getOwnPropertyDescriptor(obj, name)); |
- }); |
- return newObject; |
- }; |
- |
- function prepareBinding(expressionText, name, node, filterRegistry) { |
- var expression; |
- try { |
- expression = getExpression(expressionText); |
- if (expression.scopeIdent && |
- (node.nodeType !== Node.ELEMENT_NODE || |
- node.tagName !== 'TEMPLATE' || |
- (name !== 'bind' && name !== 'repeat'))) { |
- throw Error('as and in can only be used within <template bind/repeat>'); |
- } |
- } catch (ex) { |
- console.error('Invalid expression syntax: ' + expressionText, ex); |
- return; |
- } |
- |
- return function(model, node, oneTime) { |
- var binding = expression.getBinding(model, filterRegistry, oneTime); |
- if (expression.scopeIdent && binding) { |
- node.polymerExpressionScopeIdent_ = expression.scopeIdent; |
- if (expression.indexIdent) |
- node.polymerExpressionIndexIdent_ = expression.indexIdent; |
- } |
- |
- return binding; |
- } |
- } |
- |
- // TODO(rafaelw): Implement simple LRU. |
- var expressionParseCache = Object.create(null); |
- |
- function getExpression(expressionText) { |
- var expression = expressionParseCache[expressionText]; |
- if (!expression) { |
- var delegate = new ASTDelegate(); |
- esprima.parse(expressionText, delegate); |
- expression = new Expression(delegate); |
- expressionParseCache[expressionText] = expression; |
- } |
- return expression; |
- } |
- |
- function Literal(value) { |
- this.value = value; |
- this.valueFn_ = undefined; |
- } |
- |
- Literal.prototype = { |
- valueFn: function() { |
- if (!this.valueFn_) { |
- var value = this.value; |
- this.valueFn_ = function() { |
- return value; |
- } |
- } |
- |
- return this.valueFn_; |
- } |
- } |
- |
- function IdentPath(name) { |
- this.name = name; |
- this.path = Path.get(name); |
- } |
- |
- IdentPath.prototype = { |
- valueFn: function() { |
- if (!this.valueFn_) { |
- var name = this.name; |
- var path = this.path; |
- this.valueFn_ = function(model, observer) { |
- if (observer) |
- observer.addPath(model, path); |
- |
- return path.getValueFrom(model); |
- } |
- } |
- |
- return this.valueFn_; |
- }, |
- |
- setValue: function(model, newValue) { |
- if (this.path.length == 1); |
- model = findScope(model, this.path[0]); |
- |
- return this.path.setValueFrom(model, newValue); |
- } |
- }; |
- |
- function MemberExpression(object, property, accessor) { |
- // 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.dynamicDeps = typeof object == 'function' || object.dynamic; |
- |
- this.dynamic = typeof property == 'function' || |
- property.dynamic || |
- accessor == '['; |
- |
- this.simplePath = |
- !this.dynamic && |
- !this.dynamicDeps && |
- property instanceof IdentPath && |
- (object instanceof MemberExpression || object instanceof IdentPath); |
- |
- this.object = this.simplePath ? object : getFn(object); |
- this.property = accessor == '.' ? 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); |
- } |
- |
- return this.fullPath_; |
- }, |
- |
- valueFn: function() { |
- if (!this.valueFn_) { |
- var object = this.object; |
- |
- if (this.simplePath) { |
- var path = this.fullPath; |
- |
- this.valueFn_ = function(model, observer) { |
- if (observer) |
- observer.addPath(model, path); |
- |
- return path.getValueFrom(model); |
- }; |
- } else if (this.property instanceof IdentPath) { |
- var path = Path.get(this.property.name); |
- |
- this.valueFn_ = function(model, observer) { |
- var context = object(model, observer); |
- |
- if (observer) |
- observer.addPath(context, path); |
- |
- return path.getValueFrom(context); |
- } |
- } else { |
- // Computed property. |
- var property = this.property; |
- |
- this.valueFn_ = function(model, observer) { |
- var context = object(model, observer); |
- var propName = property(model, observer); |
- if (observer) |
- observer.addPath(context, propName); |
- |
- return context ? context[propName] : undefined; |
- }; |
- } |
- } |
- return this.valueFn_; |
- }, |
- |
- setValue: function(model, newValue) { |
- if (this.simplePath) { |
- this.fullPath.setValueFrom(model, newValue); |
- return newValue; |
- } |
- |
- var object = this.object(model); |
- var propName = this.property instanceof IdentPath ? this.property.name : |
- this.property(model); |
- return object[propName] = newValue; |
- } |
- }; |
- |
- function Filter(name, args) { |
- this.name = name; |
- this.args = []; |
- for (var i = 0; i < args.length; i++) { |
- this.args[i] = getFn(args[i]); |
- } |
- } |
- |
- Filter.prototype = { |
- transform: function(value, toModelDirection, filterRegistry, model, |
- observer) { |
- var fn = filterRegistry[this.name]; |
- var context = model; |
- if (fn) { |
- context = undefined; |
- } else { |
- fn = context[this.name]; |
- if (!fn) { |
- console.error('Cannot find filter: ' + this.name); |
- return; |
- } |
- } |
- |
- // If toModelDirection is falsey, then the "normal" (dom-bound) direction |
- // is used. Otherwise, it looks for a 'toModel' property function on the |
- // object. |
- if (toModelDirection) { |
- fn = fn.toModel; |
- } else if (typeof fn.toDOM == 'function') { |
- fn = fn.toDOM; |
- } |
- |
- if (typeof fn != 'function') { |
- console.error('No ' + (toModelDirection ? 'toModel' : 'toDOM') + |
- ' found on' + this.name); |
- return; |
- } |
- |
- var args = [value]; |
- for (var i = 0; i < this.args.length; i++) { |
- args[i + 1] = getFn(this.args[i])(model, observer); |
- } |
- |
- return fn.apply(context, args); |
- } |
- }; |
- |
- function notImplemented() { throw Error('Not Implemented'); } |
- |
- var unaryOperators = { |
- '+': function(v) { return +v; }, |
- '-': function(v) { return -v; }, |
- '!': function(v) { return !v; } |
- }; |
- |
- var binaryOperators = { |
- '+': function(l, r) { return l+r; }, |
- '-': function(l, r) { return l-r; }, |
- '*': function(l, r) { return l*r; }, |
- '/': function(l, r) { return l/r; }, |
- '%': function(l, r) { return l%r; }, |
- '<': function(l, r) { return l<r; }, |
- '>': function(l, r) { return l>r; }, |
- '<=': function(l, r) { return l<=r; }, |
- '>=': function(l, r) { return l>=r; }, |
- '==': function(l, r) { return l==r; }, |
- '!=': function(l, r) { return l!=r; }, |
- '===': function(l, r) { return l===r; }, |
- '!==': function(l, r) { return l!==r; }, |
- '&&': function(l, r) { return l&&r; }, |
- '||': function(l, r) { return l||r; }, |
- }; |
- |
- function getFn(arg) { |
- return typeof arg == 'function' ? arg : arg.valueFn(); |
- } |
- |
- function ASTDelegate() { |
- this.expression = null; |
- this.filters = []; |
- this.deps = {}; |
- this.currentPath = undefined; |
- this.scopeIdent = undefined; |
- this.indexIdent = undefined; |
- this.dynamicDeps = false; |
- } |
- |
- ASTDelegate.prototype = { |
- createUnaryExpression: function(op, argument) { |
- if (!unaryOperators[op]) |
- throw Error('Disallowed operator: ' + op); |
- |
- argument = getFn(argument); |
- |
- return function(model, observer) { |
- return unaryOperators[op](argument(model, observer)); |
- }; |
- }, |
- |
- createBinaryExpression: function(op, left, right) { |
- if (!binaryOperators[op]) |
- throw Error('Disallowed operator: ' + op); |
- |
- left = getFn(left); |
- right = getFn(right); |
- |
- return function(model, observer) { |
- return binaryOperators[op](left(model, observer), |
- right(model, observer)); |
- }; |
- }, |
- |
- createConditionalExpression: function(test, consequent, alternate) { |
- test = getFn(test); |
- consequent = getFn(consequent); |
- alternate = getFn(alternate); |
- |
- return function(model, observer) { |
- return test(model, observer) ? |
- consequent(model, observer) : alternate(model, observer); |
- } |
- }, |
- |
- createIdentifier: function(name) { |
- var ident = new IdentPath(name); |
- ident.type = 'Identifier'; |
- return ident; |
- }, |
- |
- createMemberExpression: function(accessor, object, property) { |
- var ex = new MemberExpression(object, property, accessor); |
- if (ex.dynamicDeps) |
- this.dynamicDeps = true; |
- return ex; |
- }, |
- |
- createLiteral: function(token) { |
- return new Literal(token.value); |
- }, |
- |
- createArrayExpression: function(elements) { |
- for (var i = 0; i < elements.length; i++) |
- elements[i] = getFn(elements[i]); |
- |
- return function(model, observer) { |
- var arr = [] |
- for (var i = 0; i < elements.length; i++) |
- arr.push(elements[i](model, observer)); |
- return arr; |
- } |
- }, |
- |
- createProperty: function(kind, key, value) { |
- return { |
- key: key instanceof IdentPath ? key.name : key.value, |
- value: value |
- }; |
- }, |
- |
- createObjectExpression: function(properties) { |
- for (var i = 0; i < properties.length; i++) |
- properties[i].value = getFn(properties[i].value); |
- |
- return function(model, observer) { |
- var obj = {}; |
- for (var i = 0; i < properties.length; i++) |
- obj[properties[i].key] = properties[i].value(model, observer); |
- return obj; |
- } |
- }, |
- |
- createFilter: function(name, args) { |
- this.filters.push(new Filter(name, args)); |
- }, |
- |
- createAsExpression: function(expression, scopeIdent) { |
- this.expression = expression; |
- this.scopeIdent = scopeIdent; |
- }, |
- |
- createInExpression: function(scopeIdent, indexIdent, expression) { |
- this.expression = expression; |
- this.scopeIdent = scopeIdent; |
- this.indexIdent = indexIdent; |
- }, |
- |
- createTopLevel: function(expression) { |
- this.expression = expression; |
- }, |
- |
- createThisExpression: notImplemented |
- } |
- |
- function ConstantObservable(value) { |
- this.value_ = value; |
- } |
- |
- ConstantObservable.prototype = { |
- open: function() { return this.value_; }, |
- discardChanges: function() { return this.value_; }, |
- deliver: function() {}, |
- close: function() {}, |
- } |
- |
- function Expression(delegate) { |
- this.scopeIdent = delegate.scopeIdent; |
- this.indexIdent = delegate.indexIdent; |
- |
- if (!delegate.expression) |
- throw Error('No expression found.'); |
- |
- this.expression = delegate.expression; |
- getFn(this.expression); // forces enumeration of path dependencies |
- |
- this.filters = delegate.filters; |
- this.dynamicDeps = delegate.dynamicDeps; |
- } |
- |
- Expression.prototype = { |
- getBinding: function(model, filterRegistry, oneTime) { |
- if (oneTime) |
- return this.getValue(model, undefined, filterRegistry); |
- |
- var observer = new CompoundObserver(); |
- // captures deps. |
- var firstValue = this.getValue(model, observer, filterRegistry); |
- var firstTime = true; |
- var self = this; |
- |
- function valueFn() { |
- // deps cannot have changed on first value retrieval. |
- if (firstTime) { |
- firstTime = false; |
- return firstValue; |
- } |
- |
- if (self.dynamicDeps) |
- observer.startReset(); |
- |
- var value = self.getValue(model, |
- self.dynamicDeps ? observer : undefined, |
- filterRegistry); |
- if (self.dynamicDeps) |
- observer.finishReset(); |
- |
- return value; |
- } |
- |
- function setValueFn(newValue) { |
- self.setValue(model, newValue, filterRegistry); |
- return newValue; |
- } |
- |
- return new ObserverTransform(observer, valueFn, setValueFn, true); |
- }, |
- |
- getValue: function(model, observer, filterRegistry) { |
- var value = getFn(this.expression)(model, observer); |
- for (var i = 0; i < this.filters.length; i++) { |
- value = this.filters[i].transform(value, false, filterRegistry, model, |
- observer); |
- } |
- |
- return value; |
- }, |
- |
- 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); |
- } |
- |
- if (this.expression.setValue) |
- return this.expression.setValue(model, newValue); |
- } |
- } |
- |
- /** |
- * Converts a style property name to a css property name. For example: |
- * "WebkitUserSelect" to "-webkit-user-select" |
- */ |
- function convertStylePropertyName(name) { |
- return String(name).replace(/[A-Z]/g, function(c) { |
- return '-' + c.toLowerCase(); |
- }); |
- } |
- |
- function isEventHandler(name) { |
- return name[0] === 'o' && |
- name[1] === 'n' && |
- name[2] === '-'; |
- } |
- |
- var mixedCaseEventTypes = {}; |
- [ |
- 'webkitAnimationStart', |
- 'webkitAnimationEnd', |
- 'webkitTransitionEnd', |
- 'DOMFocusOut', |
- 'DOMFocusIn', |
- 'DOMMouseScroll' |
- ].forEach(function(e) { |
- mixedCaseEventTypes[e.toLowerCase()] = e; |
- }); |
- |
- var parentScopeName = '@' + Math.random().toString(36).slice(2); |
- |
- // Single ident paths must bind directly to the appropriate scope object. |
- // I.e. Pushed values in two-bindings need to be assigned to the actual model |
- // object. |
- function findScope(model, prop) { |
- while (model[parentScopeName] && |
- !Object.prototype.hasOwnProperty.call(model, prop)) { |
- model = model[parentScopeName]; |
- } |
- |
- return model; |
- } |
- |
- function resolveEventReceiver(model, path, node) { |
- if (path.length == 0) |
- return undefined; |
- |
- if (path.length == 1) |
- return findScope(model, path[0]); |
- |
- for (var i = 0; model != null && i < path.length - 1; i++) { |
- model = model[path[i]]; |
- } |
- |
- return model; |
- } |
- |
- function prepareEventBinding(path, name, polymerExpressions) { |
- var eventType = name.substring(3); |
- eventType = mixedCaseEventTypes[eventType] || eventType; |
- |
- return function(model, node, oneTime) { |
- var fn, receiver, handler; |
- if (typeof polymerExpressions.resolveEventHandler == 'function') { |
- handler = function(e) { |
- fn = fn || polymerExpressions.resolveEventHandler(model, path, node); |
- fn(e, e.detail, e.currentTarget); |
- |
- if (Platform && typeof Platform.flush == 'function') |
- Platform.flush(); |
- }; |
- } else { |
- handler = function(e) { |
- fn = fn || path.getValueFrom(model); |
- receiver = receiver || resolveEventReceiver(model, path, node); |
- |
- fn.apply(receiver, [e, e.detail, e.currentTarget]); |
- |
- if (Platform && typeof Platform.flush == 'function') |
- Platform.flush(); |
- }; |
- } |
- |
- node.addEventListener(eventType, handler); |
- |
- if (oneTime) |
- return; |
- |
- function bindingValue() { |
- return '{{ ' + path + ' }}'; |
- } |
- |
- return { |
- open: bindingValue, |
- discardChanges: bindingValue, |
- close: function() { |
- node.removeEventListener(eventType, handler); |
- } |
- }; |
- } |
- } |
- |
- function isLiteralExpression(pathString) { |
- switch (pathString) { |
- case '': |
- return false; |
- |
- case 'false': |
- case 'null': |
- case 'true': |
- return true; |
- } |
- |
- if (!isNaN(Number(pathString))) |
- return true; |
- |
- return false; |
- }; |
- |
- function PolymerExpressions() {} |
- |
- PolymerExpressions.prototype = { |
- // "built-in" filters |
- styleObject: function(value) { |
- var parts = []; |
- for (var key in value) { |
- parts.push(convertStylePropertyName(key) + ': ' + value[key]); |
- } |
- return parts.join('; '); |
- }, |
- |
- tokenList: function(value) { |
- var tokens = []; |
- for (var key in value) { |
- if (value[key]) |
- tokens.push(key); |
- } |
- return tokens.join(' '); |
- }, |
- |
- // binding delegate API |
- prepareInstancePositionChanged: function(template) { |
- var indexIdent = template.polymerExpressionIndexIdent_; |
- if (!indexIdent) |
- return; |
- |
- return function(templateInstance, index) { |
- templateInstance.model[indexIdent] = index; |
- }; |
- }, |
- |
- prepareBinding: function(pathString, name, node) { |
- var path = Path.get(pathString); |
- if (isEventHandler(name)) { |
- if (!path.valid) { |
- console.error('on-* bindings must be simple path expressions'); |
- return; |
- } |
- |
- return prepareEventBinding(path, name, this); |
- } |
- |
- if (!isLiteralExpression(pathString) && path.valid) { |
- if (path.length == 1) { |
- return function(model, node, oneTime) { |
- if (oneTime) |
- return path.getValueFrom(model); |
- |
- var scope = findScope(model, path[0]); |
- return new PathObserver(scope, path); |
- }; |
- } |
- return; // bail out early if pathString is simple path. |
- } |
- |
- return prepareBinding(pathString, name, node, this); |
- }, |
- |
- prepareInstanceModel: function(template) { |
- var scopeName = template.polymerExpressionScopeIdent_; |
- if (!scopeName) |
- return; |
- |
- var parentScope = template.templateInstance ? |
- template.templateInstance.model : |
- template.model; |
- |
- var indexName = template.polymerExpressionIndexIdent_; |
- |
- return function(model) { |
- var scope = Object.create(parentScope); |
- scope[scopeName] = model; |
- scope[indexName] = undefined; |
- scope[parentScopeName] = parentScope; |
- return scope; |
- }; |
- } |
- }; |
- |
- global.PolymerExpressions = PolymerExpressions; |
- if (global.exposeGetExpression) |
- global.getExpression_ = getExpression; |
- |
- global.PolymerExpressions.prepareEventBinding = prepareEventBinding; |
-})(this); |
- |
-/* |
- * Copyright 2013 The Polymer Authors. All rights reserved. |
- * Use of this source code is governed by a BSD-style |
- * license that can be found in the LICENSE file. |
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt |
+ * 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 |
*/ |
+ |
(function(scope) { |
// inject style sheet |