diff --git a/src/core/a-entity.js b/src/core/a-entity.js index 6b39e4e0562..19ac59e0020 100644 --- a/src/core/a-entity.js +++ b/src/core/a-entity.js @@ -369,9 +369,13 @@ class AEntity extends ANode { // Wait for component to initialize. if (!component.initialized) { + component.pendingRemoval = true; this.addEventListener('componentinitialized', function tryRemoveLater (evt) { if (evt.detail.name !== name) { return; } - this.removeComponent(name, destroy); + if (component.pendingRemoval) { + this.removeComponent(name, destroy); + component.pendingRemoval = false; + } this.removeEventListener('componentinitialized', tryRemoveLater); }); return; @@ -462,8 +466,16 @@ class AEntity extends ANode { this.removeComponent(attr, true); return; } - // Component already initialized. Update component. + // Component initialization already started. Update component. component.updateProperties(attrValue, clobber); + + // Component has been initialized, but is pending removal + if (component.pendingRemoval) { + // This new update should cancel any pending removal, and reinstate the attribute + // (which will have been removed, synchronously, when the component removal was initiated) + window.HTMLElement.prototype.setAttribute.call(this, attr, ''); + component.pendingRemoval = false; + } return; } diff --git a/tests/core/a-entity.test.js b/tests/core/a-entity.test.js index deaa5585c63..c8c517faf04 100644 --- a/tests/core/a-entity.test.js +++ b/tests/core/a-entity.test.js @@ -1693,3 +1693,54 @@ suite('a-entity component dependency management', function () { }); }); }); + +suite('a-entity attribute operations prior to adding to DOM', function () { + var scene; + setup(function (done) { + this.TestComponent = registerComponent('test', TestComponent); + scene = document.createElement('a-scene'); + document.body.appendChild(scene); + scene.addEventListener('loaded', () => { + done(); + }); + }); + + test('Add component before element added to DOM', function () { + var TestComponent = this.TestComponent.prototype; + this.sinon.spy(TestComponent, 'init'); + const el = document.createElement('a-entity'); + el.setAttribute('test', ''); + sinon.assert.notCalled(TestComponent.init); + el.addEventListener('loaded', function () { + sinon.assert.called(TestComponent.init); + }); + scene.appendChild(el); + }); + + test('Add and remove component before element added to DOM', function () { + var TestComponent = this.TestComponent.prototype; + this.sinon.spy(TestComponent, 'init'); + const el = document.createElement('a-entity'); + el.setAttribute('test', ''); + sinon.assert.notCalled(TestComponent.init); + el.removeAttribute('test'); + el.addEventListener('loaded', function () { + sinon.assert.notCalled(TestComponent.init); + }); + scene.appendChild(el); + }); + + test('Add, remove & re-add component before element added to DOM', function () { + var TestComponent = this.TestComponent.prototype; + this.sinon.spy(TestComponent, 'init'); + const el = document.createElement('a-entity'); + el.setAttribute('test', ''); + sinon.assert.notCalled(TestComponent.init); + el.removeAttribute('test'); + el.setAttribute('test', ''); + el.addEventListener('loaded', function () { + sinon.assert.called(TestComponent.init); + }); + scene.appendChild(el); + }); +});