├── .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 | [![stars](https://img.shields.io/github/stars/webview/webview_deno?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAEFCu8CAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAHKADAAQAAAABAAAAHAAAAABHddaYAAABxElEQVRIDe2Wv04CQRDGAQuoTKQ2ITyADZWVJZWV+gJYWBNqKh/C16CRBlprWxsTE2NJfABNOH9z7Gzm2Nv7A8TCOMnHzs1838ze3e4ejUbMkiRZS64lP1x8MjTFr2DQE6Gl2nI+7POARXAmdbas44ku8eLGhU9UckRliX6qxM9sQvz0vrcVaaKJKdsSNO7LOtK1kvcbaXVRu4LMz9kgKoYwBq/KLBi/yC2DQgSnBaLMQ88Tx7Q3AVkDKHpgBdoak5HrCSjuaAW/6zOz+u/Q3ZfcVrhliuaPYCAqsSJekIO/TlWbn2BveAH5JZBVUWayusZW2ClTuPzMi6xTIp5abuBHxHLcZSyzkxHF1uNJRrV9gXBhOl7h6wFW/FqcaGILEmsDWfg9G//3858Az0lWaHhm5dP3i9JoDtTm+1UrUdMl72OZv10itfx3zOYpLAv/FPQNLvFj35Bnco/gzeCD72H6b4JYaDTpgidwaJOa3bCji5BsgYcDdJUamSMi2lQTCEbgu0Zz4Y5UX3tE3K/RTKny3qNWdst3UWU8sYtmU40py2Go9o5zC460l/guJjm1leZrjaiH4B4cVxUK12mGVTV/j/cDqcFClUX01ZEAAAAASUVORK5CYII=)](https://github.com/webview/webview_deno/stargazers) 4 | [![issues](https://img.shields.io/github/issues/webview/webview_deno?logo=github)](https://github.com/webview/webview_deno/issues) 5 | [![ci](https://img.shields.io/github/workflow/status/webview/webview_deno/ci?logo=github)](https://github.com/webview/webview_deno/actions) 6 | [![downloads](https://img.shields.io/github/downloads/webview/webview_deno/total?logo=github)](https://github.com/webview/webview_deno/releases/latest/) 7 | [![JSR](https://jsr.io/badges/@webview/webview)](https://jsr.io/@webview/webview) 8 | [![deno version](https://img.shields.io/badge/deno-^1.18.0-informational?logo=deno)](https://github.com/denoland/deno) 9 | [![deno doc](https://img.shields.io/badge/deno-doc-informational?logo=deno)](https://doc.deno.land/https/deno.land/x/webview/mod.ts) 10 | [![license](https://img.shields.io/github/license/webview/webview_deno?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAEFCu8CAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAHKADAAQAAAABAAAAHAAAAABHddaYAAAC5UlEQVRIDd2WPWtVQRCGby5pVASLiGghQSxyG8Ui2KWwCfkH9olY2JneQkiR0oCIxH/gB+qVFDYBIWBAbAIRSbCRpLXwIxLiPT7vnNm9e87ZxJtUwYH3zO47Mzv7Mbv3tlo5KYriGtgAJ81OY1ENdG/YI4boFEOI911BXgY/pdtwGuAtXpvmB1tAXHDnUolE5urkPOQo6MqA3pXWmJJL4Bb4rQ7yEYfxsjnIF29NJIoNC6e5fxOL/qN+9KCz7AaLpN8zI415N2i2EptpGrkRIjGeAuvR6IY1hSFLFUOug9Ms2M7ZxIUNytm1mnME186sdI2BOCwAyQMg54ugzSmKmwbPwSbolKH+hbAtQdsOoF+BsF3anUVwBdiOWRidFZDKTTrKEAJTm3GVrGkHzw/uPZbyx7DNNLfB7KGmRsCcr+/gjaiPSpAOTyX9qG4L/XBDdWXDDf1M+wtQ5fwCOtcb4Dto6VpLmzByB6gqdHbTItGSJdAGqibJQhmRfCF7IN4beSF2G9CqnGXQrxofXU+EykllNeoczRgYytDKMubDIRK0g5MF8rE69cGu0u9nlUcqaUZ41W0qK2nGcSzr4D2wV9U9wxp1rnpxn8agXAOHMQ9cy9kbHM7ngY4gFb03TxrO/yfBUifTtXt78jCrjY/jgEFnMn45LuNWUtknuu7NSm7D3QEn3HbatV1Q2jvgIRf1sfODKQaeymxZoMLlTqsq1LF+HvaTqQOzEzUCfni0/eNIA+DfuE3KEtbsegckGmMktTXacnBHPVe687ugkpT+axCkkhBSyRSjWI2xf1KMMVmYiQdWksK9BEFiQoiYLIlvJA3/zeTzCejP0RbB6YPbhZuB+0pR3KcdX0LaJtju0ZgBL8Bd+sbz2QIaU2OfBX3BaQLsgZysQtrk0M8Sh1A0w3DyyYnGnAiZ4gqZ/TvI2A8OGd1YIbF7+F3P+B6dYpYdsJNZgrjO0UdOIhmom0nwL0pnfnzkL1803jAoKhvyAAAAAElFTkSuQmCC)](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 | ![Example Image](images/webview_deno.png) 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 | --------------------------------------------------------------------------------