Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge remote-tracking branch 'upstream/master' into asset-loading-error
  • Loading branch information
diarmidmackenzie committed Nov 18, 2022
commit c4b9f12372d790c8a0753c3c765c91e812f0539b
286 changes: 32 additions & 254 deletions src/core/a-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
var utils = require('../utils/');

var warn = utils.debug('core:a-node:warn');
var error = utils.debug('core:a-node:error');

var knownTags = {
'a-scene': true,
'a-assets': true,
'a-assets-items': true,
'a-cubemap': true,
'a-mixin': true,
'a-node': true,
'a-entity': true
};

function isNode (node) {
return node.tagName.toLowerCase() in knownTags || node.isNode;
}

var knownTags = {
'a-scene': true,
Expand Down Expand Up @@ -32,254 +47,11 @@ class ANode extends HTMLElement {
this.mixinEls = [];
}

attachedCallback: {
value: function () {
var mixins;
this.sceneEl = this.closestScene();

if (!this.sceneEl) {
warn('You are attempting to attach <' + this.tagName + '> outside of an A-Frame ' +
'scene. Append this element to `<a-scene>` instead.');
}

this.hasLoaded = false;
this.emit('nodeready', undefined, false);

if (!this.isMixin) {
mixins = this.getAttribute('mixin');
if (mixins) { this.updateMixins(mixins); }
}
},
writable: window.debug
},

/**
* Handle mixin.
*/
attributeChangedCallback: {
value: function (attr, oldVal, newVal) {
// Ignore if `<a-node>` code is just updating computed mixin in the DOM.
if (newVal === this.computedMixinStr) { return; }

if (attr === 'mixin' && !this.isMixin) {
this.updateMixins(newVal, oldVal);
}
}
},

/**
* Returns the first scene by traversing up the tree starting from and
* including receiver element.
*/
closestScene: {
value: function closest () {
var element = this;
while (element) {
if (element.isScene) { break; }
element = element.parentElement;
}
return element;
}
},

/**
* Returns first element matching a selector by traversing up the tree starting
* from and including receiver element.
*
* @param {string} selector - Selector of element to find.
*/
closest: {
value: function closest (selector) {
var matches = this.matches || this.mozMatchesSelector ||
this.msMatchesSelector || this.oMatchesSelector || this.webkitMatchesSelector;
var element = this;
while (element) {
if (matches.call(element, selector)) { break; }
element = element.parentElement;
}
return element;
}
},

detachedCallback: {
value: function () {
this.hasLoaded = false;
}
},

/**
* Wait for children to load, if any.
* Then emit `loaded` event and set `hasLoaded`.
*/
load: {
value: function (cb, childFilter) {
var children;
var childrenLoaded;
var self = this;

if (this.hasLoaded) { return; }

// Default to waiting for all nodes.
childFilter = childFilter || isNode;
// Wait for children to load (if any), then load.
children = this.getChildren();
childrenLoaded = children.filter(childFilter).map(function (child) {
return new Promise(function waitForLoaded (resolve, reject) {
if (child.hasLoaded) { return resolve(); }
child.addEventListener('loaded', resolve);
child.addEventListener('error', reject);
});
});

Promise.allSettled(childrenLoaded).then(function emitLoaded (results) {
results.forEach(function checkResultForError (result) {
if (result.status === 'rejected') {
// An "error" event has already been fired by THREE.js loader,
// so we don't need to fire another one.
// A warning explaining the consequences of the error is sufficient.
warn('Rendering scene with errors on node: ', result.reason.target);
}
});

self.hasLoaded = true;
if (cb) { cb(); }
self.emit('loaded', undefined, false);
});
},
writable: true
},

getChildren: {
value: function () {
return Array.prototype.slice.call(this.children, 0);
}
},

/**
* Unregister old mixins and listeners.
* Register new mixins and listeners.
* Registering means to update `this.mixinEls` with listeners.
*/
updateMixins: {
value: (function () {
var newMixinIdArray = [];
var oldMixinIdArray = [];
var mixinIds = {};

return function (newMixins, oldMixins) {
var i;
var newMixinIds;
var oldMixinIds;

newMixinIdArray.length = 0;
oldMixinIdArray.length = 0;
newMixinIds = newMixins ? utils.split(newMixins.trim(), /\s+/) : newMixinIdArray;
oldMixinIds = oldMixins ? utils.split(oldMixins.trim(), /\s+/) : oldMixinIdArray;

mixinIds.newMixinIds = newMixinIds;
mixinIds.oldMixinIds = oldMixinIds;

// Unregister old mixins.
for (i = 0; i < oldMixinIds.length; i++) {
if (newMixinIds.indexOf(oldMixinIds[i]) === -1) {
this.unregisterMixin(oldMixinIds[i]);
}
}

// Register new mixins.
this.computedMixinStr = '';
this.mixinEls.length = 0;
for (i = 0; i < newMixinIds.length; i++) {
this.registerMixin(document.getElementById(newMixinIds[i]));
}

// Update DOM. Keep track of `computedMixinStr` to not recurse back here after
// update.
if (this.computedMixinStr) {
this.computedMixinStr = this.computedMixinStr.trim();
window.HTMLElement.prototype.setAttribute.call(this, 'mixin',
this.computedMixinStr);
}

return mixinIds;
};
})()
},

/**
* From mixin ID, add mixin element to `mixinEls`.
*
* @param {Element} mixinEl
*/
registerMixin: {
value: function (mixinEl) {
var compositedMixinIds;
var i;
var mixin;

if (!mixinEl) { return; }

// Register composited mixins (if mixin has mixins).
mixin = mixinEl.getAttribute('mixin');
if (mixin) {
compositedMixinIds = utils.split(mixin.trim(), /\s+/);
for (i = 0; i < compositedMixinIds.length; i++) {
this.registerMixin(document.getElementById(compositedMixinIds[i]));
}
}

// Register mixin.
this.computedMixinStr = this.computedMixinStr + ' ' + mixinEl.id;
this.mixinEls.push(mixinEl);
}
},

setAttribute: {
value: function (attr, newValue) {
if (attr === 'mixin') { this.updateMixins(newValue); }
window.HTMLElement.prototype.setAttribute.call(this, attr, newValue);
}
},

unregisterMixin: {
value: function (mixinId) {
var i;
var mixinEls = this.mixinEls;
var mixinEl;
for (i = 0; i < mixinEls.length; ++i) {
mixinEl = mixinEls[i];
if (mixinId === mixinEl.id) {
mixinEls.splice(i, 1);
break;
}
}
}
},

/**
* Emit a DOM event.
*
* @param {string} name - Name of event.
* @param {object} [detail={}] - Custom data to pass as `detail` to the event.
* @param {boolean} [bubbles=true] - Whether the event should bubble.
* @param {object} [extraData] - Extra data to pass to the event, if any.
*/
emit: {
value: (function () {
var data = {};

return function (name, detail, bubbles, extraData) {
if (bubbles === undefined) { bubbles = true; }
data.bubbles = !!bubbles;
data.detail = detail;

// If extra data is present, we need to create a new object.
if (extraData) { data = utils.extend({}, extraData, data); }

this.dispatchEvent(new CustomEvent(name, data));
};
})(),
writable: window.debug
connectedCallback () {
// Defer if DOM is not ready.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', this.connectedCallback.bind(this));
return;
}
ANode.prototype.doConnectedCallback.call(this);
}
Expand Down Expand Up @@ -365,20 +137,26 @@ class ANode extends HTMLElement {
// Wait for children to load (if any), then load.
children = this.getChildren();
childrenLoaded = children.filter(childFilter).map(function (child) {
return new Promise(function waitForLoaded (resolve) {
return new Promise(function waitForLoaded (resolve, reject) {
if (child.hasLoaded) { return resolve(); }
child.addEventListener('loaded', resolve);
child.addEventListener('error', reject);
});
});

Promise.all(childrenLoaded).then(function emitLoaded () {
Promise.allSettled(childrenLoaded).then(function emitLoaded (results) {
results.forEach(function checkResultForError (result) {
if (result.status === 'rejected') {
// An "error" event has already been fired by THREE.js loader,
// so we don't need to fire another one.
// A warning explaining the consequences of the error is sufficient.
warn('Rendering scene with errors on node: ', result.reason.target);
}
});

self.hasLoaded = true;
if (cb) { cb(); }
self.setupMutationObserver();

self.emit('loaded', undefined, false);
}).catch(function (err) {
error('Failure loading node: ', err);
});
}

Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.