From 28f12ca9fab51286354552518b532f4e94daf53c Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 21 Jan 2020 13:56:53 -0800 Subject: [PATCH] Adds support for imperatively created elements to `legacyNoObservedAttributes` Using this flag previously read attributes in the element constructor (in addition to patching setAttribute). This covers use cases except imperative or parser created custom elements (not upgraded). In that case the element will not have attributes in the constructor. This is addressed here by checking if the element has a parentNode (which will also not be true in these cases) and if so, records attributes at connected time. This is not always done to avoid having to filter out changes made by bindings. --- lib/legacy/legacy-element-mixin.js | 24 +++++++++++++++++++----- test/unit/legacy-noattributes.html | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/lib/legacy/legacy-element-mixin.js b/lib/legacy/legacy-element-mixin.js index 7c6c04d6a9..d84a5eb4a2 100644 --- a/lib/legacy/legacy-element-mixin.js +++ b/lib/legacy/legacy-element-mixin.js @@ -101,6 +101,8 @@ export const LegacyElementMixin = dedupingMixin((base) => { // NOTE: Inlined for perf from version of DisableUpgradeMixin. /** @type {boolean|undefined} */ this.__isUpgradeDisabled; + /** @type {boolean|undefined} */ + this.__needsAttributesAtConnected; } /** @@ -196,6 +198,9 @@ export const LegacyElementMixin = dedupingMixin((base) => { * @override */ connectedCallback() { + if (this.__needsAttributesAtConnected) { + this._takeAttributes(); + } // NOTE: Inlined for perf from version of DisableUpgradeMixin. if (!this.__isUpgradeDisabled) { super.connectedCallback(); @@ -301,11 +306,12 @@ export const LegacyElementMixin = dedupingMixin((base) => { this.root = /** @type {HTMLElement} */(this); this.created(); // Pull all attribute values 1x if `legacyNoObservedAttributes` is set. - if (legacyNoObservedAttributes && this.hasAttributes()) { - const a = this.attributes; - for (let i=0, l=a.length; i < l; i++) { - const attr = a[i]; - this.__attributeReaction(attr.name, null, attr.value); + if (legacyNoObservedAttributes) { + if (this.hasAttributes()) { + this._takeAttributes(); + // Element created from scratch or parser generated + } else if (!this.parentNode) { + this.__needsAttributesAtConnected = true; } } // Ensure listeners are applied immediately so that they are @@ -316,6 +322,14 @@ export const LegacyElementMixin = dedupingMixin((base) => { } } + _takeAttributes() { + const a = this.attributes; + for (let i=0, l=a.length; i < l; i++) { + const attr = a[i]; + this.__attributeReaction(attr.name, null, attr.value); + } + } + /** * Called automatically when an element is initializing. * Users may override this method to perform class registration time diff --git a/test/unit/legacy-noattributes.html b/test/unit/legacy-noattributes.html index 91b45662f3..66799153a0 100644 --- a/test/unit/legacy-noattributes.html +++ b/test/unit/legacy-noattributes.html @@ -108,6 +108,27 @@ assert.equal(el.camelCase, 'camelCase'); }); + test('imperative creation', () => { + el = document.createElement('x-attrs'); + // Create attributes in an unpatched way. + const a = document.createAttribute('foo'); + a.value = 'foo'; + el.setAttributeNode(a); + const b = document.createAttribute('bar'); + b.value = 'bar'; + el.setAttributeNode(b); + const c = document.createAttribute('camel-case'); + c.value = 'camelCase'; + el.setAttributeNode(c); + document.body.appendChild(el); + assert.equal(el.foo, 'foo'); + assert.equal(el.$.child1.getAttribute('foo'), 'x-attrs.foo'); + assert.equal(el.$.child1.foo, "x-attrs.foo"); + assert.equal(el.$.child1.$.content.textContent, 'x-attrs.foo'); + assert.equal(el.camelCase, 'camelCase'); + document.body.removeChild(el); + }); + test('created called before attributeChanged', () => { assert.isTrue(el.wasCreatedInAttributeChanged); });