Skip to content

Commit

Permalink
fix: webworkers, add multi-window example (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelFedora authored Mar 26, 2022
1 parent ea2aad1 commit 755258f
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ target/

# plugin cache
.deno_plugins/
WebView2Loader.dll
113 changes: 113 additions & 0 deletions examples/multi-window/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { serve } from "https://deno.land/std@0.132.0/http/server.ts";
import { preload } from "../../mod.ts";
await preload();

/**
* A window helper class to handle the workers
*/
class Window {
readonly worker: Worker;
#closed = false;
get closed() {
return this.#closed;
}

constructor(script: string, onclose: () => unknown = () => void 0) {
this.worker = new Worker(
new URL(script, import.meta.url),
{
type: "module",
deno: { namespace: true, permissions: "inherit" },
} as never,
);

this.worker.addEventListener("message", (evt) => {
console.log(`[Window "${script}"] Message:`, evt.data);
if (evt.data === "close") {
this.#closed = true;
onclose();
}
});

this.worker.addEventListener(
"messageerror",
(evt) => console.error(`[Window "${script}"] Message Error:`, evt.data),
);

this.worker.addEventListener(
"error",
(evt) => console.error(`[Window "${script}"] Error:`, evt),
);
}

terminate(): Promise<void> {
if (this.closed) {
return Promise.resolve();
}

return new Promise<void>((res) =>
setTimeout(() => {
this.worker.postMessage("unload");
this.worker.terminate();
this.#closed = true;
res();
}, 25)
);
}
}

// The Server

const html = `
<html>
<body>
<h1>Hello Deno!</h1>
<button onclick='fetch("./beep").then(res => res.text()).then(alert)'>
Beep
</button>
</body>
</html>
`;

const server = serve((req) => {
const pathname = new URL(req.url).pathname;

if (pathname === "/beep") {
return new Response("boop");
} else if (!pathname || pathname === "/" || pathname === "/index.html") {
return new Response(html, {
headers: { "Content-Type": "text/html" },
});
} else {
return new Response("Not Found", { status: 404 });
}
}, { port: 8000 });

console.log("[Main] Listening on http://localhost:8000");

// The Windows

// Our cleanup function to help Deno exit when the Windows are closed

let cleaning = false;
async function cleanup() {
if (cleaning) return;
cleaning = true;

await Promise.all(
[windowLocal, windowDeno].map((window) => window.terminate()),
);

Deno.exit();
}

console.log("[Main] Spawning Windows");

const windowLocal = new Window("./window-localhost.ts", cleanup);
const windowDeno = new Window("./window-denoland.ts", cleanup);

// Window 2

console.log("[Main] Done! Running Server");

await server;
20 changes: 20 additions & 0 deletions examples/multi-window/window-denoland.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Webview } from "../../mod.ts";

const webview = new Webview();

postMessage("open");

webview.navigate("https://deno.land/");

webview.bind("close", () => {
postMessage("close");
self.close();
});

addEventListener(
"onmessage",
(evt) =>
(evt as MessageEvent).data === "unload" ? webview.terminate() : void 0,
);

webview.run();
20 changes: 20 additions & 0 deletions examples/multi-window/window-localhost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Webview } from "../../mod.ts";

const webview = new Webview();

postMessage("open");

webview.navigate("http://localhost:8000");

webview.bind("close", () => {
postMessage("close");
self.close();
});

addEventListener(
"onmessage",
(evt) =>
(evt as MessageEvent).data === "unload" ? webview.terminate() : void 0,
);

webview.run();
2 changes: 1 addition & 1 deletion mod.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from "./src/webview.ts";
export { unload } from "./src/ffi.ts";
export { preload, unload } from "./src/ffi.ts";
57 changes: 52 additions & 5 deletions src/ffi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,68 @@ const policy = Deno.env.get("PLUGIN_URL") === undefined
const url = Deno.env.get("PLUGIN_URL") ??
`https://github.com/webview/webview_deno/releases/download/${version}/`;

if (Deno.build.os === "windows") {
const webview2loader = await download(`${url}WebView2Loader.dll`);
await Deno.copyFile(webview2loader, "./WebView2Loader.dll");
/**
* Checks for the existence of `./WebView2Loader.dll` for running on Windows
*
* @returns true if it exists, false if it doesn't
*/
async function checkForWebView2Loader(): Promise<boolean> {
return await Deno.stat("./WebView2Loader.dll").then(
() => true,
(e) => e instanceof Deno.errors.NotFound ? false : true,
);
}

// make sure we don't preload twice
let preloaded = false;
/**
* Loads the `./WebView2Loader.dll` for running on Windows.
* Removes old version if it already existed, and only runs once.
* Should be run on the main thread so that the `unload` gets hooked in properly, otherwise
* make sure `unload` gets called during the `window.onunload` event (after all windows are closed).
*
* Does not need to be run on non-windows platforms, but that is subject to change.
*/
export async function preload() {
if (preloaded) return;

if (Deno.build.os === "windows") {
if (await checkForWebView2Loader()) {
await Deno.remove("./WebView2Loader.dll");
}

const webview2loader = await download(`${url}WebView2Loader.dll`);
await Deno.copyFile(webview2loader, "./WebView2Loader.dll");

// deno-lint-ignore no-window-prefix
if (typeof window !== "undefined") window.addEventListener("unload", unload);
self.addEventListener("unload", unload);
}

preloaded = true;
}

/**
* Unload the library. Should only be run once all windows are closed.
* If `preload` was called in the main thread, this will automatically be called
* during the `window.onunload` event; otherwise, you may have to call this manually.
*/
export function unload() {
lib.close();
if (Deno.build.os === "windows") {
Deno.removeSync("./WebView2Loader.dll");
}
}

// Automatically run the preload if we're on windows and on the main thread.
if (Deno.build.os === "windows") {
if ((self as never)["window"]) {
await preload();
} else if (!await checkForWebView2Loader()) {
throw new Error(
"WebView2Loader.dll does not exist! Make sure to run preload() from the main thread.",
);
}
}

const lib = await prepare({
name: "webview_deno",
url,
Expand Down

0 comments on commit 755258f

Please sign in to comment.