├── .github
├── FUNDING.yml
└── workflows
│ ├── build.yml
│ ├── checks.yml
│ └── publish.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── LICENSE
├── README.md
├── deno.json
├── examples
├── bind.ts
├── local.ts
├── multi-window
│ ├── main.ts
│ ├── window-denoland.ts
│ └── window-localhost.ts
├── multiple.ts
├── remote.ts
├── run.ts
├── server.ts
├── ssr
│ ├── main.ts
│ ├── tsconfig.json
│ └── worker.tsx
└── user_agent.ts
├── images
└── webview_deno.png
├── mod.ts
├── script
├── build.bat
└── build.ts
├── src
├── ffi.ts
└── webview.ts
└── test_import_map.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | open_collective: denosaurs
2 | github: denosaurs
3 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - ".github/workflows/**"
9 | - "script/build.*"
10 | - "src/ffi.ts"
11 | - "webview/**"
12 | - ".gitmodules"
13 | - "deno.json"
14 |
15 | jobs:
16 | build:
17 | name: ${{ matrix.kind }} ${{ matrix.os }}
18 | runs-on: ${{ matrix.os }}
19 | timeout-minutes: 60
20 | strategy:
21 | matrix:
22 | os:
23 | [
24 | macos-latest,
25 | macos-13,
26 | windows-latest,
27 | ubuntu-latest,
28 | ubuntu-24.04-arm,
29 | ]
30 |
31 | steps:
32 | - name: Clone repository
33 | uses: actions/checkout@v4
34 | with:
35 | submodules: true
36 |
37 | - name: Install deno
38 | uses: denoland/setup-deno@v2
39 |
40 | - name: install webkit2gtk (Linux)
41 | if: startsWith(matrix.os, 'ubuntu')
42 | run: |
43 | sudo apt-get update
44 | sudo apt-get install -y webkitgtk-6.0 libwebkitgtk-6.0-dev cmake ninja-build clang pkg-config libgtk-4-dev
45 |
46 | - name: Install ninja (macOS)
47 | if: startsWith(matrix.os, 'macos')
48 | run: |
49 | brew install ninja
50 | brew install llvm
51 | echo "WEBVIEW_CLANG_FORMAT_EXE=$(brew --prefix llvm)/bin/clang-format" >> $GITHUB_ENV
52 |
53 | - name: Install ninja (Windows)
54 | if: matrix.os == 'windows-latest'
55 | run: |
56 | choco install ninja
57 |
58 | - name: Build dynamic library
59 | run: deno task build
60 |
61 | - name: Upload Artifacts
62 | uses: actions/upload-artifact@v3
63 | with:
64 | name: build
65 | path: |
66 | build/*.dll
67 | build/*.dylib
68 | build/*.so
69 |
70 | # - name: Release Plugin
71 | # uses: softprops/action-gh-release@master
72 | # env:
73 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
74 | # with:
75 | # tag_name: "webview_deno release"
76 | # draft: true
77 | # files: |
78 | # build/libwebview.x86_64.dylib
79 | # build/libwebview.aarch64.dylib
80 | # build/libwebview.x86_64.so
81 | # build/libwebview.aarch64.so
82 | # build/webview.dll
83 | # build/Webview2Loader.dll
84 |
--------------------------------------------------------------------------------
/.github/workflows/checks.yml:
--------------------------------------------------------------------------------
1 | name: Checks
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | checks:
7 | name: Checks
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout sources
11 | uses: actions/checkout@v4
12 |
13 | - name: Install stable deno
14 | uses: denoland/setup-deno@v2
15 |
16 | - name: Run check
17 | run: deno check mod.ts
18 |
19 | # TODO: Re-enable these tests
20 | # - name: Run test:doc
21 | # run: deno task test:doc
22 |
23 | - name: Run fmt
24 | run: deno fmt --check
25 |
26 | - name: Run lint
27 | run: deno lint
28 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: read
13 | id-token: write
14 | steps:
15 | - name: Clone repository
16 | uses: actions/checkout@v4
17 |
18 | - name: Install deno
19 | uses: denoland/setup-deno@v2
20 |
21 | - name: Publish to JSR
22 | run: deno publish
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ide
2 | .idea
3 | .vscode
4 |
5 | # plugin cache
6 | .deno_plugins/
7 | WebView2Loader.dll
8 |
9 | # webview build
10 | script/Microsoft*
11 | script/nuget.exe
12 | nuget.exe
13 | build/
14 |
15 | # MacOS
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "webview"]
2 | path = webview
3 | url = https://github.com/webview/webview.git
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog], and this project adheres to
6 | [Semantic Versioning].
7 |
8 | ## [0.6.0-pre.0] - 2021-05-12
9 |
10 | ## [0.5.6] - 2021-02-03
11 |
12 | ### Features
13 |
14 | - add perf example ([`9b852cd`])
15 |
16 | ### Bug Fixes
17 |
18 | - build ([`8af68f4`])
19 | - unbump webview-sys crate ([`c0d96d7`])
20 | - example ([`8114c4c`])
21 |
22 | ## [0.5.5] - 2020-12-04
23 |
24 | ## [0.5.4] - 2020-12-02
25 |
26 | ### Features
27 |
28 | - webview.iter for handling events ([`ce18528`])
29 |
30 | ## [0.5.3] - 2020-12-02
31 |
32 | ### Features
33 |
34 | - two-way deno bindings with support for external.invoke ([`050332f`])
35 |
36 | ## [0.5.2] - 2020-12-01
37 |
38 | ## [0.5.1] - 2020-12-01
39 |
40 | ### Bug Fixes
41 |
42 | - cache plugin by default ([`093a942`])
43 |
44 | ## [0.5.0] - 2020-12-01
45 |
46 | ### Bug Fixes
47 |
48 | - worker example ([`8b2fb91`])
49 | - typo in ci ([`900c023`])
50 |
51 | ## [0.4.7] - 2020-09-18
52 |
53 | ## [0.4.6] - 2020-09-02
54 |
55 | ## [0.4.5] - 2020-07-22
56 |
57 | ## [0.4.4] - 2020-07-22
58 |
59 | ### Features
60 |
61 | - :electric_plug: Use plug instead of plugin_prepare ([`4585e4b`])
62 |
63 | ## [0.4.3] - 2020-07-17
64 |
65 | ### Bug Fixes
66 |
67 | - update deno_core ([`20f0567`])
68 |
69 | ## [0.4.2] - 2020-06-12
70 |
71 | ### Features
72 |
73 | - :arrow_up: New release ([`74f989f`])
74 | - :construction: Attempt to add worker support to webview ([`6729fc2`])
75 |
76 | ### Bug Fixes
77 |
78 | - :arrow_up: Support deno 1.0.5 ([`3f181de`])
79 | - use official extension ([`f859467`])
80 |
81 | ## [0.4.1] - 2020-05-16
82 |
83 | ## [0.4.0] - 2020-05-15
84 |
85 | ## [0.3.3] - 2020-04-16
86 |
87 | ## [0.3.2] - 2020-04-15
88 |
89 | ## [0.3.1] - 2020-04-03
90 |
91 | ## [0.3.0] - 2020-03-21
92 |
93 | ## [0.2.2] - 2020-03-17
94 |
95 | ## [0.2.1] - 2020-03-16
96 |
97 | ## [0.2.0] - 2020-03-16
98 |
99 | ## [0.1.2] - 2020-03-13
100 |
101 | ### Bug Fixes
102 |
103 | - point releaseUrl to 0.1.1 ([`11baf05`])
104 |
105 | ## [0.1.1] - 2020-03-10
106 |
107 | ## [0.1.0] - 2020-03-10
108 |
109 | ## [0.0.2] - 2020-03-10
110 |
111 | ## [0.0.1] - 2020-03-06
112 |
113 | [keep a changelog]: https://keepachangelog.com/en/1.0.0/
114 | [semantic versioning]: https://semver.org/spec/v2.0.0.html
115 | [0.6.0-pre.0]: https://github.com/eliassjogreen/deno_webview/compare/0.5.6...0.6.0-pre.0
116 | [0.5.6]: https://github.com/eliassjogreen/deno_webview/compare/0.5.5...0.5.6
117 | [`9b852cd`]: https://github.com/eliassjogreen/deno_webview/commit/9b852cd13f8b2528896285db89f1ba12122ddfb4
118 | [`8af68f4`]: https://github.com/eliassjogreen/deno_webview/commit/8af68f4adb75dd1467815accd641b450708eed00
119 | [`c0d96d7`]: https://github.com/eliassjogreen/deno_webview/commit/c0d96d756dca3d5296c025d42367adf137f2d17c
120 | [`8114c4c`]: https://github.com/eliassjogreen/deno_webview/commit/8114c4cb639ed7a9d55c970edab0c99e3281d3d9
121 | [0.5.5]: https://github.com/eliassjogreen/deno_webview/compare/0.5.4...0.5.5
122 | [0.5.4]: https://github.com/eliassjogreen/deno_webview/compare/0.5.3...0.5.4
123 | [`ce18528`]: https://github.com/eliassjogreen/deno_webview/commit/ce18528abbdf1ac6df52e5272b6f56633cb36d73
124 | [0.5.3]: https://github.com/eliassjogreen/deno_webview/compare/0.5.2...0.5.3
125 | [`050332f`]: https://github.com/eliassjogreen/deno_webview/commit/050332f6362f58211002a8854740e8ee03e6092a
126 | [0.5.2]: https://github.com/eliassjogreen/deno_webview/compare/0.5.1...0.5.2
127 | [0.5.1]: https://github.com/eliassjogreen/deno_webview/compare/0.5.0...0.5.1
128 | [`093a942`]: https://github.com/eliassjogreen/deno_webview/commit/093a94211a3be1123ab4f11e3803809048177f4c
129 | [0.5.0]: https://github.com/eliassjogreen/deno_webview/compare/0.4.7...0.5.0
130 | [`8b2fb91`]: https://github.com/eliassjogreen/deno_webview/commit/8b2fb913ae7abab5ea955f20d66d886352e29fa3
131 | [`900c023`]: https://github.com/eliassjogreen/deno_webview/commit/900c02311f4e59d85209a72a2f18c0aee11dbb16
132 | [0.4.7]: https://github.com/eliassjogreen/deno_webview/compare/0.4.6...0.4.7
133 | [0.4.6]: https://github.com/eliassjogreen/deno_webview/compare/0.4.5...0.4.6
134 | [0.4.5]: https://github.com/eliassjogreen/deno_webview/compare/0.4.4...0.4.5
135 | [0.4.4]: https://github.com/eliassjogreen/deno_webview/compare/0.4.3...0.4.4
136 | [`4585e4b`]: https://github.com/eliassjogreen/deno_webview/commit/4585e4b9e078bc5a734c6651f73821f2dde41cef
137 | [0.4.3]: https://github.com/eliassjogreen/deno_webview/compare/0.4.2...0.4.3
138 | [`20f0567`]: https://github.com/eliassjogreen/deno_webview/commit/20f05678e8765584fdf2f787ec9dfc82a3d86a05
139 | [0.4.2]: https://github.com/eliassjogreen/deno_webview/compare/0.4.1...0.4.2
140 | [`74f989f`]: https://github.com/eliassjogreen/deno_webview/commit/74f989f1777b9abfda8613bd28d2955b7723daf9
141 | [`6729fc2`]: https://github.com/eliassjogreen/deno_webview/commit/6729fc2a0b9cf1919f5562eb5b09f6922df297de
142 | [`3f181de`]: https://github.com/eliassjogreen/deno_webview/commit/3f181ded9aaccd017e72e6fba419af07b24861b3
143 | [`f859467`]: https://github.com/eliassjogreen/deno_webview/commit/f859467bb009a174f9a65147b421da52c75980a5
144 | [0.4.1]: https://github.com/eliassjogreen/deno_webview/compare/0.4.0...0.4.1
145 | [0.4.0]: https://github.com/eliassjogreen/deno_webview/compare/0.3.3...0.4.0
146 | [0.3.3]: https://github.com/eliassjogreen/deno_webview/compare/0.3.2...0.3.3
147 | [0.3.2]: https://github.com/eliassjogreen/deno_webview/compare/0.3.1...0.3.2
148 | [0.3.1]: https://github.com/eliassjogreen/deno_webview/compare/0.3.0...0.3.1
149 | [0.3.0]: https://github.com/eliassjogreen/deno_webview/compare/0.2.2...0.3.0
150 | [0.2.2]: https://github.com/eliassjogreen/deno_webview/compare/0.2.1...0.2.2
151 | [0.2.1]: https://github.com/eliassjogreen/deno_webview/compare/0.2.0...0.2.1
152 | [0.2.0]: https://github.com/eliassjogreen/deno_webview/compare/0.1.2...0.2.0
153 | [0.1.2]: https://github.com/eliassjogreen/deno_webview/compare/0.1.1...0.1.2
154 | [`11baf05`]: https://github.com/eliassjogreen/deno_webview/commit/11baf05dfdc1581f92533a7cb98f390ee11ef6ce
155 | [0.1.1]: https://github.com/eliassjogreen/deno_webview/compare/0.1.0...0.1.1
156 | [0.1.0]: https://github.com/eliassjogreen/deno_webview/compare/0.0.2...0.1.0
157 | [0.0.2]: https://github.com/eliassjogreen/deno_webview/compare/0.0.1...0.0.2
158 | [0.0.1]: https://github.com/eliassjogreen/deno_webview/compare/0.0.1
159 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2025 the webview team
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # webview_deno
2 |
3 | [](https://github.com/webview/webview_deno/stargazers)
4 | [](https://github.com/webview/webview_deno/issues)
5 | [](https://github.com/webview/webview_deno/actions)
6 | [](https://github.com/webview/webview_deno/releases/latest/)
7 | [](https://jsr.io/@webview/webview)
8 | [](https://github.com/denoland/deno)
9 | [](https://doc.deno.land/https/deno.land/x/webview/mod.ts)
10 | [](https://github.com/webview/webview_deno/blob/master/LICENSE)
11 |
12 | [deno](https://github.com/denoland/deno) bindings for
13 | [webview](https://github.com/webview/webview)
14 |
15 | Webview is a tiny cross-platform library to make **web-based GUIs for desktop
16 | applications**.
17 |
18 | ---
19 |
20 | > ⚠️ This project is still in development. Expect breaking changes.
21 |
22 | ---
23 |
24 | 
25 |
26 | ## Installation
27 |
28 | Webview is published to [jsr.io](https://jsr.io/@webview/webview) and
29 | [deno.land](https://deno.land/x/webview). The recommended way to use it is to
30 | use JSR:
31 |
32 | ```bash
33 | deno add jsr:@webview/webview
34 | ```
35 |
36 | or without the CLI:
37 |
38 | ```typescript
39 | import { Webview } from "jsr:@webview/webview";
40 | ```
41 |
42 | ## Example
43 |
44 | ```typescript
45 | import { Webview } from "@webview/webview";
46 |
47 | const html = `
48 |
49 |
50 | Hello from deno v${Deno.version.deno}
51 |
52 |
53 | `;
54 |
55 | const webview = new Webview();
56 |
57 | webview.navigate(`data:text/html,${encodeURIComponent(html)}`);
58 | webview.run();
59 | ```
60 |
61 | You can run this example directly from the web:
62 |
63 | ```bash
64 | deno run -Ar --unstable https://deno.land/x/webview/examples/local.ts
65 | ```
66 |
67 | or in your development environment:
68 |
69 | ```bash
70 | deno run -Ar --unstable examples/local.ts
71 | ```
72 |
73 | you can find other examples in the [`examples/`](examples) directory.
74 |
75 | ## Documentation
76 |
77 | You can find the official documentation
78 | [here](https://jsr.io/@webview/webview/doc).
79 |
80 | ## Development
81 |
82 | ### Prerequisites
83 |
84 | #### Linux
85 |
86 | - [webkit2gtk](https://webkitgtk.org/) (to install using apt:
87 | `sudo apt-get install libwebkit2gtk-4.0-dev`)
88 |
89 | ### Building
90 |
91 | Make sure to init the webview submodule with:
92 |
93 | ```bash
94 | $ git submodule update --init --recursive
95 | ```
96 |
97 | Building on Windows requires admin privileges.
98 |
99 | ```bash
100 | $ deno task build
101 | ```
102 |
103 | ### Running
104 |
105 | To run webview_deno without automatically downloading the binaries from
106 | [releases](https://github.com/webview/webview_deno/releases) you will need to
107 | use the environment variable `PLUGIN_URL` and set it to the path where the built
108 | binaries are located. This is usually `file://./target/release`.
109 |
110 | ```bash
111 | $ deno task build
112 | $ PLUGIN_URL=./build/
113 | $ deno run --unstable -A examples/local.ts
114 | ```
115 |
116 | or
117 |
118 | ```bash
119 | $ deno task run examples/local.ts
120 | ```
121 |
122 | or if you have the webview library already built and didn't make any changes to
123 | it, you can skip the building step with:
124 |
125 | ```bash
126 | $ deno task run:fast examples/local.ts
127 | ```
128 |
129 | ## Environment variables
130 |
131 | - `PLUGIN_URL` - Set a custom library URL. Defaults to the latest release assets
132 | on Github. Setting this also disables cache for `plug`.
133 |
134 | ## Dependencies
135 |
136 | - [plug](https://jsr.io/@denosaurs/plug)
137 | - [webview](https://github.com/webview/webview)
138 |
139 | ## Other
140 |
141 | ### Contribution
142 |
143 | Pull request, issues and feedback are very welcome. Code style is formatted with
144 | `deno task fmt`, linted with `deno task lint` and commit messages are done
145 | following [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
146 | spec.
147 |
148 | ### Licence
149 |
150 | Copyright 2020-2022, the webview_deno team. All rights reserved. MIT license.
151 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@webview/webview",
3 | "version": "0.9.0",
4 | "exports": "./mod.ts",
5 | "lock": false,
6 | "tasks": {
7 | "build": "deno run -A script/build.ts",
8 | "run": "deno task build && export PLUGIN_URL=\"./build/\" && deno run -A --unstable-ffi",
9 | "run:fast": "export PLUGIN_URL=\"./build/\" && deno run -A --unstable-ffi"
10 | },
11 | "unstable": ["ffi"],
12 | "fmt": {
13 | "exclude": ["webview/"]
14 | },
15 | "imports": { "@denosaurs/plug": "jsr:@denosaurs/plug@^1.0" }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/bind.ts:
--------------------------------------------------------------------------------
1 | import { Webview } from "../mod.ts";
2 |
3 | const html = `
4 |
5 |
6 | Hello from deno v${Deno.version.deno}
7 |
10 |
11 |
12 | `;
13 |
14 | const webview = new Webview();
15 |
16 | webview.navigate(`data:text/html,${encodeURIComponent(html)}`);
17 |
18 | let counter = 0;
19 | webview.bind("press", (a, b, c) => {
20 | console.log(a, b, c);
21 |
22 | return { times: counter++ };
23 | });
24 |
25 | webview.bind("log", (...args) => console.log(...args));
26 |
27 | webview.run();
28 |
--------------------------------------------------------------------------------
/examples/local.ts:
--------------------------------------------------------------------------------
1 | import { Webview } from "../mod.ts";
2 |
3 | const html = `
4 |
5 |
6 | Hello from deno v${Deno.version.deno}
7 |
8 |
9 | `;
10 |
11 | const webview = new Webview();
12 |
13 | webview.navigate(`data:text/html,${encodeURIComponent(html)}`);
14 | webview.run();
15 |
--------------------------------------------------------------------------------
/examples/multi-window/main.ts:
--------------------------------------------------------------------------------
1 | import { serve } from "https://deno.land/std@0.157.0/http/server.ts";
2 | import { preload } from "../../mod.ts";
3 | await preload();
4 |
5 | /**
6 | * A window helper class to handle the workers
7 | */
8 | class Window {
9 | readonly worker: Worker;
10 | #closed = false;
11 | get closed() {
12 | return this.#closed;
13 | }
14 |
15 | constructor(script: string, onclose: () => unknown = () => void 0) {
16 | this.worker = new Worker(
17 | new URL(script, import.meta.url),
18 | {
19 | type: "module",
20 | deno: { namespace: true, permissions: "inherit" },
21 | } as never,
22 | );
23 |
24 | this.worker.addEventListener("message", (evt) => {
25 | console.log(`[Window "${script}"] Message:`, evt.data);
26 | if (evt.data === "close") {
27 | this.#closed = true;
28 | onclose();
29 | }
30 | });
31 |
32 | this.worker.addEventListener(
33 | "messageerror",
34 | (evt) => console.error(`[Window "${script}"] Message Error:`, evt.data),
35 | );
36 |
37 | this.worker.addEventListener(
38 | "error",
39 | (evt) => console.error(`[Window "${script}"] Error:`, evt),
40 | );
41 | }
42 |
43 | terminate(): Promise {
44 | if (this.closed) {
45 | return Promise.resolve();
46 | }
47 |
48 | return new Promise((res) =>
49 | setTimeout(() => {
50 | this.worker.postMessage("unload");
51 | this.worker.terminate();
52 | this.#closed = true;
53 | res();
54 | }, 25)
55 | );
56 | }
57 | }
58 |
59 | // The Server
60 |
61 | const html = `
62 |
63 |
64 | Hello Deno!
65 |
68 |
69 |
70 | `;
71 |
72 | const server = serve((req) => {
73 | const pathname = new URL(req.url).pathname;
74 |
75 | if (pathname === "/beep") {
76 | return new Response("boop");
77 | } else if (!pathname || pathname === "/" || pathname === "/index.html") {
78 | return new Response(html, {
79 | headers: { "Content-Type": "text/html" },
80 | });
81 | } else {
82 | return new Response("Not Found", { status: 404 });
83 | }
84 | }, { port: 8000 });
85 |
86 | console.log("[Main] Listening on http://localhost:8000");
87 |
88 | // The Windows
89 |
90 | // Our cleanup function to help Deno exit when the Windows are closed
91 |
92 | let cleaning = false;
93 | async function cleanup() {
94 | if (cleaning) return;
95 | cleaning = true;
96 |
97 | await Promise.all(
98 | [windowLocal, windowDeno].map((window) => window.terminate()),
99 | );
100 |
101 | Deno.exit();
102 | }
103 |
104 | console.log("[Main] Spawning Windows");
105 |
106 | const windowLocal = new Window("./window-localhost.ts", cleanup);
107 | const windowDeno = new Window("./window-denoland.ts", cleanup);
108 |
109 | // Window 2
110 |
111 | console.log("[Main] Done! Running Server");
112 |
113 | await server;
114 |
--------------------------------------------------------------------------------
/examples/multi-window/window-denoland.ts:
--------------------------------------------------------------------------------
1 | import { Webview } from "../../mod.ts";
2 |
3 | const webview = new Webview();
4 |
5 | postMessage("open");
6 |
7 | webview.navigate("https://deno.land/");
8 |
9 | webview.bind("close", () => {
10 | postMessage("close");
11 | self.close();
12 | });
13 |
14 | addEventListener(
15 | "onmessage",
16 | (evt) =>
17 | (evt as MessageEvent).data === "unload" ? webview.terminate() : void 0,
18 | );
19 |
20 | webview.run();
21 |
--------------------------------------------------------------------------------
/examples/multi-window/window-localhost.ts:
--------------------------------------------------------------------------------
1 | import { Webview } from "../../mod.ts";
2 |
3 | const webview = new Webview();
4 |
5 | postMessage("open");
6 |
7 | webview.navigate("http://localhost:8000");
8 |
9 | webview.bind("close", () => {
10 | postMessage("close");
11 | self.close();
12 | });
13 |
14 | addEventListener(
15 | "onmessage",
16 | (evt) =>
17 | (evt as MessageEvent).data === "unload" ? webview.terminate() : void 0,
18 | );
19 |
20 | webview.run();
21 |
--------------------------------------------------------------------------------
/examples/multiple.ts:
--------------------------------------------------------------------------------
1 | import { Webview } from "../mod.ts";
2 |
3 | const webview1 = new Webview();
4 | webview1.navigate("https://deno.land/");
5 |
6 | const webview2 = new Webview();
7 | webview2.navigate("https://google.com/");
8 |
9 | // NOTE: Due to design limitations, you can only have one webview
10 | // instance **at a time**
11 | const p1 = webview1.run();
12 | const p2 = webview2.run();
13 |
14 | await Promise.all([p1, p2]);
15 |
--------------------------------------------------------------------------------
/examples/remote.ts:
--------------------------------------------------------------------------------
1 | import { Webview } from "../mod.ts";
2 |
3 | const webview = new Webview();
4 | webview.navigate("https://deno.land/");
5 | webview.run();
6 |
--------------------------------------------------------------------------------
/examples/run.ts:
--------------------------------------------------------------------------------
1 | import { Webview } from "../mod.ts";
2 |
3 | const webview = new Webview();
4 | webview.run();
5 |
--------------------------------------------------------------------------------
/examples/server.ts:
--------------------------------------------------------------------------------
1 | import { serve } from "https://deno.land/std@0.157.0/http/server.ts";
2 | import { Webview } from "../mod.ts";
3 |
4 | const controller = new AbortController();
5 | const server = serve(() =>
6 | new Response("Hello World
", {
7 | headers: new Headers({
8 | "content-type": "text/html",
9 | }),
10 | }), { port: 8080, signal: controller.signal });
11 |
12 | const webview = new Webview();
13 |
14 | webview.navigate(`http://localhost:8080`);
15 |
16 | webview.run();
17 | controller.abort();
18 | await server;
19 |
--------------------------------------------------------------------------------
/examples/ssr/main.ts:
--------------------------------------------------------------------------------
1 | import { dirname, join } from "https://deno.land/std@0.157.0/path/mod.ts";
2 | import { Webview } from "../../mod.ts";
3 |
4 | const worker = new Worker(
5 | join(dirname(import.meta.url), "worker.tsx"),
6 | { type: "module" },
7 | );
8 |
9 | const webview = new Webview();
10 | webview.navigate("http://localhost:8000/");
11 |
12 | console.log("[runner] worker started");
13 | webview.run();
14 | worker.terminate();
15 |
--------------------------------------------------------------------------------
/examples/ssr/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "deno.ns", "deno.unstable"],
4 | "jsxFactory": "h"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/ssr/worker.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | ///
3 | ///
4 | ///
5 | ///
6 |
7 | import { serve } from "https://deno.land/std@0.157.0/http/server.ts";
8 | // deno-lint-ignore verbatim-module-syntax
9 | import { h, ssr, tw } from "https://crux.land/nanossr@0.0.1";
10 |
11 | const Hello = (props: { name: string }) => (
12 |
13 |
14 | Hello {props.name}!
15 |
16 |
17 | );
18 |
19 | const server = serve((req) => {
20 | console.log(req);
21 | const url = new URL(req.url);
22 | const name = url.searchParams.get("name") ?? "world";
23 | return ssr(() => );
24 | }, { port: 8000 });
25 |
26 | console.log("[runner] Listening on http://localhost:8000");
27 | await server;
28 |
--------------------------------------------------------------------------------
/examples/user_agent.ts:
--------------------------------------------------------------------------------
1 | import { Webview } from "../mod.ts";
2 |
3 | const html = `
4 |
5 |
6 |
7 |
8 |
9 | `;
10 |
11 | const webview = new Webview();
12 |
13 | webview.navigate(`data:text/html,${encodeURIComponent(html)}`);
14 | webview.run();
15 |
--------------------------------------------------------------------------------
/images/webview_deno.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webview/webview_deno/656b7b042493b35c3ae6ef67b8cb566b582dccaa/images/webview_deno.png
--------------------------------------------------------------------------------
/mod.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Webview is a tiny cross-platform library to make web-based GUIs for desktop
3 | * applications.
4 | *
5 | * @example
6 | * ```
7 | * import { Webview } from "@webview/webview";
8 | *
9 | * const html = `
10 | *
11 | *
12 | * Hello from deno v${Deno.version.deno}
13 | *
14 | *
15 | * `;
16 | *
17 | * const webview = new Webview();
18 | *
19 | * webview.navigate(`data:text/html,${encodeURIComponent(html)}`);
20 | * webview.run();
21 | * ```
22 | *
23 | * @module
24 | */
25 |
26 | export * from "./src/webview.ts";
27 | export { preload, unload } from "./src/ffi.ts";
28 |
--------------------------------------------------------------------------------
/script/build.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | echo Looking for vswhere.exe...
3 | set "vswhere=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
4 | if not exist "%vswhere%" set "vswhere=%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe"
5 | if not exist "%vswhere%" (
6 | echo ERROR: Failed to find vswhere.exe
7 | exit /b 1
8 | )
9 | echo Found %vswhere%
10 |
11 | echo Looking for VC...
12 | for /f "usebackq tokens=*" %%i in (`"%vswhere%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do (
13 | set vc_dir=%%i
14 | )
15 | if not exist "%vc_dir%\Common7\Tools\vsdevcmd.bat" (
16 | echo ERROR: Failed to find VC tools x86/x64
17 | exit /b 1
18 | )
19 | echo Found %vc_dir%
20 |
21 | call "%vc_dir%\Common7\Tools\vsdevcmd.bat" -arch=x64 -host_arch=x64
22 | cd %~dp0..\webview
23 |
24 | cmake -G "Ninja Multi-Config" -B build -S . ^
25 | -DWEBVIEW_BUILD_DOCS=OFF ^
26 | -DWEBVIEW_USE_CLANG_TIDY=OFF ^
27 | -DWEBVIEW_USE_CLANG_FORMAT=OFF
28 |
29 | cmake --build build --config Release
30 |
--------------------------------------------------------------------------------
/script/build.ts:
--------------------------------------------------------------------------------
1 | import { $ } from "jsr:@david/dax@0.42.0";
2 |
3 | async function findClangFormat() {
4 | let WEBVIEW_CLANG_FORMAT_EXE = Deno.env.get("WEBVIEW_CLANG_FORMAT_EXE");
5 | if (WEBVIEW_CLANG_FORMAT_EXE) {
6 | return WEBVIEW_CLANG_FORMAT_EXE;
7 | }
8 |
9 | WEBVIEW_CLANG_FORMAT_EXE = await $.which("clang-format");
10 | if (WEBVIEW_CLANG_FORMAT_EXE) {
11 | return WEBVIEW_CLANG_FORMAT_EXE;
12 | }
13 |
14 | if (await $.commandExists("brew")) {
15 | const llvm = await $`brew --prefix llvm`.text();
16 | return `${llvm}/bin/clang-format"`;
17 | }
18 |
19 | $.logError(
20 | "error",
21 | "clang-format not found. Please install clang-format using brew or set the WEBVIEW_CLANG_FORMAT_EXE environment variable.",
22 | );
23 | Deno.exit(1);
24 | }
25 |
26 | $.setPrintCommand(true);
27 |
28 | await $.path("./build").ensureDir();
29 | switch (Deno.build.os) {
30 | case "windows": {
31 | await $`script/build.bat`;
32 | await $`cp webview/build/core/Release/webview.dll build/webview.dll`;
33 | break;
34 | }
35 | case "linux": {
36 | $.cd("webview");
37 | await $`export PATH=/usr/lib/llvm14/bin/:/usr/lib/llvm-14/bin/:/usr/lib64/llvm15/bin/:$PATH`;
38 | await $`cmake -G Ninja -B build -S . \
39 | -D CMAKE_BUILD_TYPE=Release \
40 | -D WEBVIEW_WEBKITGTK_API=6.0 \
41 | -DWEBVIEW_ENABLE_CHECKS=false \
42 | -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/host-llvm.cmake \
43 | -DWEBVIEW_USE_CLANG_TIDY=OFF \
44 | -DWEBVIEW_BUILD_DOCS=OFF \
45 | -DWEBVIEW_USE_CLANG_FORMAT=OFF`;
46 | await $`cmake --build build`;
47 | await $`cp build/core/libwebview.so ../build/libwebview.${Deno.build.arch}.so`;
48 | await $`strip ../build/libwebview.${Deno.build.arch}.so`;
49 | break;
50 | }
51 | case "darwin": {
52 | $.cd("webview");
53 | await $`cmake -G "Ninja Multi-Config" -B build -S . \
54 | -DCMAKE_BUILD_TYPE=Release \
55 | -DWEBVIEW_BUILD_TESTS=OFF \
56 | -DWEBVIEW_BUILD_EXAMPLES=OFF \
57 | -DWEBVIEW_USE_CLANG_TOOLS=OFF \
58 | -DWEBVIEW_ENABLE_CHECKS=OFF \
59 | -DWEBVIEW_USE_CLANG_TIDY=OFF \
60 | -DWEBVIEW_BUILD_DOCS=OFF \
61 | -DWEBVIEW_USE_CLANG_FORMAT=OFF \
62 | -DWEBVIEW_CLANG_FORMAT_EXE=${await findClangFormat()}`;
63 | await $`cmake --build build --config Release`;
64 | await $`cp build/core/Release/libwebview.dylib ../build/libwebview.${Deno.build.arch}.dylib`;
65 | await $`strip -x -S ../build/libwebview.${Deno.build.arch}.dylib`;
66 | break;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/ffi.ts:
--------------------------------------------------------------------------------
1 | import manifest from "../deno.json" with { type: "json" };
2 |
3 | import { dlopen, download } from "@denosaurs/plug";
4 | import type { Webview } from "./webview.ts";
5 |
6 | const version = manifest.version;
7 | const cache = Deno.env.get("PLUGIN_URL") === undefined ? "use" : "reloadAll";
8 | const url = Deno.env.get("PLUGIN_URL") ??
9 | `https://github.com/webview/webview_deno/releases/download/${version}/`;
10 |
11 | const encoder = new TextEncoder();
12 |
13 | /**
14 | * Encodes a string to a null terminated string
15 | *
16 | * @param value The intput string
17 | * @returns A null terminated `Uint8Array` of the input string
18 | */
19 | export function encodeCString(value: string) {
20 | return encoder.encode(value + "\0");
21 | }
22 |
23 | /**
24 | * Checks for the existence of `./WebView2Loader.dll` for running on Windows.
25 | *
26 | * @returns true if it exists, false if it doesn't
27 | */
28 | async function checkForWebView2Loader(): Promise {
29 | return await Deno.stat("./WebView2Loader.dll").then(
30 | () => true,
31 | (e) => e instanceof Deno.errors.NotFound ? false : true,
32 | );
33 | }
34 |
35 | // make sure we don't preload twice
36 | let preloaded = false;
37 |
38 | /**
39 | * All active webview instances. This is internally used for automatically
40 | * destroying all instances once {@link unload} is called.
41 | */
42 | export const instances: Webview[] = [];
43 |
44 | /**
45 | * Loads the `./WebView2Loader.dll` for running on Windows. Removes old version
46 | * if it already existed, and only runs once. Should be run on the main thread
47 | * so that the `unload` gets hooked in properly, otherwise make sure `unload`
48 | * gets called during the `window.onunload` event (after all windows are
49 | * closed).
50 | *
51 | * Does not need to be run on non-windows platforms, but that is subject to change.
52 | */
53 | export async function preload() {
54 | if (preloaded) return;
55 |
56 | if (Deno.build.os === "windows") {
57 | if (await checkForWebView2Loader()) {
58 | await Deno.remove("./WebView2Loader.dll");
59 | }
60 |
61 | const webview2loader = await download({
62 | url: `${url}/WebView2Loader.dll`,
63 | cache,
64 | });
65 | await Deno.copyFile(webview2loader, "./WebView2Loader.dll");
66 |
67 | self.addEventListener("unload", unload);
68 | }
69 |
70 | preloaded = true;
71 | }
72 |
73 | /**
74 | * Unload the library and destroy all webview instances. Should only be run
75 | * once all windows are closed. If `preload` was called in the main thread,
76 | * this will automatically be called during the `window.onunload` event;
77 | * otherwise, you may have to call this manually.
78 | */
79 | export function unload() {
80 | for (const instance of instances) {
81 | instance.destroy();
82 | }
83 | lib.close();
84 | if (Deno.build.os === "windows") {
85 | Deno.removeSync("./WebView2Loader.dll");
86 | }
87 | }
88 |
89 | // Automatically run the preload if we're on windows and on the main thread.
90 | if (Deno.build.os === "windows") {
91 | if (!await checkForWebView2Loader()) {
92 | if (self === globalThis) {
93 | await preload();
94 | } else {
95 | throw new Error(
96 | "WebView2Loader.dll does not exist! Make sure to run preload() from the main thread.",
97 | );
98 | }
99 | }
100 | }
101 |
102 | export const lib = await dlopen(
103 | {
104 | name: "webview",
105 | url,
106 | cache,
107 | suffixes: {
108 | linux: `.${Deno.build.arch}`,
109 | darwin: `.${Deno.build.arch}`,
110 | },
111 | },
112 | {
113 | "webview_create": {
114 | parameters: ["i32", "pointer"],
115 | result: "pointer",
116 | },
117 | "webview_destroy": {
118 | parameters: ["pointer"],
119 | result: "void",
120 | },
121 | "webview_run": {
122 | parameters: ["pointer"],
123 | result: "void",
124 | },
125 | "webview_terminate": {
126 | parameters: ["pointer"],
127 | result: "void",
128 | },
129 | // "webview_dispatch": {
130 | // parameters: ["pointer", { function: { parameters: ["pointer", "pointer"], result: "void" } }, "pointer"],
131 | // result: "void",
132 | // },
133 | "webview_get_window": {
134 | parameters: ["pointer"],
135 | result: "pointer",
136 | },
137 | "webview_set_title": {
138 | parameters: ["pointer", "buffer"],
139 | result: "void",
140 | },
141 | "webview_set_size": {
142 | parameters: ["pointer", "i32", "i32", "i32"],
143 | result: "void",
144 | },
145 | "webview_navigate": {
146 | parameters: ["pointer", "buffer"],
147 | result: "void",
148 | },
149 | "webview_set_html": {
150 | parameters: ["pointer", "pointer"],
151 | result: "void",
152 | },
153 | "webview_init": {
154 | parameters: ["pointer", "buffer"],
155 | result: "void",
156 | },
157 | "webview_eval": {
158 | parameters: ["pointer", "buffer"],
159 | result: "void",
160 | },
161 | "webview_bind": {
162 | parameters: ["pointer", "buffer", "function", "pointer"],
163 | result: "void",
164 | },
165 | "webview_unbind": {
166 | parameters: ["pointer", "buffer"],
167 | result: "void",
168 | },
169 | "webview_return": {
170 | parameters: ["pointer", "buffer", "i32", "buffer"],
171 | result: "void",
172 | },
173 | } as const,
174 | );
175 |
--------------------------------------------------------------------------------
/src/webview.ts:
--------------------------------------------------------------------------------
1 | import { encodeCString, instances, lib } from "./ffi.ts";
2 |
3 | /** Window size hints */
4 | export type SizeHint = typeof SizeHint[keyof typeof SizeHint];
5 |
6 | /** Window size hints */
7 | export const SizeHint = {
8 | /** Width and height are default size */
9 | NONE: 0,
10 | /** Width and height are minimum bounds */
11 | MIN: 1,
12 | /** Width and height are maximum bounds */
13 | MAX: 2,
14 | /** Window size can not be changed by a user */
15 | FIXED: 3,
16 | } as const;
17 |
18 | /** Window size */
19 | export interface Size {
20 | /** The width of the window */
21 | width: number;
22 | /** The height of the window */
23 | height: number;
24 | /** The window size hint */
25 | hint: SizeHint;
26 | }
27 |
28 | /**
29 | * An instance of a webview window.
30 | *
31 | * ## Examples
32 | *
33 | * ### Local
34 | *
35 | * ```ts
36 | * import { Webview } from "../mod.ts";
37 | *
38 | * const html = `
39 | *
40 | *
41 | * Hello from deno v${Deno.version.deno}
42 | *
43 | *
44 | * `;
45 | *
46 | * const webview = new Webview();
47 | *
48 | * webview.navigate(`data:text/html,${encodeURIComponent(html)}`);
49 | * webview.run();
50 | * ```
51 | *
52 | * ### Remote
53 | *
54 | * ```ts
55 | * import { Webview } from "../mod.ts";
56 | *
57 | * const webview = new Webview();
58 | * webview.navigate("https://deno.land/");
59 | * webview.run();
60 | * ```
61 | */
62 | export class Webview {
63 | #handle: Deno.PointerValue = null;
64 | #callbacks: Map<
65 | string,
66 | Deno.UnsafeCallback<{
67 | parameters: readonly "pointer"[];
68 | result: "void";
69 | }>
70 | > = new Map();
71 |
72 | /** **UNSAFE**: Highly unsafe API, beware!
73 | *
74 | * An unsafe pointer to the webview
75 | */
76 | get unsafeHandle(): Deno.PointerValue {
77 | return this.#handle;
78 | }
79 |
80 | /** **UNSAFE**: Highly unsafe API, beware!
81 | *
82 | * An unsafe pointer to the webviews platform specific native window handle.
83 | * When using GTK backend the pointer is `GtkWindow` pointer, when using Cocoa
84 | * backend the pointer is `NSWindow` pointer, when using Win32 backend the
85 | * pointer is `HWND` pointer.
86 | */
87 | get unsafeWindowHandle(): Deno.PointerValue {
88 | return lib.symbols.webview_get_window(this.#handle);
89 | }
90 |
91 | /**
92 | * Sets the native window size
93 | *
94 | * ## Example
95 | *
96 | * ```ts
97 | * import { Webview, SizeHint } from "../mod.ts";
98 | *
99 | * const webview = new Webview();
100 | * webview.navigate("https://deno.land/");
101 | *
102 | * // Change from the default size to a small fixed window
103 | * webview.size = {
104 | * width: 200,
105 | * height: 200,
106 | * hint: SizeHint.FIXED
107 | * };
108 | *
109 | * webview.run();
110 | * ```
111 | */
112 | set size(
113 | { width, height, hint }: Size,
114 | ) {
115 | lib.symbols.webview_set_size(this.#handle, width, height, hint);
116 | }
117 |
118 | /**
119 | * Sets the native window title
120 | *
121 | * ## Example
122 | *
123 | * ```ts
124 | * import { Webview } from "../mod.ts";
125 | *
126 | * const webview = new Webview();
127 | * webview.navigate("https://deno.land/");
128 | *
129 | * // Set the window title to "Hello world!"
130 | * webview.title = "Hello world!";
131 | *
132 | * webview.run();
133 | * ```
134 | */
135 | set title(title: string) {
136 | lib.symbols.webview_set_title(this.#handle, encodeCString(title));
137 | }
138 |
139 | /** **UNSAFE**: Highly unsafe API, beware!
140 | *
141 | * Creates a new webview instance from a webview handle.
142 | *
143 | * @param handle A previously created webview instances handle
144 | */
145 | constructor(handle: Deno.PointerValue);
146 | /**
147 | * Creates a new webview instance.
148 | *
149 | * ## Example
150 | *
151 | * ```ts
152 | * import { Webview, SizeHint } from "../mod.ts";
153 | *
154 | * // Create a new webview and change from the default size to a small fixed window
155 | * const webview = new Webview(true, {
156 | * width: 200,
157 | * height: 200,
158 | * hint: SizeHint.FIXED
159 | * });
160 | *
161 | * webview.navigate("https://deno.land/");
162 | * webview.run();
163 | * ```
164 | *
165 | * @param debug Defaults to false, when true developer tools are enabled
166 | * for supported platforms
167 | * @param size The window size, default to 1024x768 with no size hint. Set
168 | * this to undefined if you do not want to automatically resize the window.
169 | * This may cause issues for MacOS where the window is invisible until
170 | * resized.
171 | * @param window **UNSAFE**: Highly unsafe API, beware! An unsafe pointer to
172 | * the platforms specific native window handle. If null or undefined a new
173 | * window is created. If it's non-null - then child WebView is embedded into
174 | * the given parent window. Otherwise a new window is created. Depending on
175 | * the platform, a `GtkWindow`, `NSWindow` or `HWND` pointer can be passed
176 | * here.
177 | */
178 | constructor(
179 | debug?: boolean,
180 | size?: Size,
181 | window?: Deno.PointerValue | null,
182 | );
183 | constructor(
184 | debugOrHandle: boolean | Deno.PointerValue = false,
185 | size: Size | undefined = { width: 1024, height: 768, hint: SizeHint.NONE },
186 | window: Deno.PointerValue | null = null,
187 | ) {
188 | this.#handle =
189 | typeof debugOrHandle === "bigint" || typeof debugOrHandle === "number"
190 | ? debugOrHandle
191 | : lib.symbols.webview_create(
192 | Number(debugOrHandle),
193 | window,
194 | );
195 |
196 | if (size !== undefined) {
197 | this.size = size;
198 | }
199 |
200 | // Push this instance to the global instances list to automatically destroy
201 | instances.push(this);
202 | }
203 |
204 | /**
205 | * Destroys the webview and closes the window along with freeing all internal
206 | * resources.
207 | */
208 | destroy() {
209 | for (const callback of Object.keys(this.#callbacks)) {
210 | this.unbind(callback);
211 | }
212 | lib.symbols.webview_terminate(this.#handle);
213 | lib.symbols.webview_destroy(this.#handle);
214 | this.#handle = null;
215 | }
216 |
217 | /**
218 | * Navigates webview to the given URL. URL may be a data URI, i.e.
219 | * `"data:text/html,..."`. It is often ok not to url-encodeCString it
220 | * properly, webview will re-encodeCString it for you.
221 | */
222 | navigate(url: URL | string) {
223 | lib.symbols.webview_navigate(
224 | this.#handle,
225 | encodeCString(url instanceof URL ? url.toString() : url),
226 | );
227 | }
228 |
229 | /**
230 | * Runs the main event loop until it's terminated. After this function exits
231 | * the webview is automatically destroyed.
232 | */
233 | run(): void {
234 | lib.symbols.webview_run(this.#handle);
235 | this.destroy();
236 | }
237 |
238 | /**
239 | * Binds a callback so that it will appear in the webview with the given name
240 | * as a global async JavaScript function. Callback receives a seq and req value.
241 | * The seq parameter is an identifier for using {@link Webview.return} to
242 | * return a value while the req parameter is a string of an JSON array representing
243 | * the arguments passed from the JavaScript function call.
244 | *
245 | * @param name The name of the bound function
246 | * @param callback A callback which takes two strings as parameters: `seq`
247 | * and `req` and the passed {@link arg} pointer
248 | * @param arg A pointer which is going to be passed to the callback once called
249 | */
250 | bindRaw(
251 | name: string,
252 | callback: (
253 | seq: string,
254 | req: string,
255 | arg: Deno.PointerValue | null,
256 | ) => void,
257 | arg: Deno.PointerValue | null = null,
258 | ) {
259 | const callbackResource = new Deno.UnsafeCallback(
260 | {
261 | parameters: ["pointer", "pointer", "pointer"],
262 | result: "void",
263 | },
264 | (
265 | seqPtr: Deno.PointerValue,
266 | reqPtr: Deno.PointerValue,
267 | arg: Deno.PointerValue | null,
268 | ) => {
269 | const seq = seqPtr
270 | ? new Deno.UnsafePointerView(seqPtr).getCString()
271 | : "";
272 | const req = reqPtr
273 | ? new Deno.UnsafePointerView(reqPtr).getCString()
274 | : "";
275 | callback(seq, req, arg);
276 | },
277 | );
278 | this.#callbacks.set(name, callbackResource);
279 | lib.symbols.webview_bind(
280 | this.#handle,
281 | encodeCString(name),
282 | callbackResource.pointer,
283 | arg,
284 | );
285 | }
286 |
287 | /**
288 | * Binds a callback so that it will appear in the webview with the given name
289 | * as a global async JavaScript function. Callback arguments are automatically
290 | * converted from json to as closely as possible match the arguments in the
291 | * webview context and the callback automatically converts and returns the
292 | * return value to the webview.
293 | *
294 | * @param name The name of the bound function
295 | * @param callback A callback which is passed the arguments as called from the
296 | * webview JavaScript environment and optionally returns a value to the
297 | * webview JavaScript caller
298 | *
299 | * ## Example
300 | * ```ts
301 | * import { Webview } from "../mod.ts";
302 | *
303 | * const html = `
304 | *
305 | *
306 | * Hello from deno v${Deno.version.deno}
307 | *
310 | *
311 | *
312 | * `;
313 | *
314 | * const webview = new Webview();
315 | *
316 | * webview.navigate(`data:text/html,${encodeURIComponent(html)}`);
317 | *
318 | * let counter = 0;
319 | * // Create and bind `press` to the webview javascript instance.
320 | * // This functions in addition to logging its parameters also returns
321 | * // a value from deno land to webview land.
322 | * webview.bind("press", (a, b, c) => {
323 | * console.log(a, b, c);
324 | *
325 | * return { times: counter++ };
326 | * });
327 | *
328 | * // Bind the `log` function in the webview to the parent instances `console.log`
329 | * webview.bind("log", (...args) => console.log(...args));
330 | *
331 | * webview.run();
332 | * ```
333 | */
334 | bind(
335 | name: string,
336 | // deno-lint-ignore no-explicit-any
337 | callback: (...args: any) => any,
338 | ) {
339 | this.bindRaw(name, (seq, req) => {
340 | const args = JSON.parse(req);
341 | let result;
342 | let success: boolean;
343 | try {
344 | result = callback(...args);
345 | success = true;
346 | } catch (err) {
347 | result = err;
348 | success = false;
349 | }
350 | if (result instanceof Promise) {
351 | result.then((result) =>
352 | this.return(seq, success ? 0 : 1, JSON.stringify(result))
353 | );
354 | } else {
355 | this.return(seq, success ? 0 : 1, JSON.stringify(result));
356 | }
357 | });
358 | }
359 |
360 | /**
361 | * Unbinds a previously bound function freeing its resource and removing it
362 | * from the webview JavaScript context.
363 | *
364 | * @param name The name of the bound function
365 | */
366 | unbind(name: string) {
367 | lib.symbols.webview_unbind(this.#handle, encodeCString(name));
368 | this.#callbacks.get(name)?.close();
369 | this.#callbacks.delete(name);
370 | }
371 |
372 | /**
373 | * Returns a value to the webview JavaScript environment.
374 | *
375 | * @param seq The request pointer as provided by the {@link Webview.bindRaw}
376 | * callback
377 | * @param status If status is zero the result is expected to be a valid JSON
378 | * result value otherwise the result is an error JSON object
379 | * @param result The stringified JSON response
380 | */
381 | return(seq: string, status: number, result: string) {
382 | lib.symbols.webview_return(
383 | this.#handle,
384 | encodeCString(seq),
385 | status,
386 | encodeCString(result),
387 | );
388 | }
389 |
390 | /**
391 | * Evaluates arbitrary JavaScript code. Evaluation happens asynchronously,
392 | * also the result of the expression is ignored. Use
393 | * {@link Webview.bind bindings} if you want to receive notifications about
394 | * the results of the evaluation.
395 | */
396 | eval(source: string) {
397 | lib.symbols.webview_eval(this.#handle, encodeCString(source));
398 | }
399 |
400 | /**
401 | * Injects JavaScript code at the initialization of the new page. Every time
402 | * the webview will open a the new page - this initialization code will be
403 | * executed. It is guaranteed that code is executed before window.onload.
404 | */
405 | init(source: string) {
406 | lib.symbols.webview_init(this.#handle, encodeCString(source));
407 | }
408 | }
409 |
--------------------------------------------------------------------------------
/test_import_map.json:
--------------------------------------------------------------------------------
1 | {
2 | "imports": {
3 | "@webview/webview": "./mod.ts",
4 | "@denosaurs/plug": "jsr:@denosaurs/plug@^1.0"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------