diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 7daa4c4e40..016d3d6510 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -1061,7 +1061,25 @@ class Runtime extends EventEmitter { * @param {ExtensionMetadata} extensionInfo - information about the extension (id, blocks, etc.) * @private */ - _registerExtensionPrimitives (extensionInfo) { + async _registerExtensionPrimitives (extensionInfo) { + + // If the extension requires other extensions, load them first. + if (Array.isArray(extensionInfo.requiredExtensions)) { + for (const extensionId of extensionInfo.requiredExtensions) { + if ( + this.extensionManager.isCoreExtension(extensionId) || + this.extensionManager.isBuiltinExtension(extensionId) || + await this.extensionManager.securityManager.canLoadExtensionFromProject(extensionId) + ) { + this.extensionManager.loadExtensionURL(extensionId); + } else { + console.warn( + `Failed to load required extension: ${extensionId} for extension: ${extensionInfo.id}` + ); + } + } + } + const categoryInfo = { id: extensionInfo.id, name: maybeFormatMessage(extensionInfo.name), diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js index edcea24ecf..fba1d3a434 100644 --- a/src/extension-support/extension-manager.js +++ b/src/extension-support/extension-manager.js @@ -29,6 +29,20 @@ const defaultBuiltinExtensions = { tw: () => require('../extensions/tw') }; +const coreExtensions = [ + 'motion', + 'looks', + 'sound', + 'events', + 'control', + 'sensing', + 'operators', + 'data', + 'json', + 'procedures', + 'comments' +]; + /** * @typedef {object} ArgumentInfo - Information about an extension block argument * @property {ArgumentType} type - the type of value this argument can take @@ -126,6 +140,7 @@ class ExtensionManager { this.asyncExtensionsLoadedCallbacks = []; this.builtinExtensions = Object.assign({}, defaultBuiltinExtensions); + this.coreExtensions = coreExtensions; dispatch.setService('extensions', createExtensionService(this)).catch(e => { log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`); @@ -153,6 +168,16 @@ class ExtensionManager { return Object.prototype.hasOwnProperty.call(this.builtinExtensions, extensionId); } + /** + * Determine whether an extension with a given ID is registered as a core extension in the VM, such as motion. + * Note that custom extensions or extensions that don't load on startup will return false here. + * @param {string} extensionId + * @returns {boolean} + */ + isCoreExtension (extensionId) { + return this.coreExtensions.includes(extensionId); + } + /** * Synchronously load an internal extension (core or non-core) by ID. This call will * fail if the provided id is not does not match an internal extension. @@ -207,8 +232,8 @@ class ExtensionManager { return; } - if (this.isExtensionURLLoaded(extensionURL)) { - // Extension is already loaded. + if (this.isExtensionURLLoaded(extensionURL) || this.isCoreExtension(extensionURL)) { + // Extension is already loaded or is a core extension. return; }