From 40ddacfe4fd445374e8fe9470eb7619ea2982800 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 7 Dec 2025 10:20:05 -0800 Subject: [PATCH] Remove wasm render plugin example --- contrib/render-plugins/example-wasm/README.md | 56 -- contrib/render-plugins/example-wasm/build.sh | 26 - .../render-plugins/example-wasm/manifest.json | 12 - contrib/render-plugins/example-wasm/render.js | 163 ----- .../render-plugins/example-wasm/wasm/main.go | 29 - .../render-plugins/example-wasm/wasm_exec.js | 575 ------------------ 6 files changed, 861 deletions(-) delete mode 100644 contrib/render-plugins/example-wasm/README.md delete mode 100755 contrib/render-plugins/example-wasm/build.sh delete mode 100644 contrib/render-plugins/example-wasm/manifest.json delete mode 100644 contrib/render-plugins/example-wasm/render.js delete mode 100644 contrib/render-plugins/example-wasm/wasm/main.go delete mode 100644 contrib/render-plugins/example-wasm/wasm_exec.js diff --git a/contrib/render-plugins/example-wasm/README.md b/contrib/render-plugins/example-wasm/README.md deleted file mode 100644 index 5a01c3dea6..0000000000 --- a/contrib/render-plugins/example-wasm/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Go + WASM Render Plugin Example - -This example shows how to build a frontend render plugin whose heavy lifting -runs inside a WebAssembly module compiled from Go. The plugin loads the WASM -binary in the browser, asks Go to post-process the fetched file content, and -then renders the result inside the file viewer. - -## Files - -- `manifest.json` — plugin metadata consumed by the Gitea backend -- `render.js` — the ES module entry point that initializes the Go runtime and - renders files handled by the plugin -- `wasm/` — contains the Go source that compiles to `plugin.wasm` -- `wasm_exec.js` — the Go runtime shim required by all Go-generated WASM - binaries (copied verbatim from the Go distribution) -- `build.sh` — helper script that builds `plugin.wasm` and produces a zip - archive ready for upload - -As with other plugins, declare any Gitea endpoints or external hosts the WASM -module needs to call inside the `permissions` array in `manifest.json`. Without -an explicit entry, the plugin may only download the file that is currently being -rendered. - -## Build & Install - -1. Build the WASM binary and zip archive: - - ```bash - cd contrib/render-plugins/example-wasm - ./build.sh - ``` - - The script requires Go 1.21+ on your PATH. It stores the compiled WASM and - an installable `example-go-wasm.zip` under `dist/`. - -2. In the Gitea web UI, visit `Site Administration → Render Plugins`, upload - `dist/example-go-wasm.zip`, and enable the plugin. - -3. Open any file whose name ends with `.wasmnote`; the viewer will show the - processed output from the Go code running inside WebAssembly. - -## How It Works - -- `wasm/main.go` exposes a single `wasmProcessFile` function to JavaScript. It - uppercases each line, prefixes it with the line number, and runs entirely - inside WebAssembly compiled from Go. -- `render.js` injects the Go runtime (`wasm_exec.js`), instantiates the compiled - module, and caches the exported `wasmProcessFile` function. -- During initialization the frontend passes the sniffed MIME type and the first - 1 KiB of file data to the plugin (`options.mimeType`/`options.headChunk`), - allowing renderers to make decisions without issuing extra network requests. -- During rendering the plugin downloads the target file, passes the contents to - Go, and displays the transformed text with minimal styling. - -Feel free to modify the Go source or the JS wrapper to experiment with richer -interfaces between JavaScript and WebAssembly. diff --git a/contrib/render-plugins/example-wasm/build.sh b/contrib/render-plugins/example-wasm/build.sh deleted file mode 100755 index 7d8966851b..0000000000 --- a/contrib/render-plugins/example-wasm/build.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -BUILD_DIR="$SCRIPT_DIR/.build" -DIST_DIR="$SCRIPT_DIR/dist" -ARCHIVE_NAME="example-go-wasm.zip" - -rm -rf "$BUILD_DIR" -mkdir -p "$BUILD_DIR" "$DIST_DIR" - -export GOOS=js -export GOARCH=wasm - -echo "[+] Building Go WASM binary..." -go build -o "$BUILD_DIR/plugin.wasm" ./wasm - -cp manifest.json "$BUILD_DIR/" -cp render.js "$BUILD_DIR/" -cp wasm_exec.js "$BUILD_DIR/" - -( cd "$BUILD_DIR" && zip -q "../dist/$ARCHIVE_NAME" manifest.json render.js wasm_exec.js plugin.wasm ) - -echo "[+] Wrote $DIST_DIR/$ARCHIVE_NAME" diff --git a/contrib/render-plugins/example-wasm/manifest.json b/contrib/render-plugins/example-wasm/manifest.json deleted file mode 100644 index b592e226aa..0000000000 --- a/contrib/render-plugins/example-wasm/manifest.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "schemaVersion": 1, - "id": "example-go-wasm", - "name": "Example Go WASM Renderer", - "version": "0.1.0", - "description": "Demonstrates calling a Go-compiled WebAssembly module inside a Gitea render plugin.", - "entry": "render.js", - "filePatterns": [ - "*.wasmnote" - ], - "permissions": [] -} diff --git a/contrib/render-plugins/example-wasm/render.js b/contrib/render-plugins/example-wasm/render.js deleted file mode 100644 index 4397ce0e52..0000000000 --- a/contrib/render-plugins/example-wasm/render.js +++ /dev/null @@ -1,163 +0,0 @@ -const wasmUrl = new URL('plugin.wasm', import.meta.url); -const wasmExecUrl = new URL('wasm_exec.js', import.meta.url); -let wasmBridgePromise; -let styleInjected = false; - -function injectScriptOnce(url) { - return new Promise((resolve, reject) => { - const existing = document.querySelector(`script[data-go-runtime="${url.href}"]`); - if (existing) { - if (existing.dataset.loaded === 'true') { - resolve(); - } else { - existing.addEventListener('load', resolve, {once: true}); - existing.addEventListener('error', reject, {once: true}); - } - return; - } - const script = document.createElement('script'); - script.dataset.goRuntime = url.href; - script.src = url.href; - script.async = true; - script.addEventListener('load', () => { - script.dataset.loaded = 'true'; - resolve(); - }, {once: true}); - script.addEventListener('error', reject, {once: true}); - document.head.appendChild(script); - }); -} - -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function waitForExport(name, timeoutMs = 2000) { - const start = Date.now(); - while (typeof globalThis[name] !== 'function') { - if (Date.now() - start > timeoutMs) { - throw new Error(`Go runtime did not expose ${name} within ${timeoutMs}ms`); - } - await sleep(20); - } - return globalThis[name]; -} - -async function ensureWasmBridge() { - if (!wasmBridgePromise) { - wasmBridgePromise = (async () => { - if (typeof globalThis.Go === 'undefined') { - await injectScriptOnce(wasmExecUrl); - } - if (typeof globalThis.Go === 'undefined') { - throw new Error('Go runtime (wasm_exec.js) is unavailable'); - } - const go = new globalThis.Go(); - let result; - const fetchRequest = fetch(wasmUrl); - if (WebAssembly.instantiateStreaming) { - try { - result = await WebAssembly.instantiateStreaming(fetchRequest, go.importObject); - } catch (err) { - console.warn('instantiateStreaming failed; falling back to ArrayBuffer', err); - const buffer = await (await fetchRequest).arrayBuffer(); - result = await WebAssembly.instantiate(buffer, go.importObject); - } - } else { - const buffer = await (await fetchRequest).arrayBuffer(); - result = await WebAssembly.instantiate(buffer, go.importObject); - } - go.run(result.instance); - const processFile = await waitForExport('wasmProcessFile'); - return { - process(content) { - const output = processFile(content); - return typeof output === 'string' ? output : String(output ?? ''); - }, - }; - })(); - } - return wasmBridgePromise; -} - -async function fetchFileText(fileUrl) { - const response = await window.fetch(fileUrl, {headers: {'Accept': 'text/plain'}}); - if (!response.ok) { - throw new Error(`failed to fetch file (${response.status})`); - } - return response.text(); -} - -function ensureStyles() { - if (styleInjected) return; - styleInjected = true; - const style = document.createElement('style'); - style.textContent = ` -.go-wasm-renderer { - font-family: var(--fonts-proportional, system-ui); - border: 1px solid var(--color-secondary); - border-radius: 6px; - overflow: hidden; -} -.go-wasm-renderer__header { - margin: 0; - padding: 0.75rem 1rem; - background: var(--color-secondary-alpha-20); - font-weight: 600; -} -.go-wasm-renderer pre { - margin: 0; - padding: 1rem; - background: var(--color-box-body); - font-family: var(--fonts-monospace, SFMono-Regular, monospace); - white-space: pre; - overflow-x: auto; -} -.go-wasm-renderer__error { - color: var(--color-danger); -} -`; - document.head.appendChild(style); -} - -function renderError(container, message) { - container.innerHTML = ''; - const wrapper = document.createElement('div'); - wrapper.className = 'go-wasm-renderer'; - const header = document.createElement('div'); - header.className = 'go-wasm-renderer__header'; - header.textContent = 'Go WASM Renderer'; - const body = document.createElement('pre'); - body.className = 'go-wasm-renderer__error'; - body.textContent = message; - wrapper.append(header, body); - container.appendChild(wrapper); -} - -export default { - name: 'Go WASM Renderer', - async render(container, fileUrl) { - ensureStyles(); - try { - const [bridge, content] = await Promise.all([ - ensureWasmBridge(), - fetchFileText(fileUrl), - ]); - - const processed = await bridge.process(content); - const wrapper = document.createElement('div'); - wrapper.className = 'go-wasm-renderer'; - const header = document.createElement('div'); - header.className = 'go-wasm-renderer__header'; - header.textContent = 'Go WASM Renderer'; - const body = document.createElement('pre'); - body.textContent = processed; - wrapper.append(header, body); - container.innerHTML = ''; - container.appendChild(wrapper); - } catch (err) { - console.error('Go WASM plugin failed', err); - renderError(container, `Unable to render file: ${err.message}`); - } - }, -}; diff --git a/contrib/render-plugins/example-wasm/wasm/main.go b/contrib/render-plugins/example-wasm/wasm/main.go deleted file mode 100644 index ef53fd9e9a..0000000000 --- a/contrib/render-plugins/example-wasm/wasm/main.go +++ /dev/null @@ -1,29 +0,0 @@ -//go:build js && wasm -// +build js,wasm - -package main - -import ( - "fmt" - "strings" - "syscall/js" -) - -func processFile(this js.Value, args []js.Value) any { - if len(args) == 0 { - return js.ValueOf("(no content)") - } - content := args[0].String() - lines := strings.Split(content, "\n") - var b strings.Builder - b.Grow(len(content) + len(lines)*8) - for i, line := range lines { - fmt.Fprintf(&b, "%4d │ %s\n", i+1, strings.ToUpper(line)) - } - return js.ValueOf(b.String()) -} - -func main() { - js.Global().Set("wasmProcessFile", js.FuncOf(processFile)) - select {} -} diff --git a/contrib/render-plugins/example-wasm/wasm_exec.js b/contrib/render-plugins/example-wasm/wasm_exec.js deleted file mode 100644 index d71af9e97e..0000000000 --- a/contrib/render-plugins/example-wasm/wasm_exec.js +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -"use strict"; - -(() => { - const enosys = () => { - const err = new Error("not implemented"); - err.code = "ENOSYS"; - return err; - }; - - if (!globalThis.fs) { - let outputBuf = ""; - globalThis.fs = { - constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused - writeSync(fd, buf) { - outputBuf += decoder.decode(buf); - const nl = outputBuf.lastIndexOf("\n"); - if (nl != -1) { - console.log(outputBuf.substring(0, nl)); - outputBuf = outputBuf.substring(nl + 1); - } - return buf.length; - }, - write(fd, buf, offset, length, position, callback) { - if (offset !== 0 || length !== buf.length || position !== null) { - callback(enosys()); - return; - } - const n = this.writeSync(fd, buf); - callback(null, n); - }, - chmod(path, mode, callback) { callback(enosys()); }, - chown(path, uid, gid, callback) { callback(enosys()); }, - close(fd, callback) { callback(enosys()); }, - fchmod(fd, mode, callback) { callback(enosys()); }, - fchown(fd, uid, gid, callback) { callback(enosys()); }, - fstat(fd, callback) { callback(enosys()); }, - fsync(fd, callback) { callback(null); }, - ftruncate(fd, length, callback) { callback(enosys()); }, - lchown(path, uid, gid, callback) { callback(enosys()); }, - link(path, link, callback) { callback(enosys()); }, - lstat(path, callback) { callback(enosys()); }, - mkdir(path, perm, callback) { callback(enosys()); }, - open(path, flags, mode, callback) { callback(enosys()); }, - read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, - readdir(path, callback) { callback(enosys()); }, - readlink(path, callback) { callback(enosys()); }, - rename(from, to, callback) { callback(enosys()); }, - rmdir(path, callback) { callback(enosys()); }, - stat(path, callback) { callback(enosys()); }, - symlink(path, link, callback) { callback(enosys()); }, - truncate(path, length, callback) { callback(enosys()); }, - unlink(path, callback) { callback(enosys()); }, - utimes(path, atime, mtime, callback) { callback(enosys()); }, - }; - } - - if (!globalThis.process) { - globalThis.process = { - getuid() { return -1; }, - getgid() { return -1; }, - geteuid() { return -1; }, - getegid() { return -1; }, - getgroups() { throw enosys(); }, - pid: -1, - ppid: -1, - umask() { throw enosys(); }, - cwd() { throw enosys(); }, - chdir() { throw enosys(); }, - } - } - - if (!globalThis.path) { - globalThis.path = { - resolve(...pathSegments) { - return pathSegments.join("/"); - } - } - } - - if (!globalThis.crypto) { - throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); - } - - if (!globalThis.performance) { - throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); - } - - if (!globalThis.TextEncoder) { - throw new Error("globalThis.TextEncoder is not available, polyfill required"); - } - - if (!globalThis.TextDecoder) { - throw new Error("globalThis.TextDecoder is not available, polyfill required"); - } - - const encoder = new TextEncoder("utf-8"); - const decoder = new TextDecoder("utf-8"); - - globalThis.Go = class { - constructor() { - this.argv = ["js"]; - this.env = {}; - this.exit = (code) => { - if (code !== 0) { - console.warn("exit code:", code); - } - }; - this._exitPromise = new Promise((resolve) => { - this._resolveExitPromise = resolve; - }); - this._pendingEvent = null; - this._scheduledTimeouts = new Map(); - this._nextCallbackTimeoutID = 1; - - const setInt64 = (addr, v) => { - this.mem.setUint32(addr + 0, v, true); - this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); - } - - const setInt32 = (addr, v) => { - this.mem.setUint32(addr + 0, v, true); - } - - const getInt64 = (addr) => { - const low = this.mem.getUint32(addr + 0, true); - const high = this.mem.getInt32(addr + 4, true); - return low + high * 4294967296; - } - - const loadValue = (addr) => { - const f = this.mem.getFloat64(addr, true); - if (f === 0) { - return undefined; - } - if (!isNaN(f)) { - return f; - } - - const id = this.mem.getUint32(addr, true); - return this._values[id]; - } - - const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; - - if (typeof v === "number" && v !== 0) { - if (isNaN(v)) { - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 0, true); - return; - } - this.mem.setFloat64(addr, v, true); - return; - } - - if (v === undefined) { - this.mem.setFloat64(addr, 0, true); - return; - } - - let id = this._ids.get(v); - if (id === undefined) { - id = this._idPool.pop(); - if (id === undefined) { - id = this._values.length; - } - this._values[id] = v; - this._goRefCounts[id] = 0; - this._ids.set(v, id); - } - this._goRefCounts[id]++; - let typeFlag = 0; - switch (typeof v) { - case "object": - if (v !== null) { - typeFlag = 1; - } - break; - case "string": - typeFlag = 2; - break; - case "symbol": - typeFlag = 3; - break; - case "function": - typeFlag = 4; - break; - } - this.mem.setUint32(addr + 4, nanHead | typeFlag, true); - this.mem.setUint32(addr, id, true); - } - - const loadSlice = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - return new Uint8Array(this._inst.exports.mem.buffer, array, len); - } - - const loadSliceOfValues = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - const a = new Array(len); - for (let i = 0; i < len; i++) { - a[i] = loadValue(array + i * 8); - } - return a; - } - - const loadString = (addr) => { - const saddr = getInt64(addr + 0); - const len = getInt64(addr + 8); - return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); - } - - const testCallExport = (a, b) => { - this._inst.exports.testExport0(); - return this._inst.exports.testExport(a, b); - } - - const timeOrigin = Date.now() - performance.now(); - this.importObject = { - _gotest: { - add: (a, b) => a + b, - callExport: testCallExport, - }, - gojs: { - // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) - // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported - // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). - // This changes the SP, thus we have to update the SP used by the imported function. - - // func wasmExit(code int32) - "runtime.wasmExit": (sp) => { - sp >>>= 0; - const code = this.mem.getInt32(sp + 8, true); - this.exited = true; - delete this._inst; - delete this._values; - delete this._goRefCounts; - delete this._ids; - delete this._idPool; - this.exit(code); - }, - - // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) - "runtime.wasmWrite": (sp) => { - sp >>>= 0; - const fd = getInt64(sp + 8); - const p = getInt64(sp + 16); - const n = this.mem.getInt32(sp + 24, true); - fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); - }, - - // func resetMemoryDataView() - "runtime.resetMemoryDataView": (sp) => { - sp >>>= 0; - this.mem = new DataView(this._inst.exports.mem.buffer); - }, - - // func nanotime1() int64 - "runtime.nanotime1": (sp) => { - sp >>>= 0; - setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); - }, - - // func walltime() (sec int64, nsec int32) - "runtime.walltime": (sp) => { - sp >>>= 0; - const msec = (new Date).getTime(); - setInt64(sp + 8, msec / 1000); - this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); - }, - - // func scheduleTimeoutEvent(delay int64) int32 - "runtime.scheduleTimeoutEvent": (sp) => { - sp >>>= 0; - const id = this._nextCallbackTimeoutID; - this._nextCallbackTimeoutID++; - this._scheduledTimeouts.set(id, setTimeout( - () => { - this._resume(); - while (this._scheduledTimeouts.has(id)) { - // for some reason Go failed to register the timeout event, log and try again - // (temporary workaround for https://github.com/golang/go/issues/28975) - console.warn("scheduleTimeoutEvent: missed timeout event"); - this._resume(); - } - }, - getInt64(sp + 8), - )); - this.mem.setInt32(sp + 16, id, true); - }, - - // func clearTimeoutEvent(id int32) - "runtime.clearTimeoutEvent": (sp) => { - sp >>>= 0; - const id = this.mem.getInt32(sp + 8, true); - clearTimeout(this._scheduledTimeouts.get(id)); - this._scheduledTimeouts.delete(id); - }, - - // func getRandomData(r []byte) - "runtime.getRandomData": (sp) => { - sp >>>= 0; - crypto.getRandomValues(loadSlice(sp + 8)); - }, - - // func finalizeRef(v ref) - "syscall/js.finalizeRef": (sp) => { - sp >>>= 0; - const id = this.mem.getUint32(sp + 8, true); - this._goRefCounts[id]--; - if (this._goRefCounts[id] === 0) { - const v = this._values[id]; - this._values[id] = null; - this._ids.delete(v); - this._idPool.push(id); - } - }, - - // func stringVal(value string) ref - "syscall/js.stringVal": (sp) => { - sp >>>= 0; - storeValue(sp + 24, loadString(sp + 8)); - }, - - // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (sp) => { - sp >>>= 0; - const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 32, result); - }, - - // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (sp) => { - sp >>>= 0; - Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); - }, - - // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (sp) => { - sp >>>= 0; - Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); - }, - - // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (sp) => { - sp >>>= 0; - storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); - }, - - // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (sp) => { - sp >>>= 0; - Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); - }, - - // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (sp) => { - sp >>>= 0; - try { - const v = loadValue(sp + 8); - const m = Reflect.get(v, loadString(sp + 16)); - const args = loadSliceOfValues(sp + 32); - const result = Reflect.apply(m, v, args); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 56, result); - this.mem.setUint8(sp + 64, 1); - } catch (err) { - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 56, err); - this.mem.setUint8(sp + 64, 0); - } - }, - - // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (sp) => { - sp >>>= 0; - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.apply(v, undefined, args); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); - } catch (err) { - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); - } - }, - - // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (sp) => { - sp >>>= 0; - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.construct(v, args); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); - } catch (err) { - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); - } - }, - - // func valueLength(v ref) int - "syscall/js.valueLength": (sp) => { - sp >>>= 0; - setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); - }, - - // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (sp) => { - sp >>>= 0; - const str = encoder.encode(String(loadValue(sp + 8))); - storeValue(sp + 16, str); - setInt64(sp + 24, str.length); - }, - - // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (sp) => { - sp >>>= 0; - const str = loadValue(sp + 8); - loadSlice(sp + 16).set(str); - }, - - // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (sp) => { - sp >>>= 0; - this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); - }, - - // func copyBytesToGo(dst []byte, src ref) (int, bool) - "syscall/js.copyBytesToGo": (sp) => { - sp >>>= 0; - const dst = loadSlice(sp + 8); - const src = loadValue(sp + 32); - if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { - this.mem.setUint8(sp + 48, 0); - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); - }, - - // func copyBytesToJS(dst ref, src []byte) (int, bool) - "syscall/js.copyBytesToJS": (sp) => { - sp >>>= 0; - const dst = loadValue(sp + 8); - const src = loadSlice(sp + 16); - if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { - this.mem.setUint8(sp + 48, 0); - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); - }, - - "debug": (value) => { - console.log(value); - }, - } - }; - } - - async run(instance) { - if (!(instance instanceof WebAssembly.Instance)) { - throw new Error("Go.run: WebAssembly.Instance expected"); - } - this._inst = instance; - this.mem = new DataView(this._inst.exports.mem.buffer); - this._values = [ // JS values that Go currently has references to, indexed by reference id - NaN, - 0, - null, - true, - false, - globalThis, - this, - ]; - this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id - this._ids = new Map([ // mapping from JS values to reference ids - [0, 1], - [null, 2], - [true, 3], - [false, 4], - [globalThis, 5], - [this, 6], - ]); - this._idPool = []; // unused ids that have been garbage collected - this.exited = false; // whether the Go program has exited - - // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. - let offset = 4096; - - const strPtr = (str) => { - const ptr = offset; - const bytes = encoder.encode(str + "\0"); - new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); - offset += bytes.length; - if (offset % 8 !== 0) { - offset += 8 - (offset % 8); - } - return ptr; - }; - - const argc = this.argv.length; - - const argvPtrs = []; - this.argv.forEach((arg) => { - argvPtrs.push(strPtr(arg)); - }); - argvPtrs.push(0); - - const keys = Object.keys(this.env).sort(); - keys.forEach((key) => { - argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); - }); - argvPtrs.push(0); - - const argv = offset; - argvPtrs.forEach((ptr) => { - this.mem.setUint32(offset, ptr, true); - this.mem.setUint32(offset + 4, 0, true); - offset += 8; - }); - - // The linker guarantees global data starts from at least wasmMinDataAddr. - // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. - const wasmMinDataAddr = 4096 + 8192; - if (offset >= wasmMinDataAddr) { - throw new Error("total length of command line and environment variables exceeds limit"); - } - - this._inst.exports.run(argc, argv); - if (this.exited) { - this._resolveExitPromise(); - } - await this._exitPromise; - } - - _resume() { - if (this.exited) { - throw new Error("Go program has already exited"); - } - this._inst.exports.resume(); - if (this.exited) { - this._resolveExitPromise(); - } - } - - _makeFuncWrapper(id) { - const go = this; - return function () { - const event = { id: id, this: this, args: arguments }; - go._pendingEvent = event; - go._resume(); - return event.result; - }; - } - } -})();