diff --git a/gruntfile.js b/gruntfile.js index 1b102435b3..145c1e3913 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -4,6 +4,7 @@ * license that can be found in the LICENSE file. */ module.exports = function(grunt) { + var banner = [grunt.file.read('LICENSE'), '// @version ' + grunt.file.readJSON('package.json').version, ''].join(grunt.util.linefeed); Platform = [ '../platform/platform.min.js' ]; @@ -81,7 +82,7 @@ module.exports = function(grunt) { }, uglify: { options: { - banner: grunt.file.read('LICENSE'), + banner: banner, nonull: true }, Polymer: { diff --git a/package.json b/package.json index 877784466e..35ce1f6458 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Polymer", - "version": "0.0.1", + "version": "0.0.20130808", "devDependencies": { "mocha": "*", "chai": "*", diff --git a/src/declaration/polymer-element.js b/src/declaration/polymer-element.js index 5c827c082e..9bb69e7590 100644 --- a/src/declaration/polymer-element.js +++ b/src/declaration/polymer-element.js @@ -6,34 +6,69 @@ (function(scope) { // imports - + var extend = Polymer.extend; var apis = scope.api.declaration; - var deferred = {}; - // imperative implementation: Polymer() - + // maps tag names to prototypes - var registry = {}; + var prototypesByName = {}; + + function getRegisteredPrototype(name) { + return prototypesByName[name]; + } + + // elements waiting for prototype, by name + var waitPrototype = {}; - // register an 'own' prototype for tag `name` + // specify an 'own' prototype for tag `name` function element(name, prototype) { - registry[name] = prototype || {}; - if (deferred[name]) { - deferred[name].define(); + //console.log('registering [' + name + ']'); + // cache the prototype + prototypesByName[name] = prototype || {}; + // notify the registrar waiting for 'name', if any + notifyPrototype(name); + } + + function notifyPrototype(name) { + if (waitPrototype[name]) { + waitPrototype[name].registerWhenReady(); + delete waitPrototype[name]; } } - + + // elements waiting for super, by name + var waitSuper = {}; + + function notifySuper(name) { + registered[name] = true; + var waiting = waitSuper[name]; + if (waiting) { + waiting.forEach(function(w) { + w.registerWhenReady(); + }); + delete waitSuper[name]; + } + } + + // track document.register'ed tag names + + var registered = {}; + + function isRegistered(name) { + return registered[name]; + } + // returns a prototype that chains to or HTMLElement function generatePrototype(tag) { return Object.create(HTMLElement.getPrototypeForTag(tag)); } - + // On platforms that do not support __proto__ (IE10), the prototype chain // of a custom element is simulated via installation of __proto__. // Although custom elements manages this, we install it here so it's - // available during desuaring. + // available during desugaring. function ensurePrototypeTraversal(prototype) { if (!Object.__proto__) { var ancestor = Object.getPrototypeOf(prototype); @@ -44,35 +79,108 @@ } } - // declarative implementation: - + function whenImportsLoaded(doThis) { + if (window.HTMLImports && !HTMLImports.readyTime) { + addEventListener('HTMLImportsLoaded', doThis); + } else { + doThis(); + } + } + + // declarative implementation: + var prototype = generatePrototype(); + extend(prototype, { // TODO(sjmiles): temporary BC readyCallback: function() { - this._createdCallback(); + this.createdCallback(); }, createdCallback: function() { - this._createdCallback(); + // fetch the element name + this.name = this.getAttribute('name'); + // install element definition, if ready + this.registerWhenReady(); }, - // custom element processing - _createdCallback: function() { - // fetch our element name - var name = this.getAttribute('name'); - if (registry[name]) { - this.define(); + registerWhenReady: function() { + var name = this.name; + // if we have no prototype + if (!getRegisteredPrototype(name)) { + // then wait for a prototype + waitPrototype[name] = this; + // TODO(sjmiles): 'noscript' gambit is mutually exclusive + // with 'async' gambit below + // + // if explicitly marked as 'noscript' + if (this.hasAttribute('noscript')) { + // go async to allow children to parse + setTimeout(function() { + // register with the default prototype + element(name, null); + }, 0); + } + // TODO(sjmiles): 'async' gambit is deemed too dangerous + // because it changes the timing of noscript elements + // in import from 'timeout 0' to 'HTMLImportsReady' + /* + // if we are not explicitly async... + if (!this.hasAttribute('async')) { + // then we expect the script to be registered + // by end of microtask(-ish) and can otherwise + // consider this element to have no script + // + // TODO(sjmiles): + // we have to wait for end-of-microtask because + // native CE upgrades polymer-element (any custom + // element, really) *before* it's children are + // parsed, and it's common for the script to + // exist as a child of the polymer-element + // + // additionally, there is a massive asynchrony + // between parsing HTML in imports and executing + // script that foils the end of microtask gambit + // Waiting on HTMLImportsLoaded signal solves + // both timing problems for imports loaded + // at startup under the import polyfill + whenImportsLoaded(function() { + if (!getRegisteredPrototype(name)) { + console.warn('giving up waiting for script for [' + name + ']'); + element(name, null); + } + }); + } + */ + return; + } + // fetch our extendee name + var extendee = this.getAttribute('extends'); + // if extending a custom element... + if (extendee && extendee.indexOf('-') >= 0) { + // wait for the extendee to be registered first + if (!isRegistered(extendee)) { + (waitSuper[extendee] = (waitSuper[extendee] || [])).push(this); + return; + } + } + // TODO(sjmiles): HTMLImports polyfill awareness + // elements in the main document are likely to parse + // in advance of elements in imports because the + // polyfill parser is simulated + // therefore, wait for imports loaded before + // finalizing elements in the main document + if (document.contains(this)) { + whenImportsLoaded(function() { + this.register(name, extendee); + }.bind(this)); } else { - deferred[name] = this; + this.register(name, extendee); } }, - define: function() { - // fetch our element name - var name = this.getAttribute('name'); - // fetch our extendee name - var extnds = this.getAttribute('extends'); + register: function(name, extendee) { + //console.log('register', name, extendee); // build prototype combining extendee, Polymer base, and named api - this.prototype = this.generateCustomPrototype(name, extnds); - // questionable backref + this.prototype = this.generateCustomPrototype(name, extendee); + // backref this.prototype.element = this; // TODO(sorvell): install a helper method this.resolvePath to aid in // setting resource paths. e.g. @@ -85,12 +193,14 @@ this.desugar(); // under ShadowDOMPolyfill, transforms to approximate missing CSS features if (window.ShadowDOMPolyfill) { - Platform.ShadowCSS.shimStyling(this.templateContent(), name, extnds); + Platform.ShadowCSS.shimStyling(this.templateContent(), name, extendee); } // register our custom element - this.register(name); - // reference constructor in a global named by 'constructor' attribute + this.registerPrototype(name); + // reference constructor in a global named by 'constructor' attribute this.publishConstructor(); + // subclasses may now register themselves + notifySuper(name); }, // implement various declarative features desugar: function(prototype) { @@ -142,7 +252,7 @@ // mix api registered to 'name' into 'prototype' addNamedApi: function(prototype, name) { // combine custom api into prototype - return extend(prototype, registry[name]); + return extend(prototype, getRegisteredPrototype(name)); }, // make a fresh object that inherits from a prototype object inheritObject: function(prototype, name) { @@ -150,7 +260,7 @@ prototype[name] = extend({}, Object.getPrototypeOf(prototype)[name]); }, // register 'prototype' to custom element 'name', store constructor - register: function(name) { + registerPrototype: function(name) { // register the custom type this.ctor = document.register(name, { prototype: this.prototype @@ -177,12 +287,12 @@ // register polymer-element with document document.register('polymer-element', {prototype: prototype}); - - // namespace shenanigans + + // namespace shenanigans so we can expose our scope on the registration function // TODO(sjmiles): find a way to do this that is less terrible // copy window.Polymer properties onto `element()` - extend(element, window.Polymer); + extend(element, scope); // make window.Polymer reference `element()` window.Polymer = element; diff --git a/src/instance/events.js b/src/instance/events.js index 23a30b654e..b10100cf9d 100644 --- a/src/instance/events.js +++ b/src/instance/events.js @@ -85,7 +85,7 @@ if (!event.cancelBubble) { event.on = EVENT_PREFIX + event.type; log.events && console.group("[%s]: listenLocal [%s]", host.localName, event.on); - if (!event.path || window.ShadowDOMPolyfill) { + if (!event.path) { _listenLocalNoEventPath(host, event); } else { _listenLocal(host, event); diff --git a/src/instance/mdv.js b/src/instance/mdv.js index f206e32f46..59f42e0d58 100644 --- a/src/instance/mdv.js +++ b/src/instance/mdv.js @@ -19,15 +19,15 @@ instanceTemplate: function(template) { return template.createInstance(this, mdv_syntax); }, - createBinding: function(name, model, path) { - //console.log(arguments); + bind: function(name, model, path) { var property = this.propertyForAttribute(name); if (property) { + this.unbind(name); // use n-way Polymer binding var observer = this.bindProperty(property, model, path); // stick path on observer so it's available via this.bindings observer.path = path; - return observer; + return this.bindings[name] = observer; } else { return this.super(arguments); } diff --git a/test/html/attr-mustache.html b/test/html/attr-mustache.html index 7b7b431be0..fd3aa8ad86 100644 --- a/test/html/attr-mustache.html +++ b/test/html/attr-mustache.html @@ -13,8 +13,8 @@ - + diff --git a/test/html/element-registration.html b/test/html/element-registration.html new file mode 100644 index 0000000000..523d35708e --- /dev/null +++ b/test/html/element-registration.html @@ -0,0 +1,157 @@ + + + + element script + + + + + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/html/element-script.html b/test/html/element-script.html index 48cb7e24d9..c2b2d6236a 100644 --- a/test/html/element-script.html +++ b/test/html/element-script.html @@ -41,13 +41,10 @@
- + - @@ -84,28 +81,32 @@ diff --git a/test/html/event-path-declarative.html b/test/html/event-path-declarative.html index 91bb74c3fd..9b26b0e954 100644 --- a/test/html/event-path-declarative.html +++ b/test/html/event-path-declarative.html @@ -120,9 +120,13 @@ } } - document.addEventListener('WebComponentsReady', function() { - var target = document.querySelector('#target'); - target.dispatchEvent(new CustomEvent('test-event', {bubbles: true})); + document.addEventListener('WebComponentsReady', function(e) { + if (e.path) { + var target = document.querySelector('#target'); + target.dispatchEvent(new CustomEvent('test-event', {bubbles: true})); + } else { + console.log('skipping event.path test since it\'s not supported'); + } done(); }); diff --git a/test/html/event-path-getDistributedNodes.html b/test/html/event-path-getDistributedNodes.html deleted file mode 100644 index 80679b2a5e..0000000000 --- a/test/html/event-path-getDistributedNodes.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - event path - - - - - - - -
Item
-
- - - - - - - - - - - - - - diff --git a/test/js/bindMDV.js b/test/js/bindMDV.js index db1b73061f..d3b0fd7164 100644 --- a/test/js/bindMDV.js +++ b/test/js/bindMDV.js @@ -52,7 +52,5 @@ suite('bindMDV', function() { htmlSuite('unbind', function() { htmlTest('html/template-distribute-dynamic.html'); - htmlTest('html/template-distribute-dynamic.html?shadow'); htmlTest('html/unbind.html'); - htmlTest('html/unbind.html?shadow'); }); \ No newline at end of file diff --git a/test/js/events.js b/test/js/events.js index b92bfaf696..7e66bd8dbf 100644 --- a/test/js/events.js +++ b/test/js/events.js @@ -68,9 +68,5 @@ suite('events', function() { htmlSuite('events-declarative', function() { htmlTest('html/event-path.html'); - // TODO(sorvell): include when we expect to pass - //htmlTest('html/event-path-getDistributedNodes.html'); - // TODO(sorvell): include when we expect to pass; currently expected - // to pass only in Canary with expr. features on. - //htmlTest('html/event-path-declarative.html'); + htmlTest('html/event-path-declarative.html'); }); \ No newline at end of file diff --git a/test/js/mdv-syntax.js b/test/js/mdv-syntax.js index 03c6a56b8b..7d7600bddd 100644 --- a/test/js/mdv-syntax.js +++ b/test/js/mdv-syntax.js @@ -11,5 +11,4 @@ htmlSuite('MDV syntax', function() { if (!window.ShadowDOMPolyfill) { htmlTest('html/mdv-syntax.html'); } - //htmlTest('html/mdv-syntax.html?shadow'); }); diff --git a/test/js/register.js b/test/js/register.js index 4ff02de121..00ea17f2a4 100644 --- a/test/js/register.js +++ b/test/js/register.js @@ -6,9 +6,9 @@ suite('register', function() { var assert = chai.assert; - + var work; - + setup(function() { work = document.createElement('div'); wrap(document.body).appendChild(work); @@ -49,3 +49,7 @@ htmlSuite('element callbacks', function() { htmlSuite('element script', function() { htmlTest('html/element-script.html'); }); + +htmlSuite('element registration', function() { + htmlTest('html/element-registration.html'); +}); diff --git a/test/js/styling.js b/test/js/styling.js index d9fee3c260..6655a99520 100644 --- a/test/js/styling.js +++ b/test/js/styling.js @@ -5,7 +5,6 @@ */ htmlSuite('styling', function() { htmlTest('html/styling/sheet-order.html'); - htmlTest('html/styling/sheet-order.html?shadow'); htmlTest('html/styling/sheet-main-doc.html'); htmlTest('html/styling/apply-reset-styles.html'); }); diff --git a/tools b/tools index 5d50474984..b853b33b56 160000 --- a/tools +++ b/tools @@ -1 +1 @@ -Subproject commit 5d504749846cc8df777ab6c493d7169ddfc8d638 +Subproject commit b853b33b567fd2c67f51be3eae7022e4eded07f4