From abdae22a199e2743f3285d8a9c416d65bdb8d1a7 Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sat, 29 Oct 2022 22:49:10 -0500 Subject: [PATCH] Disable future calls to register() when extension has finished loading --- .../tw-unsandboxed-extension-runner.js | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/extension-support/tw-unsandboxed-extension-runner.js b/src/extension-support/tw-unsandboxed-extension-runner.js index 92a963a8..5d0e0880 100644 --- a/src/extension-support/tw-unsandboxed-extension-runner.js +++ b/src/extension-support/tw-unsandboxed-extension-runner.js @@ -3,7 +3,12 @@ const BlockType = require('../extension-support/block-type'); const TargetType = require('../extension-support/target-type'); const AsyncLimiter = require('../util/async-limiter'); -const loadUnsandboxedExtension = (extensionURL, vm) => new Promise((resolve, reject) => { +/** + * Sets up the global.Scratch API for an unsandboxed extension. + * @param {VirtualMachine} vm + * @returns {Promise} Resolves with a list of extension objects when Scratch.extensions.register is called. + */ +const createUnsandboxedExtensionAPI = vm => new Promise(resolve => { global.Scratch = global.Scratch || {}; global.Scratch.vm = vm; global.Scratch.renderer = vm.runtime.renderer; @@ -12,9 +17,6 @@ const loadUnsandboxedExtension = (extensionURL, vm) => new Promise((resolve, rej global.Scratch.TargetType = TargetType; global.Scratch.ArgumentType = ArgumentType; - // When we load the script, wait for it to eventually call Scratch.extensions.register - // before resolving. Note that extension scripts may register multiple extensions. - const extensionObjects = []; const register = extensionObject => { extensionObjects.push(extensionObject); @@ -24,11 +26,35 @@ const loadUnsandboxedExtension = (extensionURL, vm) => new Promise((resolve, rej unsandboxed: true, register }; +}); + +/** + * Disable the existing global.Scratch unsandboxed extension APIs. + * This helps debug poorly designed extensions. + */ +const teardownUnsandboxedExtensionAPI = () => { + // We can assume global.Scratch already exists. + global.Scratch.extensions.register = () => { + throw new Error('Too late to register new extensions.'); + }; +}; + +/** + * Load an unsandboxed extension from an arbitrary URL. This is dangerous. + * @param {string} extensionURL + * @param {Virtualmachine} vm + * @returns {Promise} Resolves with a list of extension objects if the extension was loaded successfully. + */ +const loadUnsandboxedExtension = (extensionURL, vm) => new Promise((resolve, reject) => { + createUnsandboxedExtensionAPI(vm).then(resolve); const script = document.createElement('script'); script.onerror = () => reject(new Error(`Error in unsandboxed script ${extensionURL}`)); script.src = extensionURL; document.body.appendChild(script); +}).then(objects => { + teardownUnsandboxedExtensionAPI(); + return objects; }); // Because loading unsandboxed extensions requires messing with global state (global.Scratch), @@ -37,5 +63,6 @@ const limiter = new AsyncLimiter(loadUnsandboxedExtension, 1); const load = (extensionURL, vm) => limiter.do(extensionURL, vm); module.exports = { + createUnsandboxedExtensionAPI, load };