Skip to main content

Run seleniumbase-rs plugins that are written in any language that compiles to WebAssembly (Rust, C, Zig, Go, AssemblyScript, or plain WAT), sandboxed by the pure-Rust interpreter wasmi.

A module describes a plugin in data: a name, browser switches, scripts for pages, URL patterns to block. The host reads that data once, when it loads the module, checks every value, and then drops the module. Nothing of the module runs while the browser does. The module has no imports: no files, network, clock or randomness, and it cannot call back into the host. Only pure functions cross the boundary.

Terminal
MustCodeAl/seleniumbase-rs-plugins

Plugin framework and community plugins for seleniumbase-rs

00
Info

Install it from the same revision of the plugins repository that your seleniumbase-rs pins; see Installing a plugin. The plugins overview is here.

Use it#

WasmPlugin::from_bytes takes the module from memory instead, and from_file_with / from_bytes_with take a Limits to change the defaults below. Loading fails with a WasmError that says what is wrong with the module; it never panics and never hangs. A WasmError converts into a PluginError, so ? works in code that returns the plugin API’s Result.

Loading is synchronous and does the interpreter’s work on the calling thread. A well-behaved module takes microseconds to milliseconds; a hostile one can spend its whole fuel budget on each of its calls and then fails. In async code, load modules at start-up or inside spawn_blocking.

The loaded WasmPlugin is an ordinary Plugin:

HookWhat it does with the module’s data
nameThe name from sbrs_name.
packagesOne Package::script per entry of sbrs_scripts, loaded only_on its hosts or everywhere.
before_launchAppends the arguments of sbrs_launch_args to LaunchSettings::args.
on_page_createdWhen there are blocked URLs: Network.enable, then Network.setBlockedURLs with the patterns.
requirementsCHROMIUM_ONLY when the module blocks URLs (it uses the DevTools Protocol); none otherwise.

The ABI, version 1#

A module exports a linear memory called memory and these functions. None takes a parameter.

ExportRequiredReturns
memoryyesThe module’s linear memory (32-bit; at most one).
sbrs_abi_version() -> i32yes1. Any other value is refused.
sbrs_name() -> i64yesA JSON string: the plugin’s name.
sbrs_launch_args() -> i64noA JSON array of strings: browser switches.
sbrs_scripts() -> i64noA JSON array of script objects.
sbrs_blocked_urls() -> i64noA JSON array of strings: URL patterns to block.

An optional export that is missing means “none”. An export that is present with the wrong type is an error. The sbrs_ prefix is reserved for the ABI: an export that starts with it and is not in the table (a typo such as sbrs_script) is an error, so a mistake is never silently ignored. Exports with other names are ignored, so a module can export what it likes.

Returning a string: the packed i64#

A function that returns text returns one i64 that packs a pointer and a length, and leaves the UTF-8 JSON text in its memory at that place:

The host reads memory[pointer .. pointer + length] right after the call returns, so the bytes must stay valid until then: a static string, or a buffer the module leaks. A range outside the memory, or longer than the limit, is an error. The text must be valid UTF-8 and exactly one JSON value, with nothing after it.

The values#

sbrs_namestringrequired#

A JSON string of 1 to 64 characters from a-z, 0-9, - and _, starting with a letter or a digit, such as "my-plugin". It must be unique in the PluginSet, like any plugin name.

sbrs_launch_argsstring[]#

At most 256 strings of at most 4096 bytes. Each is a switch: it starts with -, followed by a name that starts with a letter or a digit ("--lang=de"), and holds no control characters. A bare --, or anything that is not a switch, is refused because the browser would take it for a URL.

sbrs_scriptsobject[]#

At most 64 objects with exactly these two fields and no others:

sbrs_blocked_urlsstring[]#

At most 1024 strings of 1 to 2048 bytes, Chrome URL patterns where * matches anything ("*.png", "*://ads.example.com/*").

Limits#

The host applies these to every module. Limits changes the first four.

LimitDefault
Module size4 MiB
Memory (at start and after any memory.grow)16 MiB
Fuel for each call, and for the start function20,000,000 (roughly as many instructions)
Size of one returned JSON value1 MiB
Call stackwasmi’s defaults; a recursion bomb traps
Importsnone: a module that imports anything is refused
Memories, tables1 memory; tables of at most 100,000 elements; wasmi’s strict limits on functions, globals and parameters
Memory page size, memory64the standard 64 KiB page and 32-bit addresses only

A function that runs out of fuel stops with WasmError::OutOfFuel; a trap (unreachable, stack overflow, out-of-bounds access) is WasmError::Trapped. memory.grow past the cap returns -1, as the WebAssembly specification says. The text format is not accepted: give from_bytes a binary module (use wat or wasm-tools to build one from WAT).

What a module can still do#

Warning

The sandbox keeps a module from touching your machine while it is loaded. What it describes then reaches three places, so review a module you did not write.

  • Scripts run inside pages, with the page’s rights. A script for "everywhere" sees every page, cookie and password in the browser, and can send them anywhere the page can. Prefer hosts lists.
  • Browser switches go to the browser. The host refuses the ones known to run programs or switch off the browser’s protections, by name, wherever they appear in the list: renderer-cmd-prefix, utility-cmd-prefix, zygote-cmd-prefix, gpu-launcher, ppapi-plugin-launcher, browser-subprocess-path, nacl-gdb, no-sandbox, disable-setuid-sandbox, disable-gpu-sandbox, no-zygote, disable-web-security, ignore-certificate-errors, proxy-server, proxy-pac-url, remote-debugging-port, remote-debugging-address, remote-debugging-pipe, remote-allow-origins, load-extension, disable-extensions-except and user-data-dir (FORBIDDEN_FLAGS in the code). That list is a floor, not a complete audit of Chrome’s switches: read WasmPlugin::launch_args() if you must be sure.
  • Blocked URLs change what a page can load.

Write a module#

A module can be written in WAT or in any language that compiles to WebAssembly. Two examples:

examples/hello.wat is a whole plugin: it is named hello and logs a line on example.com. The two strings sit in the data segments; each function returns the pointer in the high half and the length in the low half.

Build the binary with wasm-tools parse examples/hello.wat -o hello.wasm, or in Rust with wat::parse_str.

Compile a plugin to wasm32-unknown-unknown as a cdylib, with no dependencies and no unsafe:

Terminal

To build the JSON at run time (with serde_json, say), leak the String so it stays valid after the function returns: pack(Box::leak(json.into_boxed_str())). The host calls each function once and then discards the module, so the leak is a single allocation.

Test a module#

Load it, then run the plugin against the recording fakes of seleniumbase-rs-plugin-testing: no browser, no module-specific harness.

Versioning#

The ABI version is the number sbrs_abi_version returns. A change that an existing module could not survive (a new required export, another meaning for a field) bumps it. A host refuses a module of a version it does not speak, with WasmError::UnsupportedAbi, before it looks at anything else. For that reason sbrs_abi_version() -> i32 keeps its signature in every version.