Unsandboxed extensions
Unsandboxed extensions run as plain <script> tags in the main window rather than in a sandbox. They have access to a lot of new powers and responsibilities that we will discuss below.
URL restrictions
To protect users from malicious extensions, extensions loaded from URLs will only run unsandboxed if their URL begins with one of these exactly:
- `https://about.gitlab.com/stages-devops-lifecycle/pages/
- `https://about.gitlab.com/stages-devops-lifecycle/pages/
As you don't have control over extensions.turbowarp.org, you will have to use the latter option. For this, configure your local HTTP server to run on port 8000 instead of what you've been using so far.
When manually loading an extension from a file or JavaScript source code, there is an option to load the extension without the sandbox. This option to force an extension to run unsandboxed does not exist when using URLs due to security concerns.
Syntax
The syntax for unsandboxed extensions is very familiar but has some differences. Technically, if you just copy and paste your old sandboxed extensions as unsandboxed extensions, it will appear to just work. However, this is dangerous and is likely to cause bugs later.
If your sandboxed extension has code like like this:
// Old sandboxed extensions (worker or <iframe> sandbox):
class MyExtension {
getInfo () {
return { /* ... */ };
}
}
Scratch.extensions.register(new MyExtension());
Or if your extension uses an old "plugin" mechanism, such as this one: (if you don't recognize this code then don't worry about it)
class MyExtension {
getInfo () {
return { /* ... */ };
}
}
(function() {
var extensionInstance = new MyExtension(window.vm.extensionManager.runtime)
var serviceName = window.vm.extensionManager._registerInternalExtension(extensionInstance)
window.vm.extensionManager._loadedExtensions.set(extensionInstance.getInfo().id, serviceName)
})();
The unsandboxed version would have code like this:
(function(Scratch) {
'use strict';
class MyExtension {
getInfo () {
return { /* ... */ };
}
}
Scratch.extensions.register(new MyExtension());
})(Scratch);
Using this template prevents unsandboxed extensions from interfering with each other when they try to define variables, classes, or functions with the same name. By requiring everything to be defined in an immediately-invoked-function-expression (IIFE) and enabling strict mode, we prevent variables from accidentally leaking to the global scope.
All functions and variables defined by the extension must be defined within the IIFE. Additionally, each extension must make sure to use its own personal copy of the Scratch API, which this template does automatically.
An interesting thing to note about this template is that it is backward compatible with sandboxed extensions. As long as the extension doesn't use any of the features given to unsandboxed extensions, it will continue to work the same as a sandboxed extension.
A more complete example
Here you can see a complete unsandboxed extension:
(function(Scratch) {
'use strict';
if (!Scratch.extensions.unsandboxed) {
throw new Error('This Hello World example must run unsandboxed');
}
class HelloWorld {
getInfo() {
return {
id: 'helloworldunsandboxed',
name: 'Unsandboxed Hello World',
blocks: [
{
opcode: 'hello',
blockType: Scratch.BlockType.REPORTER,
text: 'Hello!'
}
]
};
}
hello() {
return 'World!';
}
}
Scratch.extensions.register(new HelloWorld());
})(Scratch);
If you're using a local HTTP server, save this so you can access it through the server, then load the exact URL [https://about.gitlab.com/stages-devops-lifecycle/pages/) in TurboWarp. If nothing appears, see the developer console. If you see an error that the extension must be run unsandboxed, most likely you are using an old version of TurboWarp or you didn't load it from a URL that starts with `https://about.gitlab.com/stages-devops-lifecycle/pages/ exactly. 127.0.0.1 and 0.0.0.0 won't work! It must be localhost, port 8000 exactly.
If you're just using files, make sure to check the "Run extension without sandbox" box each time you load the extension.
Create a new empty project with a repeat (30) loop that adds the "hello" block to a list. Notice that it now runs instantly while the sandboxed version would've taken at least a second.
Observe that the majority of the code is still identical: You still create a class, then call Scratch.extensions.register(), then Scratch calls getInfo() which returns the same type of object. Just the surrounding template is different.
Increased power brings increased responsibility
Before we talk about the new APIs, we want to note some additional requirements for unsandboxed extensions:
- Blocks must not throw errors. While sandboxed extensions could, unsandboxed extensions that do this may break scripts.
- Input and boolean blocks must return a valid value. While sandboxed extensions are free to neglect this, unsandboxed extensions that don't return proper values (string, number, or boolean) can break scripts in unknown ways.
- Blocks must not get stuck in infinite loops. While sandboxed extensions will usually not be able to freeze the entire window if they get stuck in a loop, unsandboxed extensions will. This can result in data loss.