diff --git a/lazy-register.html b/lazy-register.html new file mode 100644 index 0000000000..182a621e4c --- /dev/null +++ b/lazy-register.html @@ -0,0 +1,76 @@ + + + + + lazy-register + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/polymer.html b/polymer.html index 9975c54b33..c156365738 100644 --- a/polymer.html +++ b/polymer.html @@ -32,8 +32,12 @@ this._prepConstructor(); // template this._prepTemplate(); - // styles and style properties + // styles this._prepStyles(); + }, + + _registerLazyFeatures: function() { + this._prepShimStyles(); // template markup this._prepAnnotations(); // accessors diff --git a/src/lib/base.html b/src/lib/base.html index acfc5e9a18..d7d02c5e43 100644 --- a/src/lib/base.html +++ b/src/lib/base.html @@ -28,16 +28,36 @@ this._desugarBehaviors(); // abstract this._doBehavior('beforeRegister'); // abstract this._registerFeatures(); // abstract - this._doBehavior('registered'); // abstract }, createdCallback: function() { + this._ensureRegistered(this.__proto__); Polymer.telemetry.instanceCount++; this.root = this; this._doBehavior('created'); // abstract this._initFeatures(); // abstract }, + /** + * When called from the element's prototype, ensures that the element has + * fully registered. By default registration tasks are defered until the + * first instance of an element is created. + */ + ensureRegistered: function() { + this._ensureRegistered(this); + }, + + _ensureRegistered: function(proto) { + if (proto.__hasRegistered !== proto.is) { + proto.__hasRegistered = proto.is; + if (proto._registerLazyFeatures) { + proto._registerLazyFeatures(); + } + // registration extension point + this._doBehavior('registered'); + } + }, + // reserved for canonical behavior attachedCallback: function() { // NOTE: workaround for: diff --git a/src/lib/style-util.html b/src/lib/style-util.html index 4b93a6b22f..b429e93b5e 100644 --- a/src/lib/style-util.html +++ b/src/lib/style-util.html @@ -77,22 +77,38 @@ }, // add a string of cssText to the document. - applyCss: function(cssText, moniker, target, afterNode) { + applyCss: function(cssText, moniker, target, contextNode) { + var style = this.createScopeStyle(cssText, moniker); + target = target || document.head; + var after = (contextNode && contextNode.nextSibling) || + target.firstChild; + this.__lastHeadApplyNode = style; + return target.insertBefore(style, after); + }, + + createScopeStyle: function(cssText, moniker) { var style = document.createElement('style'); if (moniker) { style.setAttribute('scope', moniker); } style.textContent = cssText; - target = target || document.head; - if (!afterNode) { - var n$ = target.querySelectorAll('style[scope]'); - afterNode = n$[n$.length-1]; - } - target.insertBefore(style, - (afterNode && afterNode.nextSibling) || target.firstChild); return style; }, + __lastHeadApplyNode: null, + + // insert a comment node as a styling position placeholder. + applyStylePlaceHolder: function(moniker) { + var placeHolder = document.createComment(' polymer element ' + + moniker + ' '); + var after = this.__lastHeadApplyNode ? + this.__lastHeadApplyNode.nextSibling : null; + var scope = document.head; + scope.insertBefore(placeHolder, after || scope.firstChild); + this.__lastHeadApplyNode = placeHolder; + return placeHolder; + }, + cssFromModules: function(moduleIds, warnIfNotFound) { var modules = moduleIds.trim().split(' '); var cssText = ''; diff --git a/src/standard/styling.html b/src/standard/styling.html index 55fc5a2f94..6e9cdaef64 100644 --- a/src/standard/styling.html +++ b/src/standard/styling.html @@ -42,6 +42,18 @@ } if (this._template) { this._styles = this._collectStyles(); + // under shady dom we always output a shimmed style (which may be + // empty) so that other dynamic stylesheets can always be placed + // after the element's main stylesheet. + // This helps ensure element styles are always in registration order. + if (this._styles.length && !nativeShadow) { + this._scopeStyle = styleUtil.applyStylePlaceHolder(this.is); + } + } + }, + + _prepShimStyles: function() { + if (this._template) { // calculate shimmed styles (we must always do this as it // stores shimmed style data in the css rules for later use) var cssText = styleTransformer.elementStyles(this); @@ -50,21 +62,18 @@ // do we really need to output static shimmed styles? // only if no custom properties are used since otherwise // styles are applied via property shimming. - var needsStatic = this._styles.length && - !this._needsStyleProperties(); - // under shady dom we always output a shimmed style (which may be - // empty) so that other dynamic stylesheets can always be placed - // after the element's main stylesheet. - // This helps ensure element styles are always in registration order. - if (needsStatic || !nativeShadow) { + if (!this._needsStyleProperties() && this._styles.length) { // NOTE: IE has css style ordering issues unless there's at least a // space in the stylesheet. - cssText = needsStatic ? cssText : ' '; var style = styleUtil.applyCss(cssText, this.is, - nativeShadow ? this._template.content : null); + nativeShadow ? this._template.content : null, this._scopeStyle); // keep track of style when in document scope (polyfill) so we can // attach property styles after it. if (!nativeShadow) { + // remove old scope style (comment node) when it's not needed. + if (this._scopeStyle) { + this._scopeStyle.parentNode.removeChild(this._scopeStyle); + } this._scopeStyle = style; } } diff --git a/test/unit/base.html b/test/unit/base.html index 9da6bda545..6ffb7ea53f 100644 --- a/test/unit/base.html +++ b/test/unit/base.html @@ -29,10 +29,10 @@ setup(function() { // Ensure a clean environment for each test. - /* global Base */ window.Base = Polymer.Base; window.Child = Object.create(Base); - Child._registerFeatures = function() {}; + Child._registerFeatures = function() { + }; Child._initFeatures = function() {}; Child._setAttributeToProperty = function() {}; Child._desugarBehaviors = function() {}; @@ -54,22 +54,6 @@ }); -suite('registerCallback', function() { - - test('calls registered() after registerFeatures()', function() { - var called = []; - Child._registerFeatures = function() { - called.push('1'); - }; - Child.registered = function() { - called.push('2'); - }; - assert.deepEqual(called, []); - Child.registerCallback(); - assert.includeMembers(called, ['1', '2']); - }); - -}); suite('createdCallback', function() { diff --git a/test/unit/notify-path.html b/test/unit/notify-path.html index e32853b187..6f6033f3b9 100644 --- a/test/unit/notify-path.html +++ b/test/unit/notify-path.html @@ -950,7 +950,7 @@ Polymer({ is: 'x-broken', observers: ['foo(missingParenthesis'] - }); + }).prototype.ensureRegistered(); } catch (e) { assert.equal(e.message, "Malformed observer expression 'foo(missingParenthesis'"); thrown = true;