diff --git a/src/core/a-entity.js b/src/core/a-entity.js index 014bee38a2f..50ace41e9db 100644 --- a/src/core/a-entity.js +++ b/src/core/a-entity.js @@ -404,7 +404,7 @@ class AEntity extends ANode { var name; var componentsToUpdate = this.componentsToUpdate; - if (!this.hasLoaded && !this.sceneEl) { return; } + if (!this.hasLoaded && !this.isLoading) { return; } // Gather mixin-defined components. for (i = 0; i < this.mixinEls.length; i++) { @@ -511,7 +511,7 @@ class AEntity extends ANode { var key; // Already playing. - if (this.isPlaying || (!this.hasLoaded && !this.sceneEl)) { return; } + if (this.isPlaying || (!this.hasLoaded && !this.isLoading)) { return; } this.isPlaying = true; // Wake up all components. @@ -568,7 +568,7 @@ class AEntity extends ANode { /** * When mixins updated, trigger init or optimized-update of relevant components. */ - mixinUpdate (newMixins, oldMixins) { + mixinUpdate (newMixins, oldMixins, deferred) { var componentsUpdated = AEntity.componentsUpdated; var component; @@ -577,14 +577,15 @@ class AEntity extends ANode { var i; var self = this; + if (!deferred) { oldMixins = oldMixins || this.getAttribute('mixin'); } + if (!this.hasLoaded) { - this.addEventListener('loaded', function () { - self.mixinUpdate(newMixins, oldMixins); + this.addEventListener('loaded-private', function () { + self.mixinUpdate(newMixins, oldMixins, true); }, ONCE); return; } - oldMixins = oldMixins || this.getAttribute('mixin'); mixinIds = this.updateMixins(newMixins, oldMixins); // Loop over current mixins. diff --git a/src/core/a-node.js b/src/core/a-node.js index eee93e8026e..3aa265f5526 100644 --- a/src/core/a-node.js +++ b/src/core/a-node.js @@ -145,9 +145,14 @@ class ANode extends HTMLElement { } }); + self.isLoading = true; self.setupMutationObserver(); if (cb) { cb(); } + self.isLoading = false; self.hasLoaded = true; + // loaded-private is an event analog to loaded that gives A-Frame an opportunity to manage internal + // affairs before the publicly loaded event fires and corresponding handlers executed. + self.emit('loaded-private', undefined, false); self.emit('loaded', undefined, false); }); } @@ -225,6 +230,10 @@ class ANode extends HTMLElement { this.computedMixinStr); } + if (newMixinIds.length === 0) { + window.HTMLElement.prototype.removeAttribute.call(this, 'mixin'); + } + return mixinIds; } diff --git a/src/core/component.js b/src/core/component.js index c95ce4801f2..1e49cc664b4 100644 --- a/src/core/component.js +++ b/src/core/component.js @@ -278,7 +278,7 @@ Component.prototype = { // Just cache the attribute if the entity has not loaded // Components are not initialized until the entity has loaded - if (!el.hasLoaded && !el.sceneEl) { + if (!el.hasLoaded && !el.isLoading) { this.updateCachedAttrValue(attrValue); return; } diff --git a/tests/core/a-entity.test.js b/tests/core/a-entity.test.js index db1e688ad4d..0bb48c27162 100644 --- a/tests/core/a-entity.test.js +++ b/tests/core/a-entity.test.js @@ -142,6 +142,26 @@ suite('a-entity', function () { parentEl.appendChild(el2); }); + test('update method is called only once', function (done) { + var triggerEl = document.createElement('a-entity'); + + registerComponent('trigger', { + init: function () { + var childEl = document.createElement('a-entity'); + childEl.setAttributeNS(null, 'visible', 'false'); + childEl.addEventListener('loaded', function () { + sinon.assert.calledOnce(updateSpy); + done(); + }); + this.el.sceneEl.appendChild(childEl); + childEl.setAttribute('visible', true); + } + }); + var updateSpy = sinon.spy(components.visible.Component.prototype, 'update'); + triggerEl.setAttribute('trigger', ''); + el.sceneEl.appendChild(triggerEl); + }); + suite('attachedCallback', function () { test('initializes 3D object', function (done) { elFactory().then(el => { diff --git a/tests/core/a-mixin.test.js b/tests/core/a-mixin.test.js index d9139167cda..2d3343e0319 100644 --- a/tests/core/a-mixin.test.js +++ b/tests/core/a-mixin.test.js @@ -1,5 +1,6 @@ /* global assert, setup, suite, test */ var helpers = require('../helpers'); +var registerComponent = require('index').registerComponent; suite('a-mixin', function () { var assetsEl; @@ -79,6 +80,29 @@ suite('a-mixin', function () { }); }); + test('allows mixin to be removed during component init', function (done) { + var mixinEl = document.createElement('a-mixin'); + var testEl = document.createElement('a-entity'); + mixinEl.setAttribute('id', 'red'); + mixinEl.setAttribute('material', 'color: red'); + assetsEl.appendChild(mixinEl); + + registerComponent('delete-mixin', { + init: function () { + this.el.removeAttribute('mixin'); + } + }); + + testEl.setAttribute('mixin', 'red'); + testEl.setAttribute('delete-mixin', ''); + testEl.addEventListener('loaded', function () { + assert.equal(testEl.getAttribute('mixin'), null); + assert.equal(testEl.getAttribute('material'), ''); + done(); + }); + el.sceneEl.appendChild(testEl); + }); + test('allows mixin to define mixin pre-attach', done => { var mixinEl1; var mixinEl2; diff --git a/tests/extras/primitives/primitives.test.js b/tests/extras/primitives/primitives.test.js index cb6c31de22e..97aeffe596d 100644 --- a/tests/extras/primitives/primitives.test.js +++ b/tests/extras/primitives/primitives.test.js @@ -1,6 +1,7 @@ /* global AFRAME, assert, suite, test, THREE */ var helpers = require('../../helpers'); var registerPrimitive = require('extras/primitives/primitives').registerPrimitive; +var registerComponent = require('index').registerComponent; var primitives = require('extras/primitives/primitives').primitives; var primitiveId = 0; @@ -339,6 +340,31 @@ suite('registerPrimitive (using innerHTML)', function () { }); }); + test('handles primitive created and updated in init method', function (done) { + var el = helpers.entityFactory(); + var tagName = 'a-test-' + primitiveId++; + registerPrimitive(tagName, { + defaultComponents: { + scale: {x: 0.2, y: 0.2, z: 0.2} + } + }); + + registerComponent('create-and-update-primitive', { + init: function () { + var primitiveEl = document.createElement(tagName); + this.el.appendChild(primitiveEl); + primitiveEl.setAttribute('scale', {y: 0.2}); + primitiveEl.addEventListener('loaded', function () { + assert.equal(primitiveEl.object3D.scale.x, 0.2); + done(); + }); + } + }); + + el.setAttribute('create-and-update-primitive', ''); + el.sceneEl.appendChild(el); + }); + test('resolves mapping collisions', function (done) { primitiveFactory({ defaultComponents: {