Skip to content

Commit

Permalink
✨ feat: convert sideload blocks to procedures_call to keep compatibility
Browse files Browse the repository at this point in the history
Signed-off-by: SimonShiki <[email protected]>
  • Loading branch information
SimonShiki committed Oct 31, 2023
1 parent f788efa commit 3ad0f60
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 18 deletions.
2 changes: 2 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference path="node_modules/@turbowarp/types/index.d.ts" />
// <reference path="./loader/loader" />
// <reference path="./loader/make-ctx" />
// <reference path="./util/settings" />

declare interface Window {
Blockly?: Partial<ScratchBlocks>;
Expand All @@ -9,6 +10,7 @@ declare interface Window {
vm?: VM;
blockly?: ScratchBlocks | null;
loader?: ChibiLoader;
settings: Settings;
registeredExtension: Record<
string,
{
Expand Down
83 changes: 82 additions & 1 deletion src/injector/inject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference path="../global.d.ts" />
import { log, warn, error } from '../util/log';
import { settings } from '../util/settings';
import { ChibiLoader } from '../loader/loader';
import openFrontend from '../frontend';
import type VM from 'scratch-vm';
Expand Down Expand Up @@ -27,6 +28,21 @@ interface ChibiCompatibleVM extends VM {

const MAX_LISTENING_MS = 30 * 1000;

/**
* Get sanitized non-core extension ID for a given sb3 opcode.
* Note that this should never return a URL. If in the future the SB3 loader supports loading extensions by URL, this
* ID should be used to (for example) look up the extension's full URL from a table in the SB3's JSON.
* @param {!string} opcode The opcode to examine for extension.
* @return {?string} The extension ID, if it exists and is not a core extension.
*/
function getExtensionIdForOpcode (opcode: string) {
// Allowed ID characters are those matching the regular expression [\w-]: A-Z, a-z, 0-9, and hyphen ("-").
const index = opcode.indexOf('_');
const forbiddenSymbols = /[^\w-]/g;
const prefix = opcode.substring(0, index).replace(forbiddenSymbols, '-');
if (prefix !== '') return prefix;
}

/**
* Get Blockly instance.
* @param vm Virtual machine instance. For some reasons we cannot use VM here.
Expand Down Expand Up @@ -76,6 +92,7 @@ export function trap (open: typeof window.open): Promise<void> {
// @ts-expect-error defined in webpack define plugin
version: __CHIBI_VERSION__,
registeredExtension: {},
settings: settings,
openFrontend: openFrontend.bind(null, open)
};

Expand Down Expand Up @@ -182,9 +199,47 @@ export function inject (vm: ChibiCompatibleVM) {
// @ts-expect-error internal hack
const json = originalToJSONFunc.call(this, optTargetId, ...args);
const obj = JSON.parse(json);
const [urls, envs] = window.chibi.loader.getLoadedInfo();

const urls: Record<string, string> = {};
const envs: Record<string, string> = {};
const sideloadIds: string[] = [];
for (const [extId, ext] of window.chibi.loader.loadedScratchExtension.entries()) {
urls[extId] = ext.url;
envs[extId] = ext.env;
sideloadIds.push(extId);
}
obj.extensionURLs = Object.assign({}, obj.extensionURLs, urls);
obj.extensionEnvs = Object.assign({}, obj.extensionEnvs, envs);

if (window.chibi.settings.convertProcCall) {
for (const target of obj.targets) {
for (const blockId in target.blocks) {
const block = target.blocks[blockId];
if (!block.opcode) continue;
const extensionId = getExtensionIdForOpcode(block.opcode);
if (!extensionId) continue;
if (sideloadIds.includes(extensionId)) {
if (!('mutation' in block)) block.mutation = {};
block.mutation.proccode = `[📎 Sideload] ${block.opcode}`;
block.mutation.children = [];
block.mutation.tagName = 'mutation';

block.opcode = 'procedures_call';
}
}
}
for (const i in obj.monitors) {
const monitor = obj.monitors[i];
if (!monitor.opcode) continue;
const extensionId = getExtensionIdForOpcode(monitor.opcode);
if (!extensionId) continue;
if (sideloadIds.includes(extensionId)) {
if (!('sideloadMonitors' in obj)) obj.sideloadMonitors = [];
obj.sideloadMonitors.push(monitor);
obj.monitors.splice(i, 1);
}
}
}
return JSON.stringify(obj);
};

Expand All @@ -200,6 +255,32 @@ export function inject (vm: ChibiCompatibleVM) {
: 'sandboxed'
};
}
for (const target of projectJSON.targets) {
for (const blockId in target.blocks) {
const block = target.blocks[blockId];
if (block.opcode === 'procedures_call' && 'mutation' in block) {
if (!block.mutation.proccode.trim().startsWith('[📎 Sideload] ')) {
continue;
}
const originalOpcode = block.mutation.proccode.trim().substring(14);
const extensionId = getExtensionIdForOpcode(originalOpcode);
if (!extensionId) {
warn(`find a sideload block with an invalid id: ${originalOpcode}, ignored.`);
continue;
}
if (!(extensionId in window.chibi.registeredExtension)) {
warn(`find a sideload block with unregistered extension: ${extensionId}, ignored.`);
continue;
}
block.opcode = originalOpcode;
delete block.mutation;
}
}
}
if ('sideloadMonitors' in projectJSON) {
projectJSON.monitors.push(...projectJSON.sideloadMonitors);
delete projectJSON.sideloadMonitors;
}
}
// @ts-expect-error internal hack
return originalDrserializeFunc.call(this, projectJSON, ...args);
Expand Down
23 changes: 6 additions & 17 deletions src/loader/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,12 @@ class ChibiLoader {
const originalScript = await response.text();
const closureFunc = new Function('Scratch', originalScript);
const ctx = makeCtx(this.vm);
// Provide compatibility support for some extensions
// Nevertheless, it is a very bad behavior for Chibi to directly read the Scratch
// object on the window, because the extension should not depend on unknown external
// environment.
/*
* Provide compatibility support for some extensions
* Nevertheless, it is a very bad behavior for Chibi to directly read the Scratch
* object on the window, because the extension should not depend on unknown external
* environment.
*/
if (!('Scratch' in window)) {
window.Scratch = ctx;
}
Expand Down Expand Up @@ -144,19 +146,6 @@ class ChibiLoader {
return info;
}

/**
* Get all sideloaded extension infos.
*/
getLoadedInfo () {
const extensionURLs: Record<string, string> = {};
const extensionEnv: Record<string, string> = {};
for (const [extId, ext] of this.loadedScratchExtension.entries()) {
extensionURLs[extId] = ext.url;
extensionEnv[extId] = ext.env;
}
return [extensionURLs, extensionEnv];
}

getIdByUrl (url: string) {
for (const [extId, ext] of this.loadedScratchExtension.entries()) {
if (ext.url === url) {
Expand Down

0 comments on commit 3ad0f60

Please sign in to comment.