From 31702ffab5816e25a5be2718d8bb5402d13893b4 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Mon, 14 Mar 2016 16:08:50 -0700 Subject: [PATCH] Lazy register features we can be deferred until first instance. This is an optimization which can speed up page load time when elements are registered but not needed at time of first paint/interaction --- lazy-register.html | 76 ++++++++++++++++++++++++++++++++++++++ polymer.html | 6 ++- src/lib/base.html | 22 ++++++++++- src/lib/style-util.html | 32 ++++++++++++---- src/standard/styling.html | 27 +++++++++----- test/unit/base.html | 20 +--------- test/unit/notify-path.html | 2 +- 7 files changed, 147 insertions(+), 38 deletions(-) create mode 100644 lazy-register.html 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;