| name | wasm-extension-integration |
| description | Guide for integrating WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, and cross-browser loading |
| tags | ["browser-extension","webassembly","wasm","wasm-pack","wasm-bindgen","rust"] |
WASM Extension Integration
Guide for integrating WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, and cross-browser loading patterns.
Overview
WASM in browser extensions enables:
- High-performance computations (cryptography, parsing, compression)
- Code reuse from Rust/C++ libraries
- Sandboxed execution environments
Build Pipeline
wasm-pack Workflow
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
wasm-pack build --target web --out-dir pkg
wasm-pack build --target bundler --out-dir pkg
Cargo.toml Configuration
[package]
name = "extension-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["console"] }
[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
strip = true
Build Targets
| Target | Output | Use Case |
|---|
web | ES modules | Direct browser loading |
bundler | npm package | Webpack/Vite bundling |
nodejs | CommonJS | Node.js (not for extensions) |
no-modules | Global | Legacy browser support |
WXT Integration
Project Structure
extension/
├── wasm/
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── entrypoints/
│ └── background.ts
└── wxt.config.ts
WXT Configuration
import { defineConfig } from 'wxt';
export default defineConfig({
vite: () => ({
plugins: [
{
name: 'wasm-pack',
buildStart: async () => {
const { execSync } = await import('child_process');
execSync('wasm-pack build wasm --target web --out-dir ../public/wasm', {
stdio: 'inherit'
});
}
}
],
build: {
target: 'esnext',
rollupOptions: {
output: {
inlineDynamicImports: false
}
}
},
optimizeDeps: {
exclude: ['*.wasm']
}
})
});
Cross-Browser Loading
Synchronous Instantiation (Small Modules)
async function loadWasmSync(wasmPath: string): Promise<WebAssembly.Instance> {
const response = await fetch(chrome.runtime.getURL(wasmPath));
const bytes = await response.arrayBuffer();
const module = new WebAssembly.Module(bytes);
return new WebAssembly.Instance(module);
}
Asynchronous Instantiation (Large Modules)
async function loadWasmAsync(wasmPath: string): Promise<WebAssembly.Instance> {
const response = await fetch(chrome.runtime.getURL(wasmPath));
if (WebAssembly.instantiateStreaming) {
const { instance } = await WebAssembly.instantiateStreaming(response);
return instance;
} else {
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes);
return instance;
}
}
wasm-bindgen Initialization
import init, { process_data } from './pkg/extension_wasm.js';
let wasmReady = false;
async function initWasm(): Promise<void> {
if (wasmReady) return;
const wasmUrl = chrome.runtime.getURL('pkg/extension_wasm_bg.wasm');
await init(wasmUrl);
wasmReady = true;
}
async function processWithWasm(data: Uint8Array): Promise<Uint8Array> {
await initWasm();
return process_data(data);
}
Browser Compatibility
Feature Detection
function checkWasmSupport(): {
basic: boolean;
streaming: boolean;
threads: boolean;
simd: boolean;
} {
const basic = typeof WebAssembly !== 'undefined';
const streaming = basic &&
typeof WebAssembly.instantiateStreaming === 'function';
const threads = basic &&
typeof SharedArrayBuffer !== 'undefined';
const simd = basic && (() => {
try {
const bytes = new Uint8Array([
0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123,
3, 2, 1, 0, 10, 10, , , , , , , , , ,
]);
.(bytes);
} {
;
}
})();
{ basic, streaming, threads, simd };
}
Safari Considerations
Safari has stricter WASM policies:
{
"web_accessible_resources": [
{
"resources": ["pkg/*.wasm", "pkg/*.js"],
"matches": ["<all_urls>"]
}
]
}
Memory Management
Extension Memory Limits
| Context | Chrome | Firefox | Safari |
|---|
| Service worker | 128MB default | 512MB | 128MB |
| Content script | Tab memory | Tab memory | Tab memory |
| Popup | 128MB | 128MB | 128MB |
Memory-Efficient Patterns
use wasm_bindgen::prelude::*;
static mut BUFFER: Vec<u8> = Vec::new();
#[wasm_bindgen]
pub fn process_chunk(data: &[u8]) -> Vec<u8> {
unsafe {
BUFFER.clear();
BUFFER.extend_from_slice(data);
BUFFER.clone()
}
}
#[wasm_bindgen]
pub fn free_buffer() {
unsafe {
BUFFER = Vec::new();
BUFFER.shrink_to_fit();
}
}
Streaming Processing
async function processLargeData(
data: ArrayBuffer,
chunkSize: number = 1024 * 1024
): Promise<ArrayBuffer> {
await initWasm();
const input = new Uint8Array(data);
const results: Uint8Array[] = [];
for (let i = 0; i < input.length; i += chunkSize) {
const chunk = input.slice(i, i + chunkSize);
const processed = process_chunk(chunk);
results.push(processed);
}
const totalLength = results.reduce((sum, arr) => sum + arr.length, 0);
const output = new Uint8Array(totalLength);
let offset = 0;
for (const result of results) {
output.set(result, offset);
offset += result.length;
}
();
output.;
}
Service Worker Integration
Loading WASM in Service Worker
let wasmModule: WebAssembly.Module | null = null;
chrome.runtime.onInstalled.addListener(async () => {
const response = await fetch(chrome.runtime.getURL('pkg/module.wasm'));
const bytes = await response.arrayBuffer();
wasmModule = await WebAssembly.compile(bytes);
console.log('WASM module compiled');
});
async function getWasmInstance(): Promise<WebAssembly.Instance> {
if (!wasmModule) {
const response = await fetch(chrome.runtime.getURL('pkg/module.wasm'));
const bytes = await response.arrayBuffer();
wasmModule = await WebAssembly.compile(bytes);
}
.(wasmModule);
}
Service Worker Lifecycle
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'PROCESS_WASM') {
processWithWasm(message.data)
.then(sendResponse)
.catch(error => sendResponse({ error: error.message }));
return true;
}
});
MCP Server: wasm-bindgen
The plugin includes a wasm-bindgen MCP server for build automation.
Configuration
{
"mcpServers": {
"wasm-bindgen": {
"command": "cargo",
"args": [
"run",
"--manifest-path",
"${HOME}/.local/share/mcp/wasm-bindgen-mcp/Cargo.toml",
"--",
"--target-dir",
".output/wasm"
],
"env": {
"WASM_PACK_PATH": "wasm-pack",
"WASM_TARGET": "web",
"WASM_EXTENSION_MODE": "true"
}
}
}
}
Available Tools
| Tool | Description |
|---|
wasm_build | Build WASM module with wasm-pack |
wasm_optimize | Optimize WASM binary size |
wasm_validate | Validate WASM module |
wasm_inspect | Inspect WASM module exports |
Usage
Size Optimization
Build Flags
[profile.release]
opt-level = "z"
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true
Post-Build Optimization
brew install binaryen
wasm-opt -Os -o output.wasm input.wasm
wasm-pack build --release -- --features wee_alloc
wee_alloc (Smaller Allocator)
[dependencies]
wee_alloc = { version = "0.4", optional = true }
[features]
default = ["wee_alloc"]
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
Common Use Cases
Cryptography
use wasm_bindgen::prelude::*;
use sha2::{Sha256, Digest};
#[wasm_bindgen]
pub fn hash_sha256(data: &[u8]) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(data);
hasher.finalize().to_vec()
}
Data Compression
use wasm_bindgen::prelude::*;
use flate2::Compression;
use flate2::write::GzEncoder;
use std::io::Write;
#[wasm_bindgen]
pub fn compress_gzip(data: &[u8]) -> Vec<u8> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(data).unwrap();
encoder.finish().unwrap()
}
JSON Parsing (Fast)
use wasm_bindgen::prelude::*;
use serde_json::Value;
#[wasm_bindgen]
pub fn parse_json(input: &str) -> JsValue {
match serde_json::from_str::<Value>(input) {
Ok(value) => serde_wasm_bindgen::to_value(&value).unwrap(),
Err(e) => JsValue::from_str(&format!("Error: {}", e))
}
}
Debugging
Source Maps
wasm-pack build --dev
RUSTFLAGS="-C debuginfo=2" wasm-pack build
Browser DevTools
- Chrome: DevTools → Sources → shows .wasm files
- Firefox: Debugger → shows WASM as text
- Safari: Limited WASM debugging
Console Logging
use web_sys::console;
#[wasm_bindgen]
pub fn debug_log(message: &str) {
console::log_1(&message.into());
}
Quality Checklist