Clean up unsandboxed extension loader

Remove a bit of global state hackery and prevent concurrent loads
This commit is contained in:
GarboMuffin committed 2022-10-22 16:03:05 -05:00
1 parent 1b226fc515
commit 326af1930f
2 files changed
+46 -31

No files matched your search

+25 -11
View File
@@ -92,8 +92,14 @@ class ExtensionManager {
this.pendingWorkers = [];
/**
* Set of loaded extension URLs/IDs (equivalent for built-in extensions).
* @type {Set.<string>}
* Map of worker ID to the URL where it was loaded from.
* @type {Array<string>}
*/
this.workerURLs = [];
/**
* Map of loaded extension URLs/IDs to service names.
* @type {Map.<string, string>}
* @private
*/
this._loadedExtensions = new Map();
@@ -185,13 +191,21 @@ class ExtensionManager {
this.loadingAsyncExtensions++;
if (this.workerMode === 'unsandboxed') {
return require('./tw-unsandboxed-extension-runner')
.load(
extensionURL,
this._registerInternalExtension.bind(this)
)
.then(() => {
this._finishedLoadingExtension();
const {load} = require('./tw-unsandboxed-extension-runner');
return load(extensionURL)
.then(extensionObjects => {
const fakeWorkerId = this.nextExtensionWorker++;
this.workerURLs[fakeWorkerId] = extensionURL;
for (const extensionObject of extensionObjects) {
const extensionInfo = extensionObject.getInfo();
const serviceName = `unsandboxed.${fakeWorkerId}.${extensionInfo.id}`;
dispatch.setServiceSync(serviceName, extensionObject);
dispatch.callSync('extensions', 'registerExtensionServiceSync', serviceName);
this._loadedExtensions.set(extensionInfo.id, serviceName);
}
this._finishedLoadingExtensionScript();
});
}
@@ -274,11 +288,11 @@ class ExtensionManager {
dispatch.call(serviceName, 'getInfo').then(info => {
this._loadedExtensions.set(info.id, serviceName);
this._registerExtensionInfo(serviceName, info);
this._finishedLoadingExtension();
this._finishedLoadingExtensionScript();
});
}
_finishedLoadingExtension () {
_finishedLoadingExtensionScript () {
this.loadingAsyncExtensions--;
if (this.loadingAsyncExtensions === 0) {
this.asyncExtensionsLoadedCallbacks.forEach(i => i());
@@ -1,37 +1,38 @@
const ArgumentType = require('../extension-support/argument-type');
const BlockType = require('../extension-support/block-type');
const TargetType = require('../extension-support/target-type');
const AsyncLimiter = require('../util/async-limiter');
// Custom extensions by nature rely on the single global Scratch object to register themselves
// To make this work, we'll have to track the most recent callback and just hope that we don't
// have two VMs on the same page trying to load unsandboxed extensions.
let mostRecentRegisterExtensionCallback = null;
const loadUnsandboxedExtension = extensionURL => new Promise((resolve, reject) => {
global.Scratch = global.Scratch || {};
global.Scratch.ArgumentType = ArgumentType;
global.Scratch.BlockType = BlockType;
global.Scratch.TargetType = TargetType;
global.Scratch.ArgumentType = ArgumentType;
const register = extensionObject => {
mostRecentRegisterExtensionCallback(extensionObject);
};
// 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 Scratch = {
ArgumentType,
BlockType,
TargetType,
extensions: {
const extensionObjects = [];
const register = extensionObject => {
extensionObjects.push(extensionObject);
resolve(extensionObjects);
};
global.Scratch.extensions = {
register
}
};
global.Scratch = Scratch;
const load = (extensionURL, registerExtensionCallback) => new Promise((resolve, reject) => {
mostRecentRegisterExtensionCallback = registerExtensionCallback;
};
const script = document.createElement('script');
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Error in unsandboxed script ${extensionURL}`));
script.src = extensionURL;
document.body.appendChild(script);
});
// Because loading unsandboxed extensions requires messing with global state (global.Scratch),
// only let one extension load at a time.
const limiter = new AsyncLimiter(loadUnsandboxedExtension, 1);
const load = extensionURL => limiter.do(extensionURL);
module.exports = {
load
};