Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/visor/hypervisor_handlers_browse.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ func (hv *Hypervisor) uiHandler() http.Handler {
w.Header().Set("Cache-Control", "no-cache")
_, _ = w.Write(browseui.WinBoxWasm()) //nolint:errcheck
return
case "/gobrowser.wasm":
// The experimental Go/wasm browser, launched by
// globalThis.SkywireGoBrowser.open() from gobrowser-loader.js.
w.Header().Set("Content-Type", "application/wasm")
w.Header().Set("Cache-Control", "no-cache")
_, _ = w.Write(browseui.GoBrowserWasm()) //nolint:errcheck
return
case "/skywire-browse-launcher.js":
serveJS(w, []byte(nativeBrowseLauncherJS))
return
Expand Down
1 change: 1 addition & 0 deletions pkg/visor/wasmserve.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ func ServeWasm(ctx context.Context, cfg WasmServeConfig) error {
serveBytes("/desk", "text/html; charset=utf-8", deskHTML)
}
serveBytes("/winbox.wasm", "application/wasm", wasmhv.WinBoxWasm())
serveBytes("/gobrowser.wasm", "application/wasm", wasmhv.GoBrowserWasm())
serveBytes("/autoupdate.js", "text/javascript", wasmhv.AutoUpdateJS)
serveBytes("/manifest.webmanifest", "application/manifest+json", wasmhv.PWAManifest)
serveBytes("/icon-192.png", "image/png", wasmhv.PWAIcon192)
Expand Down
39 changes: 39 additions & 0 deletions pkg/wasmhv/browseui/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ var seedSkywireJS []byte
//go:embed skywire-exec.js
var skywireExecJS []byte

// goBrowserLoaderJS defines globalThis.SkywireGoBrowser.open() — the launcher
// for the experimental Go/wasm browser (github.com/0magnet/shipyard cmd/browser).
// Concatenated into the bundle so the launcher is present on every desk page;
// the module itself is a separate fetch, served at /gobrowser.wasm from
// GoBrowserWasm() below.
//
//go:embed gobrowser-loader.js
var goBrowserLoaderJS []byte

//go:embed gobrowser.wasm.gz
var goBrowserWasmGz []byte

// deskBootJS is the shared desk boot (skywireDeskBoot(opts)) behind both
// desk-first pages: the docs playground and the converged visor page. Served
// as its own asset (not part of the bundle) because it runs page-level
Expand Down Expand Up @@ -67,6 +79,7 @@ var BrowseJS = func() []byte {
winboxdist.LoaderJS(),
netscrape.BrowseJS(),
skywireExecJS,
goBrowserLoaderJS,
}
n := 0
for _, p := range parts {
Expand Down Expand Up @@ -95,6 +108,32 @@ func VNetSWJS() []byte { return bottle.VNetSWJS() }
// serving it.
func WinBoxWasmGz() []byte { return winboxdist.WasmGz() }

// GoBrowserWasmGz is the compressed experimental Go browser module.
func GoBrowserWasmGz() []byte { return goBrowserWasmGz }

var (
goBrowserOnce sync.Once
goBrowserWasm []byte
)

// GoBrowserWasm is the experimental Go browser module as served at
// /gobrowser.wasm. Inflated once on first use and kept.
func GoBrowserWasm() []byte {
goBrowserOnce.Do(func() {
zr, err := gzip.NewReader(bytes.NewReader(goBrowserWasmGz))
if err != nil {
return
}
defer zr.Close() //nolint:errcheck
b, err := io.ReadAll(zr)
if err != nil {
return
}
goBrowserWasm = b
})
return goBrowserWasm
}

var (
winBoxOnce sync.Once
winBoxWasm []byte
Expand Down
73 changes: 73 additions & 0 deletions pkg/wasmhv/browseui/gobrowser-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// gobrowser-loader.js — opens the Go/wasm browser (github.com/0magnet/shipyard
// cmd/browser) in a winbox window, wired to the visor's transports. This is an
// EXPERIMENTAL alternative to netscrape's browse.js: the browser's chrome, page
// transcoding (sandboxed srcdoc, inlined stylesheets/images) and navigation are
// Go/syscall/js; only the network is delegated here, through the same
// skywireVisor.fetchDmsg / fetchClearnet the JS engine uses.
//
// Usage from the visor page (or its console): SkywireGoBrowser.open().
// It reads globalThis.WinBox (window manager), globalThis.Go (the wasm_exec
// runtime) and globalThis.skywireVisor (transports) at call time, so it is safe
// to define at bundle load before those exist.
(function () {
var WASM_URL = "/gobrowser.wasm";

function meshHost(h) {
return /\.(dmsg|skysocks|skynet)$/i.test(h) || /^[0-9a-f]{66}$/i.test(h);
}

// respond wraps the visor's {status, body:Uint8Array, headers} into a fetch
// Response, so the Go browser's fetchVia sees a normal Response either way.
function respond(r) {
var h = new Headers();
if (r && r.headers) {
try { for (var k in r.headers) h.set(k, r.headers[k]); } catch (e) { /* ignore */ }
}
return new Response((r && r.body) || new Uint8Array(0), { status: (r && r.status) || 200, headers: h });
}

// The transport the Go browser calls (globalThis.__shipyardBrowserFetch):
// mesh hosts go over dmsg, everything else over clearnet, with a plain
// same-origin fetch as the last resort.
function transport(url) {
var sv = globalThis.skywireVisor || {};
var u;
try { u = new URL(url); } catch (e) { return fetch(url); }
var path = (u.pathname || "/") + (u.search || "");
if (sv.fetchDmsg && meshHost(u.hostname)) {
return Promise.resolve(sv.fetchDmsg(u.hostname, "GET", path, null)).then(respond);
}
if (sv.fetchClearnet) {
return Promise.resolve(sv.fetchClearnet(url, "GET", null)).then(respond);
}
return fetch(url);
}

globalThis.SkywireGoBrowser = {
// open mounts the Go browser in a new winbox window and returns it.
open: function (title) {
if (typeof globalThis.WinBox !== "function") {
console.error("SkywireGoBrowser: window manager not ready");
return null;
}
if (typeof globalThis.Go !== "function") {
console.error("SkywireGoBrowser: Go runtime (wasm_exec) not present");
return null;
}
globalThis.__shipyardBrowserFetch = transport;
var wb = new globalThis.WinBox({ title: title || "Go Browser (beta)", width: "72%", height: "72%" });
var mount = document.createElement("div");
mount.style.cssText = "position:absolute;inset:0";
wb.body.appendChild(mount);
globalThis.__shipyardMount = mount;
fetch(WASM_URL)
.then(function (r) { return r.arrayBuffer(); })
.then(function (buf) {
var go = new globalThis.Go();
return WebAssembly.instantiate(buf, go.importObject).then(function (res) { go.run(res.instance); });
})
.catch(function (e) { mount.textContent = "Go browser failed to load: " + e; });
return wb;
},
};
})();
Binary file added pkg/wasmhv/browseui/gobrowser.wasm.gz
Binary file not shown.
8 changes: 8 additions & 0 deletions pkg/wasmhv/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ func WinBoxWasm() []byte { return browseui.WinBoxWasm() }
// generator to inline.
func WinBoxWasmGz() []byte { return browseui.WinBoxWasmGz() }

// GoBrowserWasm is the experimental Go/wasm browser served at /gobrowser.wasm,
// launched by globalThis.SkywireGoBrowser.open(). Re-exported from the browseui
// leaf like WinBoxWasm.
func GoBrowserWasm() []byte { return browseui.GoBrowserWasm() }

// GoBrowserWasmGz is the same module compressed, for the single-file generator.
func GoBrowserWasmGz() []byte { return browseui.GoBrowserWasmGz() }

// DeskBootJS is the shared desk boot (skywireDeskBoot) behind the desk-first
// pages — the docs playground and `hv serve`'s /desk. Re-exported from the
// browseui leaf like BrowseJS.
Expand Down
Loading