Disable future calls to register() when extension has finished loading

This commit is contained in:
GarboMuffin committed 2022-10-29 22:49:10 -05:00
1 parent 1549d40868
commit abdae22a19
1 file changed
+31 -4
@@ -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<object[]>} 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<object[]>} 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
};