diff --git a/lib/mixins/element-mixin.html b/lib/mixins/element-mixin.html index 2e0e8c86ac..30af1ec1a7 100644 --- a/lib/mixins/element-mixin.html +++ b/lib/mixins/element-mixin.html @@ -549,6 +549,7 @@ */ _initializeProperties() { Polymer.telemetry.instanceCount++; + hostStack.registerHost(this); this.constructor.finalize(); const importPath = this.constructor.importPath; // note: finalize template when we have access to `localName` to @@ -598,7 +599,9 @@ if (window.ShadyCSS) { window.ShadyCSS.styleElement(this); } - this._flushProperties(); + if (!this.__dataInitialized) { + this._flushProperties(); + } } /** @@ -616,7 +619,9 @@ */ ready() { if (this._template) { + hostStack.beginHosting(this); this.root = this._stampTemplate(this._template); + hostStack.endHosting(this); } super.ready(); } @@ -745,6 +750,53 @@ return PolymerElement; }); + /** + * Helper api for enqueing client dom created by a host element. + * + * By default elements are flushed via `_flushProperties` when + * `connectedCallback` is called. Elements attach their client dom to + * themselves at `ready` time which results from this first flush. + * This provides an ordering guarantee that the client dom an element + * creates is flushed before the element itself (i.e. client `ready` + * fires before host `ready`). + * + * However, if `_flushProperties` is called *before* an element is connected, + * as for example `Templatize` does, this ordering guarantee cannot be + * satisfied because no elements are connected. (Note: Bound elements that + * receive data do become enqueued clients and are properly ordered but + * unbound elements are not.) + * + * To maintain the desired "client before host" ordering guarantee for this + * case we rely on the "host stack. Client nodes registers themselves with + * the creating host element when created. This ensures that all client dom + * is readied in the proper order, maintaining the desired guarantee. + * + * @private + */ + let hostStack = { + + stack: [], + + registerHost(inst) { + if (this.stack.length) { + let host = this.stack[this.stack.length-1]; + host._enqueueClient(inst); + } + }, + + beginHosting(inst) { + this.stack.push(inst); + }, + + endHosting(inst) { + let stackLen = this.stack.length; + if (stackLen && this.stack[stackLen-1] == inst) { + this.stack.pop(); + } + } + + } + /** * Provides basic tracking of element definitions (registrations) and * instance counts. diff --git a/test/runner.html b/test/runner.html index 86c18d4d2c..9a9f1b003a 100644 --- a/test/runner.html +++ b/test/runner.html @@ -46,6 +46,7 @@ 'unit/case-map.html', 'unit/configure.html', 'unit/ready-attached-order.html', + 'unit/ready-attached-order-class.html', 'unit/attributes.html', 'unit/async.html', 'unit/behaviors.html', diff --git a/test/smoke/ordering-test.html b/test/smoke/ordering-test.html index c46d9a0079..5400425ed8 100644 --- a/test/smoke/ordering-test.html +++ b/test/smoke/ordering-test.html @@ -40,8 +40,8 @@ } connectedCallback() { console.group(this.localName, 'connected'); - super.connectedCallback(); console.warn(this.localName, 'connected (user)', this.shadowRoot); + super.connectedCallback(); console.groupEnd(this.localName, 'connected'); } _flushProperties() { @@ -79,8 +79,8 @@ } connectedCallback() { console.group(this.localName, 'connected'); - super.connectedCallback(); console.warn(this.localName, 'connected (user)', this.shadowRoot); + super.connectedCallback(); console.groupEnd(this.localName, 'connected'); } _flushProperties() { @@ -113,8 +113,8 @@ } connectedCallback() { console.group(this.localName, 'connected'); - super.connectedCallback(); console.warn(this.localName, 'connected (user)', this.shadowRoot); + super.connectedCallback(); console.groupEnd(this.localName, 'connected'); } _flushProperties() { diff --git a/test/unit/ready-attached-order-class.html b/test/unit/ready-attached-order-class.html new file mode 100644 index 0000000000..b675de8e9d --- /dev/null +++ b/test/unit/ready-attached-order-class.html @@ -0,0 +1,277 @@ + + + +
+ + + + + + + + + +