├── src
├── wkwebview
│ ├── ios
│ │ └── mod.rs
│ ├── class
│ │ ├── mod.rs
│ │ ├── wry_download_delegate.rs
│ │ ├── document_title_changed_observer.rs
│ │ ├── wry_web_view_delegate.rs
│ │ ├── wry_web_view_parent.rs
│ │ ├── wry_web_view.rs
│ │ └── wry_navigation_delegate.rs
│ ├── util.rs
│ ├── proxy.rs
│ ├── navigation.rs
│ ├── drag_drop.rs
│ ├── synthetic_mouse_events.rs
│ └── download.rs
├── util.rs
├── proxy.rs
├── android
│ └── kotlin
│ │ ├── proguard-wry.pro
│ │ ├── Ipc.kt
│ │ ├── Logger.kt
│ │ ├── RustWebView.kt
│ │ ├── RustWebViewClient.kt
│ │ ├── PermissionHelper.kt
│ │ └── WryActivity.kt
├── error.rs
├── web_context.rs
├── webview2
│ ├── util.rs
│ └── drag_drop.rs
└── webkitgtk
│ ├── drag_drop.rs
│ └── synthetic_mouse_events.rs
├── .github
├── splash.png
├── CODEOWNERS
├── FUNDING.yml
├── workflows
│ ├── covector-status.yml
│ ├── audit.yml
│ ├── covector-comment-on-fork.yml
│ ├── clippy-fmt.yml
│ ├── covector-version-or-publish.yml
│ ├── bench.yml
│ └── build.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── PULL_REQUEST_TEMPLATE.md
└── CODE_OF_CONDUCT.md
├── examples
├── custom_protocol
│ ├── wasm.wasm
│ ├── subpage.html
│ ├── index.html
│ └── script.js
├── streaming
│ └── index.html
├── cookies.rs
├── simple.rs
├── transparent.rs
├── winit.rs
├── reparent.rs
├── window_border.rs
├── custom_protocol.rs
├── async_custom_protocol.rs
├── multiwindow.rs
├── gtk_multiwebview.rs
├── multiwebview.rs
├── wgpu.rs
└── streaming.rs
├── .changes
├── web-content-process-termination.md
├── OnBackPressedCallback.md
├── readme.md
└── config.json
├── audits
└── Radically_Open_Security-v1-report.pdf
├── .cargo
└── config.toml
├── .license_template
├── bench
├── tests
│ ├── src
│ │ ├── static
│ │ │ ├── index.css
│ │ │ ├── index.html
│ │ │ ├── worker.js
│ │ │ └── site.js
│ │ ├── hello_world.rs
│ │ ├── custom_protocol.rs
│ │ └── cpu_intensive.rs
│ └── Cargo.toml
├── Cargo.toml
└── src
│ ├── build_benchmark_jsons.rs
│ ├── utils.rs
│ └── run_benchmark.rs
├── renovate.json
├── .gitignore
├── rustfmt.toml
├── SECURITY.md
├── LICENSE.spdx
├── LICENSE-MIT
├── wry-logo.svg
└── Cargo.toml
/src/wkwebview/ios/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod WKWebView;
2 |
--------------------------------------------------------------------------------
/.github/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/wry/HEAD/.github/splash.png
--------------------------------------------------------------------------------
/examples/custom_protocol/wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/wry/HEAD/examples/custom_protocol/wasm.wasm
--------------------------------------------------------------------------------
/.changes/web-content-process-termination.md:
--------------------------------------------------------------------------------
1 | ---
2 | "wry": minor
3 | ---
4 |
5 | Add handler for web content process termination.
6 |
--------------------------------------------------------------------------------
/audits/Radically_Open_Security-v1-report.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/wry/HEAD/audits/Radically_Open_Security-v1-report.pdf
--------------------------------------------------------------------------------
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | # [build]
2 | # target = "aarch64-linux-android"
3 | [target.x86_64-apple-darwin]
4 | rustflags = ["-C", "link-arg=-mmacosx-version-min=10.12"]
5 |
--------------------------------------------------------------------------------
/.changes/OnBackPressedCallback.md:
--------------------------------------------------------------------------------
1 | ---
2 | "wry": patch
3 | ---
4 |
5 | Use OnBackPressedCallback instead of the deprecated onKeyDown for back navigation on Android.
6 |
--------------------------------------------------------------------------------
/.license_template:
--------------------------------------------------------------------------------
1 | // Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
--------------------------------------------------------------------------------
/bench/tests/src/static/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
3 | Arial, sans-serif;
4 | margin: auto;
5 | max-width: 38rem;
6 | padding: 2rem;
7 | }
8 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:recommended"],
3 | "rangeStrategy": "replace",
4 | "packageRules": [
5 | {
6 | "semanticCommitType": "chore",
7 | "matchPackageNames": ["*"]
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | # SPDX-License-Identifier: Apache-2.0
3 | # SPDX-License-Identifier: MIT
4 |
5 | target
6 | gh-pages
7 | .DS_Store
8 | examples/test_video.mp4
9 | .vscode/
10 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Current WG Code Sub Teams:
2 | # @tauri-apps/wg-webview
3 | # @tauri-apps/wg-devops
4 |
5 | # Order is important; the last matching pattern takes the most precedence.
6 | * @tauri-apps/wg-webview
7 |
8 | .github @tauri-apps/wg-devops
9 |
--------------------------------------------------------------------------------
/src/util.rs:
--------------------------------------------------------------------------------
1 | use std::sync::atomic::{AtomicU32, Ordering};
2 |
3 | pub struct Counter(AtomicU32);
4 |
5 | impl Counter {
6 | pub const fn new() -> Self {
7 | Self(AtomicU32::new(1))
8 | }
9 |
10 | pub fn next(&self) -> u32 {
11 | self.0.fetch_add(1, Ordering::Relaxed)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: #
4 | patreon: #
5 | open_collective: tauri
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | custom: # Replace with a single custom sponsorship URL
9 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | max_width = 100
2 | hard_tabs = false
3 | tab_spaces = 2
4 | newline_style = "Unix"
5 | use_small_heuristics = "Default"
6 | reorder_imports = true
7 | reorder_modules = true
8 | remove_nested_parens = true
9 | edition = "2018"
10 | merge_derives = true
11 | use_try_shorthand = false
12 | use_field_init_shorthand = false
13 | force_explicit_abi = true
14 | imports_granularity = "Crate"
15 | #license_template_path = ".license_template"
16 |
--------------------------------------------------------------------------------
/src/wkwebview/class/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | pub mod document_title_changed_observer;
6 | pub mod url_scheme_handler;
7 | pub mod wry_download_delegate;
8 | pub mod wry_navigation_delegate;
9 | pub mod wry_web_view;
10 | pub mod wry_web_view_delegate;
11 | pub mod wry_web_view_parent;
12 | pub mod wry_web_view_ui_delegate;
13 |
--------------------------------------------------------------------------------
/bench/tests/src/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World!
6 |
7 |
8 |
9 | Calculate prime numbers
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/proxy.rs:
--------------------------------------------------------------------------------
1 | #[derive(Debug, Clone)]
2 | pub struct ProxyEndpoint {
3 | /// Proxy server host (e.g. 192.168.0.100, localhost, example.com, etc.)
4 | pub host: String,
5 | /// Proxy server port (e.g. 1080, 3128, etc.)
6 | pub port: String,
7 | }
8 |
9 | #[derive(Debug, Clone)]
10 | pub enum ProxyConfig {
11 | /// Connect to proxy server via HTTP CONNECT
12 | Http(ProxyEndpoint),
13 | /// Connect to proxy server via SOCKSv5
14 | Socks5(ProxyEndpoint),
15 | }
16 |
--------------------------------------------------------------------------------
/.github/workflows/covector-status.yml:
--------------------------------------------------------------------------------
1 | name: covector status
2 | on: [pull_request]
3 |
4 | jobs:
5 | covector:
6 | runs-on: ubuntu-latest
7 |
8 | steps:
9 | - uses: actions/checkout@v4
10 | with:
11 | fetch-depth: 0
12 | - name: covector status
13 | uses: jbolda/covector/packages/action@covector-v0
14 | with:
15 | command: "status"
16 | token: ${{ secrets.GITHUB_TOKEN }}
17 | comment: true
18 |
--------------------------------------------------------------------------------
/src/wkwebview/util.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use objc2_foundation::NSProcessInfo;
6 |
7 | pub fn operating_system_version() -> (isize, isize, isize) {
8 | let process_info = NSProcessInfo::processInfo();
9 | let version = process_info.operatingSystemVersion();
10 | (
11 | version.majorVersion,
12 | version.minorVersion,
13 | version.patchVersion,
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/examples/custom_protocol/subpage.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Page 2
16 | Back home
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/bench/tests/Cargo.toml:
--------------------------------------------------------------------------------
1 | workspace = {}
2 |
3 | [package]
4 | name = "hello_world"
5 | version = "0.1.0"
6 | description = "A very simple WRY Appplication"
7 | edition = "2018"
8 |
9 | [dependencies]
10 | wry = { path = "../../" }
11 | serde = { version = "1.0", features = ["derive"] }
12 | tao = "0.34"
13 |
14 | [[bin]]
15 | name = "bench_hello_world"
16 | path = "src/hello_world.rs"
17 |
18 | [[bin]]
19 | name = "bench_cpu_intensive"
20 | path = "src/cpu_intensive.rs"
21 |
22 | [[bin]]
23 | name = "bench_custom_protocol"
24 | path = "src/custom_protocol.rs"
25 |
26 | [profile.release]
27 | panic = "abort"
28 | codegen-units = 1
29 | lto = true
30 | incremental = false
31 | opt-level = "s"
32 |
--------------------------------------------------------------------------------
/bench/Cargo.toml:
--------------------------------------------------------------------------------
1 | workspace = {}
2 |
3 | [package]
4 | name = "wry_bench"
5 | version = "0.1.0"
6 | authors = [ "Tauri Programme within The Commons Conservancy" ]
7 | edition = "2018"
8 | license = "Apache-2.0 OR MIT"
9 | description = "Cross-platform WebView rendering library"
10 | repository = "https://github.com/tauri-apps/wry"
11 |
12 | [dependencies]
13 | anyhow = "1.0"
14 | time = { version = "0.3", features = ["formatting"] }
15 | tempfile = "3.10"
16 | serde_json = "1.0"
17 | serde = { version = "1.0", features = [ "derive" ] }
18 |
19 | [[bin]]
20 | name = "run_benchmark"
21 | path = "src/run_benchmark.rs"
22 |
23 | [[bin]]
24 | name = "build_benchmark_jsons"
25 | path = "src/build_benchmark_jsons.rs"
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for Tauri
4 | title: ''
5 | labels: feature request
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
22 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | | Version | Supported |
6 | | ------- | ------------------ |
7 | | > 1.0 | :white_check_mark: |
8 | | < 1.0 | :x: |
9 |
10 | ## Reporting a Vulnerability
11 |
12 | If you have found a potential security threat, vulnerability or exploit in Tauri
13 | or one of its upstream dependencies, please DON’T create a pull-request, DON’T
14 | file an issue on GitHub, DON’T mention it on Discord and DON’T create a forum thread.
15 |
16 | We will be adding contact information to this page very soon.
17 |
18 | At the current time we do not have the financial ability to reward bounties,
19 | but in extreme cases will at our discretion consider a reward.
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve wry
4 | title: ''
5 | labels: 'type: bug'
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Steps To Reproduce**
14 | Steps to reproduce the behavior. It **must** use wry directly instead of tauri.
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Platform and Versions (please complete the following information):**
23 | OS:
24 | Rustc:
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
29 |
--------------------------------------------------------------------------------
/.github/workflows/audit.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | # SPDX-License-Identifier: Apache-2.0
3 | # SPDX-License-Identifier: MIT
4 |
5 | name: audit
6 |
7 | on:
8 | workflow_dispatch:
9 | schedule:
10 | - cron: '0 0 * * *'
11 | push:
12 | branches:
13 | - dev
14 | paths:
15 | - 'Cargo.lock'
16 | - 'Cargo.toml'
17 | pull_request:
18 | paths:
19 | - 'Cargo.lock'
20 | - 'Cargo.toml'
21 |
22 | concurrency:
23 | group: ${{ github.workflow }}-${{ github.ref }}
24 | cancel-in-progress: true
25 |
26 | jobs:
27 | audit:
28 | runs-on: ubuntu-latest
29 | steps:
30 | - uses: actions/checkout@v4
31 | - uses: rustsec/audit-check@v1
32 | with:
33 | token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/LICENSE.spdx:
--------------------------------------------------------------------------------
1 | SPDXVersion: SPDX-2.1
2 | DataLicense: CC0-1.0
3 | PackageName: wry
4 | DataFormat: SPDXRef-1
5 | PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy
6 | PackageHomePage: https://tauri.app
7 | PackageLicenseDeclared: Apache-2.0
8 | PackageLicenseDeclared: MIT
9 | PackageCopyrightText: 2020-2023, The Tauri Programme in the Commons Conservancy
10 | PackageSummary: Wry is the official, rust-based webview
11 | windowing service for Tauri.
12 |
13 | PackageComment: The package includes the following libraries; see
14 | Relationship information.
15 |
16 | Created: 2020-05-20T09:00:00Z
17 | PackageDownloadLocation: git://github.com/tauri-apps/wry
18 | PackageDownloadLocation: git+https://github.com/tauri-apps/wry.git
19 | PackageDownloadLocation: git+ssh://github.com/tauri-apps/wry.git
20 | Creator: Person: Daniel Thompson-Yvetot
--------------------------------------------------------------------------------
/examples/custom_protocol/index.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Welcome to WRY!
15 | Page 1
16 |
17 |
18 | Link
19 |
20 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/examples/custom_protocol/script.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 | if (window.location.pathname.startsWith("/page2")) {
5 | console.log("hello from javascript in page2");
6 | } else {
7 | console.log("hello from javascript in page1");
8 |
9 | if (typeof WebAssembly.instantiateStreaming !== "undefined") {
10 | WebAssembly.instantiateStreaming(fetch("/wasm.wasm")).then((wasm) => {
11 | console.log(wasm.instance.exports.main()); // should log 42
12 | });
13 | } else {
14 | // Older WKWebView may not support `WebAssembly.instantiateStreaming` yet.
15 | fetch("/wasm.wasm")
16 | .then((response) => response.arrayBuffer())
17 | .then((bytes) => WebAssembly.instantiate(bytes))
18 | .then((wasm) => {
19 | console.log(wasm.instance.exports.main()); // should log 42
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/android/kotlin/proguard-wry.pro:
--------------------------------------------------------------------------------
1 | # Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | # SPDX-License-Identifier: Apache-2.0
3 | # SPDX-License-Identifier: MIT
4 |
5 | -keep class {{package-unescaped}}.* {
6 | native ;
7 | }
8 |
9 | -keep class {{package-unescaped}}.WryActivity {
10 | public (...);
11 |
12 | void setWebView({{package-unescaped}}.RustWebView);
13 | java.lang.Class getAppClass(...);
14 | java.lang.String getVersion();
15 | }
16 |
17 | -keep class {{package-unescaped}}.Ipc {
18 | public (...);
19 |
20 | @android.webkit.JavascriptInterface public ;
21 | }
22 |
23 | -keep class {{package-unescaped}}.RustWebView {
24 | public (...);
25 |
26 | void loadUrlMainThread(...);
27 | void loadHTMLMainThread(...);
28 | void evalScript(...);
29 | }
30 |
31 | -keep class {{package-unescaped}}.RustWebChromeClient,{{package-unescaped}}.RustWebViewClient {
32 | public (...);
33 | }
34 |
--------------------------------------------------------------------------------
/src/android/kotlin/Ipc.kt:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | @file:Suppress("unused")
6 |
7 | package {{package}}
8 |
9 | import android.webkit.*
10 |
11 | class Ipc(val webViewClient: RustWebViewClient) {
12 | @JavascriptInterface
13 | fun postMessage(message: String?) {
14 | message?.let {m ->
15 | // we're not using WebView::getUrl() here because it needs to be executed on the main thread
16 | // and it would slow down the Ipc
17 | // so instead we track the current URL on the webview client
18 | this.ipc(webViewClient.currentUrl, m)
19 | }
20 | }
21 |
22 | companion object {
23 | init {
24 | System.loadLibrary("{{library}}")
25 | }
26 | }
27 |
28 | private external fun ipc(url: String, message: String)
29 |
30 | {{class-extension}}
31 | }
32 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/.github/workflows/covector-comment-on-fork.yml:
--------------------------------------------------------------------------------
1 | name: covector comment
2 | on:
3 | workflow_run:
4 | workflows: [covector status] # the `name` of the workflow run on `pull_request` running `status` with `comment: true`
5 | types:
6 | - completed
7 |
8 | # note all other permissions are set to none if not specified
9 | # and these set the permissions for `secrets.GITHUB_TOKEN`
10 | permissions:
11 | # to read the action artifacts on `covector status` workflows
12 | actions: read
13 | # to write the comment
14 | pull-requests: write
15 |
16 | jobs:
17 | download:
18 | runs-on: ubuntu-latest
19 | if: github.event.workflow_run.conclusion == 'success' &&
20 | (github.event.workflow_run.head_repository.full_name != github.repository || github.actor == 'dependabot[bot]')
21 | steps:
22 | - name: covector status
23 | uses: jbolda/covector/packages/action@covector-v0
24 | with:
25 | token: ${{ secrets.GITHUB_TOKEN }}
26 | command: "status"
27 |
--------------------------------------------------------------------------------
/bench/tests/src/static/worker.js:
--------------------------------------------------------------------------------
1 | const isPrime = (number) => {
2 | if (number % 2 === 0 && number > 2) {
3 | return false;
4 | }
5 |
6 | let start = 2;
7 | const limit = Math.sqrt(number);
8 | while (start <= limit) {
9 | if (number % start++ < 1) {
10 | return false;
11 | }
12 | }
13 | return number > 1;
14 | };
15 |
16 | addEventListener("message", (e) => {
17 | const { startTime } = e.data;
18 |
19 | let n = 0;
20 | let total = 0;
21 | const THRESHOLD = e.data.value;
22 | const primes = [];
23 |
24 | let previous = startTime;
25 |
26 | while (++n <= THRESHOLD) {
27 | if (isPrime(n)) {
28 | primes.push(n);
29 | total++;
30 |
31 | const now = Date.now();
32 |
33 | if (now - previous > 250) {
34 | previous = now;
35 | postMessage({
36 | status: "calculating",
37 | count: total,
38 | time: Date.now() - startTime,
39 | });
40 | }
41 | }
42 | }
43 |
44 | postMessage({ status: "done", count: total, time: Date.now() - startTime });
45 | });
46 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2023 Ngo Iok Ui & Tauri Programme within The Commons Conservancy
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 |
--------------------------------------------------------------------------------
/.github/workflows/clippy-fmt.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | # SPDX-License-Identifier: Apache-2.0
3 | # SPDX-License-Identifier: MIT
4 |
5 | name: clippy & fmt
6 |
7 | on:
8 | push:
9 | branches:
10 | - dev
11 | pull_request:
12 |
13 | concurrency:
14 | group: ${{ github.workflow }}-${{ github.ref }}
15 | cancel-in-progress: true
16 |
17 | jobs:
18 | clippy:
19 | strategy:
20 | fail-fast: false
21 | matrix:
22 | platform: [ubuntu-latest, macos-latest, windows-latest]
23 |
24 | runs-on: ${{ matrix.platform }}
25 |
26 | steps:
27 | - uses: actions/checkout@v4
28 | - name: install system deps
29 | if: matrix.platform == 'ubuntu-latest'
30 | run: |
31 | sudo apt-get update
32 | sudo apt-get install -y libwebkit2gtk-4.1-dev
33 |
34 | - uses: dtolnay/rust-toolchain@stable
35 | with:
36 | components: clippy
37 |
38 | - run: cargo clippy --all-targets
39 |
40 | fmt:
41 | runs-on: ubuntu-latest
42 | steps:
43 | - uses: actions/checkout@v4
44 | - uses: dtolnay/rust-toolchain@stable
45 | with:
46 | components: rustfmt
47 |
48 | - run: cargo fmt --all -- --check
49 |
--------------------------------------------------------------------------------
/examples/streaming/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 | Enter a path to a video to play, then hit Enter or click Start
10 |
14 |
15 |
16 |
17 |
38 |
39 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4 |
5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6 |
7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8 |
9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10 |
11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12 |
13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
14 |
--------------------------------------------------------------------------------
/bench/tests/src/static/site.js:
--------------------------------------------------------------------------------
1 | // Create web worker
2 | const THRESHOLD = 10000000;
3 | const worker = new Worker("/worker.js");
4 | /** @type {HTMLButtonElement} */
5 | const start = document.getElementById("start");
6 | /** @type {HTMLParagraphElement} */
7 | const status = document.getElementById("status");
8 | const results = document.getElementById("results");
9 |
10 | const ITERATIONS = 1;
11 |
12 | let resolver;
13 |
14 | const onMessage = (message) => {
15 | // Update the UI
16 | let prefix = "[Calculating]";
17 |
18 | if (message.data.status === "done") {
19 | // tell rust that we are done
20 | ipc.postMessage("process-complete");
21 | }
22 |
23 | status.innerHTML = `${prefix} Found ${message.data.count} prime numbers in ${message.data.time}ms`;
24 |
25 | if (message.data.status === "done") {
26 | resolver(message.data.time);
27 | }
28 | };
29 |
30 | worker.addEventListener("message", onMessage);
31 |
32 | const benchmark = () => {
33 | return new Promise((resolve) => {
34 | const startTime = Date.now();
35 | resolver = resolve;
36 | worker.postMessage({ value: THRESHOLD, startTime });
37 | });
38 | };
39 |
40 | const calculate = async () => {
41 | let total = 0;
42 |
43 | for (let i = 0; i < ITERATIONS; i++) {
44 | const result = await benchmark();
45 | total += result;
46 | }
47 |
48 | const average = total / ITERATIONS;
49 |
50 | results.innerText = `Average time: ${average}ms`;
51 | };
52 |
53 | window.addEventListener("DOMContentLoaded", calculate);
54 |
--------------------------------------------------------------------------------
/src/wkwebview/proxy.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use objc2_foundation::NSObject;
6 | use std::ffi::{c_char, CString};
7 |
8 | use crate::{proxy::ProxyEndpoint, Error};
9 |
10 | #[allow(non_camel_case_types)]
11 | pub type nw_endpoint_t = *mut NSObject;
12 | #[allow(non_camel_case_types)]
13 | pub type nw_protocol_options_t = *mut NSObject;
14 | #[allow(non_camel_case_types)]
15 | pub type nw_proxy_config_t = *mut NSObject;
16 |
17 | #[link(name = "Network", kind = "framework")]
18 | extern "C" {
19 | fn nw_endpoint_create_host(host: *const c_char, port: *const c_char) -> nw_endpoint_t;
20 | pub fn nw_proxy_config_create_socksv5(proxy_endpoint: nw_endpoint_t) -> nw_proxy_config_t;
21 | pub fn nw_proxy_config_create_http_connect(
22 | proxy_endpoint: nw_endpoint_t,
23 | proxy_tls_options: nw_protocol_options_t,
24 | ) -> nw_proxy_config_t;
25 | }
26 |
27 | impl TryFrom for nw_endpoint_t {
28 | type Error = Error;
29 | fn try_from(endpoint: ProxyEndpoint) -> Result {
30 | unsafe {
31 | let endpoint_host =
32 | CString::new(endpoint.host).map_err(|_| Error::ProxyEndpointCreationFailed)?;
33 | let endpoint_port =
34 | CString::new(endpoint.port).map_err(|_| Error::ProxyEndpointCreationFailed)?;
35 | let endpoint = nw_endpoint_create_host(endpoint_host.as_ptr(), endpoint_port.as_ptr());
36 |
37 | if endpoint.is_null() {
38 | Err(Error::ProxyEndpointCreationFailed)
39 | } else {
40 | Ok(endpoint)
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.changes/readme.md:
--------------------------------------------------------------------------------
1 | # Changes
2 |
3 | ##### via https://github.com/jbolda/covector
4 |
5 | As you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend that it represents the overall change for organizational purposes.
6 |
7 | When you select the version bump required, you do _not_ need to consider dependencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process.
8 |
9 | Use the following format:
10 |
11 | ```md
12 | ---
13 | "wry": patch
14 | ---
15 |
16 | Change summary goes here
17 |
18 | ```
19 |
20 | Summaries do not have a specific character limit, but are text only. These summaries are used within the (future implementation of) changelogs. They will give context to the change and also point back to the original PR if more details and context are needed.
21 |
22 | Changes will be designated as a `major`, `minor` or `patch` as further described in [semver](https://semver.org/).
23 |
24 | Given a version number MAJOR.MINOR.PATCH, increment the:
25 |
26 | - MAJOR version when you make incompatible API changes,
27 | - MINOR version when you add functionality in a backwards compatible manner, and
28 | - PATCH version when you make backwards compatible bug fixes.
29 |
30 | Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format, but will be discussed prior to usage (as extra steps will be necessary in consideration of merging and publishing).
31 |
--------------------------------------------------------------------------------
/bench/src/build_benchmark_jsons.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use std::{fs::File, io::BufReader};
6 | mod utils;
7 |
8 | fn main() {
9 | let wry_data = &utils::wry_root_path()
10 | .join("gh-pages")
11 | .join("wry-data.json");
12 | let wry_recent = &utils::wry_root_path()
13 | .join("gh-pages")
14 | .join("wry-recent.json");
15 |
16 | // current data
17 | let current_data_buffer = BufReader::new(
18 | File::open(utils::target_dir().join("bench.json")).expect("Unable to read current data file"),
19 | );
20 | let current_data: utils::BenchResult =
21 | serde_json::from_reader(current_data_buffer).expect("Unable to read current data buffer");
22 |
23 | // all data's
24 | let all_data_buffer = BufReader::new(File::open(wry_data).expect("Unable to read all data file"));
25 | let mut all_data: Vec =
26 | serde_json::from_reader(all_data_buffer).expect("Unable to read all data buffer");
27 |
28 | // add current data to all data
29 | all_data.push(current_data);
30 |
31 | // use only latest 20 elements from all data
32 | let recent: Vec = if all_data.len() > 20 {
33 | all_data[all_data.len() - 20..].to_vec()
34 | } else {
35 | all_data.clone()
36 | };
37 |
38 | // write jsons
39 | utils::write_json(
40 | wry_data.to_str().expect("Something wrong with wry_data"),
41 | &serde_json::to_value(&all_data).expect("Unable to build final json (all)"),
42 | )
43 | .unwrap_or_else(|_| panic!("Unable to write {:?}", wry_data));
44 |
45 | utils::write_json(
46 | wry_recent
47 | .to_str()
48 | .expect("Something wrong with wry_recent"),
49 | &serde_json::to_value(recent).expect("Unable to build final json (recent)"),
50 | )
51 | .unwrap_or_else(|_| panic!("Unable to write {:?}", wry_recent));
52 | }
53 |
--------------------------------------------------------------------------------
/bench/tests/src/hello_world.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use serde::{Deserialize, Serialize};
6 | use std::process::exit;
7 |
8 | #[derive(Debug, Serialize, Deserialize)]
9 | struct MessageParameters {
10 | message: String,
11 | }
12 |
13 | fn main() -> wry::Result<()> {
14 | use tao::{
15 | event::{Event, WindowEvent},
16 | event_loop::{ControlFlow, EventLoop},
17 | window::WindowBuilder,
18 | };
19 | use wry::http::Request;
20 | use wry::WebViewBuilder;
21 |
22 | let event_loop = EventLoop::new();
23 | let window = WindowBuilder::new().build(&event_loop).unwrap();
24 |
25 | let html = r#"
26 |
27 |
28 |
33 |
34 | "#;
35 |
36 | let handler = |req: Request| {
37 | if req.body() == "dom-loaded" {
38 | exit(0);
39 | }
40 | };
41 |
42 | let builder = WebViewBuilder::new()
43 | .with_html(html)
44 | .with_ipc_handler(handler);
45 |
46 | #[cfg(any(
47 | target_os = "windows",
48 | target_os = "macos",
49 | target_os = "ios",
50 | target_os = "android"
51 | ))]
52 | let _webview = builder.build(&window)?;
53 |
54 | #[cfg(not(any(
55 | target_os = "windows",
56 | target_os = "macos",
57 | target_os = "ios",
58 | target_os = "android"
59 | )))]
60 | {
61 | use tao::platform::unix::WindowExtUnix;
62 | use wry::WebViewBuilderExtUnix;
63 | let vbox = window.default_vbox().unwrap();
64 | let _webview = builder.build_gtk(vbox)?;
65 | };
66 |
67 | event_loop.run(move |event, _, control_flow| {
68 | *control_flow = ControlFlow::Wait;
69 |
70 | match event {
71 | Event::WindowEvent {
72 | event: WindowEvent::CloseRequested,
73 | ..
74 | } => *control_flow = ControlFlow::Exit,
75 | _ => {}
76 | }
77 | })
78 | }
79 |
--------------------------------------------------------------------------------
/examples/cookies.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use tao::{
6 | event::{Event, WindowEvent},
7 | event_loop::{ControlFlow, EventLoop},
8 | window::WindowBuilder,
9 | };
10 | use wry::WebViewBuilder;
11 |
12 | fn main() -> wry::Result<()> {
13 | let event_loop = EventLoop::new();
14 | let window = WindowBuilder::new().build(&event_loop).unwrap();
15 |
16 | let builder = WebViewBuilder::new().with_url("https://www.httpbin.org/cookies/set?foo=bar");
17 |
18 | #[cfg(any(
19 | target_os = "windows",
20 | target_os = "macos",
21 | target_os = "ios",
22 | target_os = "android"
23 | ))]
24 | let webview = builder.build(&window)?;
25 | #[cfg(not(any(
26 | target_os = "windows",
27 | target_os = "macos",
28 | target_os = "ios",
29 | target_os = "android"
30 | )))]
31 | let webview = {
32 | use tao::platform::unix::WindowExtUnix;
33 | use wry::WebViewBuilderExtUnix;
34 | let vbox = window.default_vbox().unwrap();
35 | builder.build_gtk(vbox)?
36 | };
37 |
38 | webview.set_cookie(
39 | cookie::Cookie::build(("foo1", "bar1"))
40 | .domain("www.httpbin.org")
41 | .path("/")
42 | .secure(true)
43 | .http_only(true)
44 | .max_age(cookie::time::Duration::seconds(10))
45 | .inner(),
46 | )?;
47 |
48 | let cookie_deleted = cookie::Cookie::build(("will_be_deleted", "will_be_deleted"));
49 |
50 | webview.set_cookie(cookie_deleted.inner())?;
51 | println!("Setting Cookies:");
52 | for cookie in webview.cookies()? {
53 | println!("\t{cookie}");
54 | }
55 |
56 | println!("After Deleting:");
57 | webview.delete_cookie(cookie_deleted.inner())?;
58 | for cookie in webview.cookies()? {
59 | println!("\t{cookie}");
60 | }
61 |
62 | event_loop.run(move |event, _, control_flow| {
63 | *control_flow = ControlFlow::Wait;
64 |
65 | if let Event::WindowEvent {
66 | event: WindowEvent::CloseRequested,
67 | ..
68 | } = event
69 | {
70 | *control_flow = ControlFlow::Exit;
71 | }
72 | });
73 | }
74 |
--------------------------------------------------------------------------------
/examples/simple.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use tao::{
6 | event::{Event, WindowEvent},
7 | event_loop::{ControlFlow, EventLoop},
8 | window::WindowBuilder,
9 | };
10 | use wry::WebViewBuilder;
11 |
12 | fn main() -> wry::Result<()> {
13 | let event_loop = EventLoop::new();
14 | let window = WindowBuilder::new().build(&event_loop).unwrap();
15 |
16 | let builder = WebViewBuilder::new()
17 | .with_url("http://tauri.app")
18 | .with_new_window_req_handler(|url, features| {
19 | println!("new window req: {url} {features:?}");
20 | wry::NewWindowResponse::Allow
21 | });
22 |
23 | #[cfg(feature = "drag-drop")]
24 | let builder = builder.with_drag_drop_handler(|e| {
25 | match e {
26 | wry::DragDropEvent::Enter { paths, position } => {
27 | println!("DragEnter: {position:?} {paths:?} ")
28 | }
29 | wry::DragDropEvent::Over { position } => println!("DragOver: {position:?} "),
30 | wry::DragDropEvent::Drop { paths, position } => {
31 | println!("DragDrop: {position:?} {paths:?} ")
32 | }
33 | wry::DragDropEvent::Leave => println!("DragLeave"),
34 | _ => {}
35 | }
36 |
37 | true
38 | });
39 |
40 | #[cfg(any(
41 | target_os = "windows",
42 | target_os = "macos",
43 | target_os = "ios",
44 | target_os = "android"
45 | ))]
46 | let _webview = builder.build(&window)?;
47 | #[cfg(not(any(
48 | target_os = "windows",
49 | target_os = "macos",
50 | target_os = "ios",
51 | target_os = "android"
52 | )))]
53 | let _webview = {
54 | use tao::platform::unix::WindowExtUnix;
55 | use wry::WebViewBuilderExtUnix;
56 | let vbox = window.default_vbox().unwrap();
57 | builder.build_gtk(vbox)?
58 | };
59 |
60 | event_loop.run(move |event, _, control_flow| {
61 | *control_flow = ControlFlow::Wait;
62 |
63 | if let Event::WindowEvent {
64 | event: WindowEvent::CloseRequested,
65 | ..
66 | } = event
67 | {
68 | *control_flow = ControlFlow::Exit;
69 | }
70 | });
71 | }
72 |
--------------------------------------------------------------------------------
/wry-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.changes/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitSiteUrl": "https://github.com/tauri-apps/wry/",
3 | "timeout": 3600000,
4 | "additionalBumpTypes": ["housekeeping"],
5 | "pkgManagers": {
6 | "rust": {
7 | "version": true,
8 | "getPublishedVersion": {
9 | "use": "fetch:check",
10 | "options": {
11 | "url": "https://crates.io/api/v1/crates/${ pkg.pkg }/${ pkg.pkgFile.version }"
12 | }
13 | },
14 | "prepublish": [
15 | {
16 | "command": "cargo install cargo-audit --features=fix",
17 | "dryRunCommand": true
18 | },
19 | {
20 | "command": "echo '\nCargo Audit
\n\n```'",
21 | "dryRunCommand": true,
22 | "pipe": true
23 | },
24 | {
25 | "command": "cargo generate-lockfile",
26 | "dryRunCommand": true,
27 | "runFromRoot": true,
28 | "pipe": true
29 | },
30 | {
31 | "command": "cargo audit ${ process.env.CARGO_AUDIT_OPTIONS || '' }",
32 | "dryRunCommand": true,
33 | "runFromRoot": true,
34 | "pipe": true
35 | },
36 | {
37 | "command": "echo '```\n\n \n'",
38 | "dryRunCommand": true,
39 | "pipe": true
40 | }
41 | ],
42 | "publish": [
43 | {
44 | "command": "echo '\nCargo Publish
\n\n```'",
45 | "dryRunCommand": true,
46 | "pipe": true
47 | },
48 | {
49 | "command": "cargo publish --no-verify --allow-dirty",
50 | "dryRunCommand": "cargo publish --no-verify --allow-dirty --dry-run",
51 | "pipe": true
52 | },
53 | {
54 | "command": "echo '```\n\n \n'",
55 | "dryRunCommand": true,
56 | "pipe": true
57 | }
58 | ],
59 | "postpublish": [
60 | "git tag ${ pkg.pkg }-v${ pkgFile.versionMajor } -f",
61 | "git tag ${ pkg.pkg }-v${ pkgFile.versionMajor }.${ pkgFile.versionMinor } -f",
62 | "git push --tags -f"
63 | ]
64 | }
65 | },
66 | "packages": {
67 | "wry": {
68 | "path": "./",
69 | "manager": "rust"
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/android/kotlin/Logger.kt:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | @file:Suppress("unused", "MemberVisibilityCanBePrivate")
6 |
7 | package {{package}}
8 |
9 | // taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/Logger.java
10 |
11 | import android.text.TextUtils
12 | import android.util.Log
13 |
14 | class Logger {
15 | companion object {
16 | private const val LOG_TAG_CORE = "Tauri"
17 |
18 | fun tags(vararg subtags: String): String {
19 | return if (subtags.isNotEmpty()) {
20 | LOG_TAG_CORE + "/" + TextUtils.join("/", subtags)
21 | } else LOG_TAG_CORE
22 | }
23 |
24 | fun verbose(message: String) {
25 | verbose(LOG_TAG_CORE, message)
26 | }
27 |
28 | private fun verbose(tag: String, message: String) {
29 | if (!shouldLog()) {
30 | return
31 | }
32 | Log.v(tag, message)
33 | }
34 |
35 | fun debug(message: String) {
36 | debug(LOG_TAG_CORE, message)
37 | }
38 |
39 | fun debug(tag: String, message: String) {
40 | if (!shouldLog()) {
41 | return
42 | }
43 | Log.d(tag, message)
44 | }
45 |
46 | fun info(message: String) {
47 | info(LOG_TAG_CORE, message)
48 | }
49 |
50 | fun info(tag: String, message: String) {
51 | if (!shouldLog()) {
52 | return
53 | }
54 | Log.i(tag, message)
55 | }
56 |
57 | fun warn(message: String) {
58 | warn(LOG_TAG_CORE, message)
59 | }
60 |
61 | fun warn(tag: String, message: String) {
62 | if (!shouldLog()) {
63 | return
64 | }
65 | Log.w(tag, message)
66 | }
67 |
68 | fun error(message: String) {
69 | error(LOG_TAG_CORE, message, null)
70 | }
71 |
72 | fun error(message: String, e: Throwable?) {
73 | error(LOG_TAG_CORE, message, e)
74 | }
75 |
76 | fun error(tag: String, message: String, e: Throwable?) {
77 | if (!shouldLog()) {
78 | return
79 | }
80 | Log.e(tag, message, e)
81 | }
82 |
83 | private fun shouldLog(): Boolean {
84 | return BuildConfig.DEBUG
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/.github/workflows/covector-version-or-publish.yml:
--------------------------------------------------------------------------------
1 | name: covector version or publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - dev
7 |
8 | jobs:
9 | version-or-publish:
10 | runs-on: ubuntu-latest
11 | timeout-minutes: 65
12 | outputs:
13 | change: ${{ steps.covector.outputs.change }}
14 | commandRan: ${{ steps.covector.outputs.commandRan }}
15 | successfulPublish: ${{ steps.covector.outputs.successfulPublish }}
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 | with:
20 | fetch-depth: 0 # required for use of git history
21 |
22 | - name: cargo login
23 | run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
24 |
25 | - name: git config
26 | run: |
27 | git config --global user.name "${{ github.event.pusher.name }}"
28 | git config --global user.email "${{ github.event.pusher.email }}"
29 |
30 | - name: covector version or publish (publish when no change files present)
31 | uses: jbolda/covector/packages/action@covector-v0
32 | id: covector
33 | env:
34 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
35 | CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }}
36 | with:
37 | token: ${{ secrets.GITHUB_TOKEN }}
38 | command: "version-or-publish"
39 | createRelease: true
40 | recognizeContributors: true
41 |
42 | - name: install cargo-readme
43 | if: steps.covector.outputs.commandRan == 'version'
44 | run: cargo install cargo-readme --locked
45 |
46 | - run: cargo readme --no-title --no-license > README.md
47 | if: steps.covector.outputs.commandRan == 'version'
48 |
49 | - name: Sync Cargo.lock
50 | if: steps.covector.outputs.commandRan == 'version'
51 | run: cargo tree --depth 0
52 |
53 | - name: Create Pull Request With Versions Bumped
54 | id: cpr
55 | uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # 7.0.7
56 | if: steps.covector.outputs.commandRan == 'version'
57 | with:
58 | token: ${{ secrets.GITHUB_TOKEN }}
59 | title: Apply Version Updates From Current Changes
60 | commit-message: "apply version updates"
61 | labels: "version updates"
62 | branch: "ci/pending-release"
63 | body: ${{ steps.covector.outputs.change }}
64 | sign-commits: true
65 |
--------------------------------------------------------------------------------
/examples/transparent.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use tao::{
6 | event::{Event, WindowEvent},
7 | event_loop::{ControlFlow, EventLoop},
8 | window::WindowBuilder,
9 | };
10 | use wry::WebViewBuilder;
11 |
12 | fn main() -> wry::Result<()> {
13 | let event_loop = EventLoop::new();
14 | #[allow(unused_mut)]
15 | let mut builder = WindowBuilder::new()
16 | .with_decorations(false)
17 | // There are actually three layer of background color when creating webview window.
18 | // The first is window background...
19 | .with_transparent(true);
20 | #[cfg(target_os = "windows")]
21 | {
22 | use tao::platform::windows::WindowBuilderExtWindows;
23 | builder = builder.with_undecorated_shadow(false);
24 | }
25 | let window = builder.build(&event_loop).unwrap();
26 |
27 | #[cfg(target_os = "windows")]
28 | {
29 | use tao::platform::windows::WindowExtWindows;
30 | window.set_undecorated_shadow(true);
31 | }
32 |
33 | let builder = WebViewBuilder::new()
34 | // The second is on webview...
35 | // Feature `transparent` is required for transparency to work.
36 | .with_transparent(true)
37 | // And the last is in html.
38 | .with_html(
39 | r#"
40 |
41 |
46 | "#,
47 | );
48 |
49 | #[cfg(any(
50 | target_os = "windows",
51 | target_os = "macos",
52 | target_os = "ios",
53 | target_os = "android"
54 | ))]
55 | let _webview = builder.build(&window)?;
56 | #[cfg(not(any(
57 | target_os = "windows",
58 | target_os = "macos",
59 | target_os = "ios",
60 | target_os = "android"
61 | )))]
62 | let _webview = {
63 | use tao::platform::unix::WindowExtUnix;
64 | use wry::WebViewBuilderExtUnix;
65 | let vbox = window.default_vbox().unwrap();
66 | builder.build_gtk(vbox)?
67 | };
68 |
69 | event_loop.run(move |event, _, control_flow| {
70 | *control_flow = ControlFlow::Wait;
71 |
72 | if let Event::WindowEvent {
73 | event: WindowEvent::CloseRequested,
74 | ..
75 | } = event
76 | {
77 | *control_flow = ControlFlow::Exit
78 | }
79 | });
80 | }
81 |
--------------------------------------------------------------------------------
/bench/tests/src/custom_protocol.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use serde::{Deserialize, Serialize};
6 | use std::process::exit;
7 |
8 | const INDEX_HTML: &[u8] = br#"
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Welcome to WRY!
18 |
23 |
24 | "#;
25 |
26 | #[derive(Debug, Serialize, Deserialize)]
27 | struct MessageParameters {
28 | message: String,
29 | }
30 |
31 | fn main() -> wry::Result<()> {
32 | use tao::{
33 | event::{Event, WindowEvent},
34 | event_loop::{ControlFlow, EventLoop},
35 | window::WindowBuilder,
36 | };
37 | use wry::http::Request;
38 | use wry::{
39 | http::{header::CONTENT_TYPE, Response},
40 | WebViewBuilder,
41 | };
42 |
43 | let event_loop = EventLoop::new();
44 | let window = WindowBuilder::new().build(&event_loop).unwrap();
45 |
46 | let handler = |req: Request| {
47 | if req.body() == "dom-loaded" {
48 | exit(0);
49 | }
50 | };
51 |
52 | let builder = WebViewBuilder::new()
53 | .with_ipc_handler(handler)
54 | .with_custom_protocol("wrybench".into(), move |_id, _request| {
55 | Response::builder()
56 | .header(CONTENT_TYPE, "text/html")
57 | .body(INDEX_HTML.into())
58 | .unwrap()
59 | })
60 | .with_url("wrybench://localhost");
61 |
62 | #[cfg(any(
63 | target_os = "windows",
64 | target_os = "macos",
65 | target_os = "ios",
66 | target_os = "android"
67 | ))]
68 | let _webview = builder.build(&window)?;
69 |
70 | #[cfg(not(any(
71 | target_os = "windows",
72 | target_os = "macos",
73 | target_os = "ios",
74 | target_os = "android"
75 | )))]
76 | {
77 | use tao::platform::unix::WindowExtUnix;
78 | use wry::WebViewBuilderExtUnix;
79 | let vbox = window.default_vbox().unwrap();
80 | let _webview = builder.build_gtk(vbox)?;
81 | };
82 |
83 | event_loop.run(move |event, _, control_flow| {
84 | *control_flow = ControlFlow::Wait;
85 |
86 | match event {
87 | Event::WindowEvent {
88 | event: WindowEvent::CloseRequested,
89 | ..
90 | } => *control_flow = ControlFlow::Exit,
91 | _ => {}
92 | }
93 | })
94 | }
95 |
--------------------------------------------------------------------------------
/src/wkwebview/class/wry_download_delegate.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use std::{cell::RefCell, path::PathBuf, rc::Rc};
6 |
7 | use objc2::{define_class, msg_send, rc::Retained, runtime::NSObject, MainThreadOnly};
8 | use objc2_foundation::{
9 | MainThreadMarker, NSData, NSError, NSObjectProtocol, NSString, NSURLResponse, NSURL,
10 | };
11 | use objc2_web_kit::{WKDownload, WKDownloadDelegate};
12 |
13 | use crate::wkwebview::download::{download_did_fail, download_did_finish, download_policy};
14 |
15 | pub struct WryDownloadDelegateIvars {
16 | pub started: Option bool + 'static>>>,
17 | pub completed: Option, bool) + 'static>>,
18 | }
19 |
20 | define_class!(
21 | #[unsafe(super(NSObject))]
22 | #[name = "WryDownloadDelegate"]
23 | #[thread_kind = MainThreadOnly]
24 | #[ivars = WryDownloadDelegateIvars]
25 | pub struct WryDownloadDelegate;
26 |
27 | unsafe impl NSObjectProtocol for WryDownloadDelegate {}
28 |
29 | unsafe impl WKDownloadDelegate for WryDownloadDelegate {
30 | #[unsafe(method(download:decideDestinationUsingResponse:suggestedFilename:completionHandler:))]
31 | fn download_policy(
32 | &self,
33 | download: &WKDownload,
34 | response: &NSURLResponse,
35 | suggested_filename: &NSString,
36 | handler: &block2::Block,
37 | ) {
38 | download_policy(self, download, response, suggested_filename, handler);
39 | }
40 |
41 | #[unsafe(method(downloadDidFinish:))]
42 | fn download_did_finish(&self, download: &WKDownload) {
43 | download_did_finish(self, download);
44 | }
45 |
46 | #[unsafe(method(download:didFailWithError:resumeData:))]
47 | fn download_did_fail(&self, download: &WKDownload, error: &NSError, resume_data: &NSData) {
48 | download_did_fail(self, download, error, resume_data);
49 | }
50 | }
51 | );
52 |
53 | impl WryDownloadDelegate {
54 | pub fn new(
55 | download_started_handler: Option bool + 'static>>,
56 | download_completed_handler: Option, bool) + 'static>>,
57 | mtm: MainThreadMarker,
58 | ) -> Retained {
59 | let delegate = mtm
60 | .alloc::()
61 | .set_ivars(WryDownloadDelegateIvars {
62 | started: download_started_handler.map(|handler| RefCell::new(handler)),
63 | completed: download_completed_handler,
64 | });
65 |
66 | unsafe { msg_send![super(delegate), init] }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/bench/tests/src/cpu_intensive.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use std::process::exit;
6 |
7 | fn main() -> wry::Result<()> {
8 | use tao::{
9 | event::{Event, WindowEvent},
10 | event_loop::{ControlFlow, EventLoop},
11 | window::WindowBuilder,
12 | };
13 | use wry::http::Request;
14 | use wry::{
15 | http::{header::CONTENT_TYPE, Response},
16 | WebViewBuilder,
17 | };
18 |
19 | let event_loop = EventLoop::new();
20 | let window = WindowBuilder::new().build(&event_loop).unwrap();
21 |
22 | let handler = |req: Request| {
23 | if req.body() == "process-complete" {
24 | exit(0);
25 | }
26 | };
27 |
28 | let builder = WebViewBuilder::new()
29 | .with_custom_protocol("wrybench".into(), move |_id, request| {
30 | let path = request.uri().to_string();
31 | let requested_asset_path = path.strip_prefix("wrybench://localhost").unwrap();
32 | let (data, mimetype): (_, String) = match requested_asset_path {
33 | "/index.css" => (
34 | include_bytes!("static/index.css").as_slice().into(),
35 | "text/css".into(),
36 | ),
37 | "/site.js" => (
38 | include_bytes!("static/site.js").as_slice().into(),
39 | "text/javascript".into(),
40 | ),
41 | "/worker.js" => (
42 | include_bytes!("static/worker.js").as_slice().into(),
43 | "text/javascript".into(),
44 | ),
45 | _ => (
46 | include_bytes!("static/index.html").as_slice().into(),
47 | "text/html".into(),
48 | ),
49 | };
50 |
51 | Response::builder()
52 | .header(CONTENT_TYPE, mimetype)
53 | .body(data)
54 | .unwrap()
55 | })
56 | .with_url("wrybench://localhost")
57 | .with_ipc_handler(handler);
58 |
59 | #[cfg(any(
60 | target_os = "windows",
61 | target_os = "macos",
62 | target_os = "ios",
63 | target_os = "android"
64 | ))]
65 | let _webview = builder.build(&window)?;
66 |
67 | #[cfg(not(any(
68 | target_os = "windows",
69 | target_os = "macos",
70 | target_os = "ios",
71 | target_os = "android"
72 | )))]
73 | {
74 | use tao::platform::unix::WindowExtUnix;
75 | use wry::WebViewBuilderExtUnix;
76 | let vbox = window.default_vbox().unwrap();
77 | let _webview = builder.build_gtk(vbox)?;
78 | }
79 |
80 | event_loop.run(move |event, _, control_flow| {
81 | *control_flow = ControlFlow::Wait;
82 |
83 | match event {
84 | Event::WindowEvent {
85 | event: WindowEvent::CloseRequested,
86 | ..
87 | } => *control_flow = ControlFlow::Exit,
88 | _ => {}
89 | }
90 | });
91 | }
92 |
--------------------------------------------------------------------------------
/src/wkwebview/class/document_title_changed_observer.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use std::{ffi::c_void, ptr::null_mut};
6 |
7 | use objc2::{
8 | define_class, msg_send,
9 | rc::Retained,
10 | runtime::{AnyObject, NSObject},
11 | AllocAnyThread, DefinedClass,
12 | };
13 | use objc2_foundation::{
14 | NSDictionary, NSKeyValueChangeKey, NSKeyValueObservingOptions,
15 | NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSString,
16 | };
17 |
18 | use crate::WryWebView;
19 | pub struct DocumentTitleChangedObserverIvars {
20 | pub object: Retained,
21 | pub handler: Box,
22 | }
23 |
24 | define_class!(
25 | #[unsafe(super(NSObject))]
26 | #[name = "DocumentTitleChangedObserver"]
27 | #[ivars = DocumentTitleChangedObserverIvars]
28 | pub struct DocumentTitleChangedObserver;
29 |
30 | /// NSKeyValueObserving.
31 | impl DocumentTitleChangedObserver {
32 | #[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]
33 | fn observe_value_for_key_path(
34 | &self,
35 | key_path: Option<&NSString>,
36 | of_object: Option<&AnyObject>,
37 | _change: Option<&NSDictionary>,
38 | _context: *mut c_void,
39 | ) {
40 | if let (Some(key_path), Some(object)) = (key_path, of_object) {
41 | if key_path.to_string() == "title" {
42 | unsafe {
43 | let handler = &self.ivars().handler;
44 | // if !handler.is_null() {
45 | let title: *const NSString = msg_send![object, title];
46 | handler((*title).to_string());
47 | // }
48 | }
49 | }
50 | }
51 | }
52 | }
53 |
54 | unsafe impl NSObjectProtocol for DocumentTitleChangedObserver {}
55 | );
56 |
57 | impl DocumentTitleChangedObserver {
58 | pub fn new(webview: Retained, handler: Box) -> Retained {
59 | let observer = Self::alloc().set_ivars(DocumentTitleChangedObserverIvars {
60 | object: webview,
61 | handler,
62 | });
63 |
64 | let observer: Retained = unsafe { msg_send![super(observer), init] };
65 |
66 | unsafe {
67 | observer
68 | .ivars()
69 | .object
70 | .addObserver_forKeyPath_options_context(
71 | &observer,
72 | &NSString::from_str("title"),
73 | NSKeyValueObservingOptions::New,
74 | null_mut(),
75 | );
76 | }
77 |
78 | observer
79 | }
80 | }
81 |
82 | impl Drop for DocumentTitleChangedObserver {
83 | fn drop(&mut self) {
84 | unsafe {
85 | self
86 | .ivars()
87 | .object
88 | .removeObserver_forKeyPath(self, &NSString::from_str("title"));
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/examples/winit.rs:
--------------------------------------------------------------------------------
1 | use dpi::{LogicalPosition, LogicalSize};
2 | use winit::{
3 | application::ApplicationHandler,
4 | event::WindowEvent,
5 | event_loop::{ActiveEventLoop, EventLoop},
6 | window::{Window, WindowId},
7 | };
8 | use wry::{Rect, WebViewBuilder};
9 |
10 | #[derive(Default)]
11 | struct State {
12 | window: Option,
13 | webview: Option,
14 | }
15 |
16 | impl ApplicationHandler for State {
17 | fn resumed(&mut self, event_loop: &ActiveEventLoop) {
18 | let mut attributes = Window::default_attributes();
19 | attributes.inner_size = Some(LogicalSize::new(800, 800).into());
20 | let window = event_loop.create_window(attributes).unwrap();
21 |
22 | let webview = WebViewBuilder::new()
23 | .with_url("https://tauri.app")
24 | .build_as_child(&window)
25 | .unwrap();
26 |
27 | self.window = Some(window);
28 | self.webview = Some(webview);
29 | }
30 |
31 | fn window_event(
32 | &mut self,
33 | _event_loop: &ActiveEventLoop,
34 | _window_id: WindowId,
35 | event: WindowEvent,
36 | ) {
37 | match event {
38 | WindowEvent::Resized(size) => {
39 | let window = self.window.as_ref().unwrap();
40 | let webview = self.webview.as_ref().unwrap();
41 |
42 | let size = size.to_logical::(window.scale_factor());
43 | webview
44 | .set_bounds(Rect {
45 | position: LogicalPosition::new(0, 0).into(),
46 | size: LogicalSize::new(size.width, size.height).into(),
47 | })
48 | .unwrap();
49 | }
50 | WindowEvent::CloseRequested => {
51 | std::process::exit(0);
52 | }
53 | _ => {}
54 | }
55 | }
56 |
57 | fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
58 | #[cfg(any(
59 | target_os = "linux",
60 | target_os = "dragonfly",
61 | target_os = "freebsd",
62 | target_os = "netbsd",
63 | target_os = "openbsd",
64 | ))]
65 | {
66 | while gtk::events_pending() {
67 | gtk::main_iteration_do(false);
68 | }
69 | }
70 | }
71 | }
72 |
73 | fn main() -> wry::Result<()> {
74 | #[cfg(any(
75 | target_os = "linux",
76 | target_os = "dragonfly",
77 | target_os = "freebsd",
78 | target_os = "netbsd",
79 | target_os = "openbsd",
80 | ))]
81 | {
82 | use gtk::prelude::DisplayExtManual;
83 |
84 | gtk::init().unwrap();
85 | if gtk::gdk::Display::default().unwrap().backend().is_wayland() {
86 | panic!("This example doesn't support wayland!");
87 | }
88 |
89 | winit::platform::x11::register_xlib_error_hook(Box::new(|_display, error| {
90 | let error = error as *mut x11_dl::xlib::XErrorEvent;
91 | (unsafe { (*error).error_code }) == 170
92 | }));
93 | }
94 |
95 | let event_loop = EventLoop::new().unwrap();
96 | let mut state = State::default();
97 | event_loop.run_app(&mut state).unwrap();
98 |
99 | Ok(())
100 | }
101 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 | /// Convenient type alias of Result type for wry.
2 | pub type Result = std::result::Result;
3 |
4 | /// Errors returned by wry.
5 | #[non_exhaustive]
6 | #[derive(thiserror::Error, Debug)]
7 | pub enum Error {
8 | #[cfg(gtk)]
9 | #[error(transparent)]
10 | GlibError(#[from] gtk::glib::Error),
11 | #[cfg(gtk)]
12 | #[error(transparent)]
13 | GlibBoolError(#[from] gtk::glib::BoolError),
14 | #[cfg(gtk)]
15 | #[error("Fail to fetch security manager")]
16 | MissingManager,
17 | #[cfg(gtk)]
18 | #[error("Couldn't find X11 Display")]
19 | X11DisplayNotFound,
20 | #[cfg(all(gtk, feature = "x11"))]
21 | #[error(transparent)]
22 | XlibError(#[from] x11_dl::error::OpenError),
23 | #[error("Failed to initialize the script")]
24 | InitScriptError,
25 | #[error("Bad RPC request: {0} ((1))")]
26 | RpcScriptError(String, String),
27 | #[error(transparent)]
28 | NulError(#[from] std::ffi::NulError),
29 | #[error(transparent)]
30 | ReceiverError(#[from] std::sync::mpsc::RecvError),
31 | #[cfg(target_os = "android")]
32 | #[error(transparent)]
33 | ReceiverTimeoutError(#[from] crossbeam_channel::RecvTimeoutError),
34 | #[error(transparent)]
35 | SenderError(#[from] std::sync::mpsc::SendError),
36 | #[error("Failed to send the message")]
37 | MessageSender,
38 | #[error("IO error: {0}")]
39 | Io(#[from] std::io::Error),
40 | #[cfg(target_os = "windows")]
41 | #[error("WebView2 error: {0}")]
42 | WebView2Error(webview2_com::Error),
43 | #[error(transparent)]
44 | HttpError(#[from] http::Error),
45 | #[error("Infallible error, something went really wrong: {0}")]
46 | Infallible(#[from] std::convert::Infallible),
47 | #[cfg(target_os = "android")]
48 | #[error(transparent)]
49 | JniError(#[from] jni::errors::Error),
50 | #[error("Failed to create proxy endpoint")]
51 | ProxyEndpointCreationFailed,
52 | #[error(transparent)]
53 | WindowHandleError(#[from] raw_window_handle::HandleError),
54 | #[error("the window handle kind is not supported")]
55 | UnsupportedWindowHandle,
56 | #[error(transparent)]
57 | Utf8Error(#[from] std::str::Utf8Error),
58 | #[cfg(target_os = "android")]
59 | #[error(transparent)]
60 | CrossBeamRecvError(#[from] crossbeam_channel::RecvError),
61 | #[error("not on the main thread")]
62 | NotMainThread,
63 | #[error("Custom protocol task is invalid.")]
64 | CustomProtocolTaskInvalid,
65 | #[error("Failed to register URL scheme: {0}, could be due to invalid URL scheme or the scheme is already registered.")]
66 | UrlSchemeRegisterError(String),
67 | #[error("Duplicate custom protocol '{0}' registered on the WebViewBuilder")]
68 | DuplicateCustomProtocol(String),
69 | #[error("Duplicate custom protocol '{0}' registered on the same web context on Linux")]
70 | ContextDuplicateCustomProtocol(String),
71 | #[error(transparent)]
72 | #[cfg(any(target_os = "macos", target_os = "ios"))]
73 | UrlParse(#[from] url::ParseError),
74 | #[cfg(any(target_os = "macos", target_os = "ios"))]
75 | #[error("data store is currently opened")]
76 | DataStoreInUse,
77 | }
78 |
--------------------------------------------------------------------------------
/.github/workflows/bench.yml:
--------------------------------------------------------------------------------
1 | name: benches
2 |
3 | on:
4 | push:
5 | branches:
6 | - dev
7 | workflow_dispatch:
8 |
9 | env:
10 | RUST_BACKTRACE: 1
11 | CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency.
12 | LC_ALL: en_US.UTF-8 # This prevents strace from changing its number format to use commas.
13 |
14 | concurrency:
15 | group: ${{ github.workflow }}-${{ github.ref }}
16 | cancel-in-progress: true
17 |
18 | jobs:
19 | bench:
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | rust: [nightly]
24 | platform:
25 | - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }
26 |
27 | runs-on: ${{ matrix.platform.os }}
28 |
29 | steps:
30 | - uses: actions/checkout@v4
31 |
32 | - name: install Rust ${{ matrix.rust }}
33 | uses: dtolnay/rust-toolchain@master
34 | with:
35 | toolchain: ${{ matrix.rust }}
36 | components: rust-src
37 | targets: ${{ matrix.platform.target }}
38 |
39 | - name: Setup python
40 | uses: actions/setup-python@v4
41 | with:
42 | python-version: '3.10'
43 | architecture: x64
44 |
45 | - name: install dependencies
46 | run: |
47 | python -m pip install --upgrade pip
48 | sudo apt-get update
49 | sudo apt-get install -y --no-install-recommends \
50 | libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \
51 | xvfb \
52 | at-spi2-core
53 | wget https://github.com/sharkdp/hyperfine/releases/download/v1.18.0/hyperfine_1.18.0_amd64.deb
54 | sudo dpkg -i hyperfine_1.18.0_amd64.deb
55 | pip install memory_profiler
56 |
57 | - uses: Swatinem/rust-cache@v2
58 | with:
59 | workspaces: |
60 | .
61 | bench/tests
62 |
63 | - name: run benchmarks
64 | run: |
65 | cargo +nightly build --release -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target ${{ matrix.platform.target }} --manifest-path bench/tests/Cargo.toml
66 | xvfb-run --auto-servernum cargo run --manifest-path ./bench/Cargo.toml --bin run_benchmark
67 |
68 | - name: clone benchmarks_results
69 | if: github.repository == 'tauri-apps/wry' && github.ref == 'refs/heads/dev'
70 | uses: actions/checkout@v4
71 | with:
72 | token: ${{ secrets.BENCH_PAT }}
73 | path: gh-pages
74 | repository: tauri-apps/benchmark_results
75 |
76 | - name: push new benchmarks
77 | if: github.repository == 'tauri-apps/wry' && github.ref == 'refs/heads/dev'
78 | run: |
79 | cargo run --manifest-path ./bench/Cargo.toml --bin build_benchmark_jsons
80 | cd gh-pages
81 | git pull
82 | git config user.name "tauri-bench"
83 | git config user.email "gh.tauribot@gmail.com"
84 | git add .
85 | git commit --message "Update WRY benchmarks"
86 | git push origin gh-pages
87 |
88 | - name: Print worker info
89 | run: |
90 | cat /proc/cpuinfo
91 | cat /proc/meminfo
92 |
--------------------------------------------------------------------------------
/src/android/kotlin/RustWebView.kt:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | @file:Suppress("unused", "SetJavaScriptEnabled")
6 |
7 | package {{package}}
8 |
9 | import android.annotation.SuppressLint
10 | import android.webkit.*
11 | import android.content.Context
12 | import androidx.webkit.WebViewCompat
13 | import androidx.webkit.WebViewFeature
14 | import kotlin.collections.Map
15 |
16 | @SuppressLint("RestrictedApi")
17 | class RustWebView(context: Context, val initScripts: Array, val id: String): WebView(context) {
18 | val isDocumentStartScriptEnabled: Boolean
19 |
20 | init {
21 | settings.javaScriptEnabled = true
22 | settings.domStorageEnabled = true
23 | settings.setGeolocationEnabled(true)
24 | settings.databaseEnabled = true
25 | settings.mediaPlaybackRequiresUserGesture = false
26 | settings.javaScriptCanOpenWindowsAutomatically = true
27 |
28 | if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
29 | isDocumentStartScriptEnabled = true
30 | for (script in initScripts) {
31 | WebViewCompat.addDocumentStartJavaScript(this, script, setOf("*"));
32 | }
33 | } else {
34 | isDocumentStartScriptEnabled = false
35 | }
36 |
37 | {{class-init}}
38 | }
39 |
40 | fun loadUrlMainThread(url: String) {
41 | post {
42 | loadUrl(url)
43 | }
44 | }
45 |
46 | fun loadUrlMainThread(url: String, additionalHttpHeaders: Map) {
47 | post {
48 | loadUrl(url, additionalHttpHeaders)
49 | }
50 | }
51 |
52 | override fun loadUrl(url: String) {
53 | if (!shouldOverride(url)) {
54 | super.loadUrl(url);
55 | }
56 | }
57 |
58 | override fun loadUrl(url: String, additionalHttpHeaders: Map) {
59 | if (!shouldOverride(url)) {
60 | super.loadUrl(url, additionalHttpHeaders);
61 | }
62 | }
63 |
64 | fun loadHTMLMainThread(html: String) {
65 | post {
66 | super.loadData(html, "text/html", null)
67 | }
68 | }
69 |
70 | fun evalScript(id: Int, script: String) {
71 | post {
72 | super.evaluateJavascript(script) { result ->
73 | onEval(id, result)
74 | }
75 | }
76 | }
77 |
78 | fun clearAllBrowsingData() {
79 | try {
80 | super.getContext().deleteDatabase("webviewCache.db")
81 | super.getContext().deleteDatabase("webview.db")
82 | super.clearCache(true)
83 | super.clearHistory()
84 | super.clearFormData()
85 | } catch (ex: Exception) {
86 | Logger.error("Unable to create temporary media capture file: " + ex.message)
87 | }
88 | }
89 |
90 | fun getCookies(url: String): String {
91 | val cookieManager = CookieManager.getInstance()
92 | return cookieManager.getCookie(url)
93 | }
94 |
95 | private external fun shouldOverride(url: String): Boolean
96 | private external fun onEval(id: Int, result: String)
97 |
98 | {{class-extension}}
99 | }
100 |
--------------------------------------------------------------------------------
/examples/reparent.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use tao::{
6 | event::{ElementState, Event, KeyEvent, WindowEvent},
7 | event_loop::{ControlFlow, EventLoop},
8 | keyboard::Key,
9 | window::WindowBuilder,
10 | };
11 | use wry::WebViewBuilder;
12 |
13 | #[cfg(target_os = "macos")]
14 | use {objc2_app_kit::NSWindow, tao::platform::macos::WindowExtMacOS, wry::WebViewExtMacOS};
15 | #[cfg(target_os = "windows")]
16 | use {tao::platform::windows::WindowExtWindows, wry::WebViewExtWindows};
17 |
18 | #[cfg(not(any(
19 | target_os = "windows",
20 | target_os = "macos",
21 | target_os = "ios",
22 | target_os = "android"
23 | )))]
24 | #[cfg(not(any(
25 | target_os = "windows",
26 | target_os = "macos",
27 | target_os = "ios",
28 | target_os = "android"
29 | )))]
30 | use {
31 | tao::platform::unix::WindowExtUnix,
32 | wry::{WebViewBuilderExtUnix, WebViewExtUnix},
33 | };
34 |
35 | fn main() -> wry::Result<()> {
36 | let event_loop = EventLoop::new();
37 | let window = WindowBuilder::new().build(&event_loop).unwrap();
38 | let window2 = WindowBuilder::new().build(&event_loop).unwrap();
39 |
40 | let builder = WebViewBuilder::new().with_url("https://tauri.app");
41 |
42 | #[cfg(any(
43 | target_os = "windows",
44 | target_os = "macos",
45 | target_os = "ios",
46 | target_os = "android"
47 | ))]
48 | let webview = builder.build(&window)?;
49 | #[cfg(not(any(
50 | target_os = "windows",
51 | target_os = "macos",
52 | target_os = "ios",
53 | target_os = "android"
54 | )))]
55 | let webview = {
56 | use tao::platform::unix::WindowExtUnix;
57 | let vbox = window.default_vbox().unwrap();
58 | builder.build_gtk(vbox)?
59 | };
60 |
61 | let mut webview_container = window.id();
62 |
63 | event_loop.run(move |event, _event_loop, control_flow| {
64 | *control_flow = ControlFlow::Wait;
65 |
66 | match event {
67 | Event::WindowEvent {
68 | event: WindowEvent::CloseRequested,
69 | ..
70 | } => *control_flow = ControlFlow::Exit,
71 |
72 | Event::WindowEvent {
73 | event:
74 | WindowEvent::KeyboardInput {
75 | event:
76 | KeyEvent {
77 | logical_key: Key::Character("x"),
78 | state: ElementState::Pressed,
79 | ..
80 | },
81 | ..
82 | },
83 | ..
84 | } => {
85 | let new_parent = if webview_container == window.id() {
86 | &window2
87 | } else {
88 | &window
89 | };
90 | webview_container = new_parent.id();
91 |
92 | #[cfg(target_os = "macos")]
93 | webview
94 | .reparent(new_parent.ns_window() as *mut NSWindow)
95 | .unwrap();
96 | #[cfg(not(any(
97 | target_os = "windows",
98 | target_os = "macos",
99 | target_os = "ios",
100 | target_os = "android"
101 | )))]
102 | webview
103 | .reparent(new_parent.default_vbox().unwrap())
104 | .unwrap();
105 | #[cfg(target_os = "windows")]
106 | webview.reparent(new_parent.hwnd()).unwrap();
107 | }
108 | _ => {}
109 | }
110 | });
111 | }
112 |
--------------------------------------------------------------------------------
/examples/window_border.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use dpi::LogicalSize;
6 | use tao::{
7 | event::{Event, StartCause, WindowEvent},
8 | event_loop::{ControlFlow, EventLoopBuilder},
9 | window::WindowBuilder,
10 | };
11 | use wry::{http::Request, WebViewBuilder};
12 |
13 | #[derive(Debug)]
14 | enum UserEvent {
15 | TogglShadows,
16 | }
17 |
18 | fn main() -> wry::Result<()> {
19 | let event_loop = EventLoopBuilder::::with_user_event().build();
20 | let window = WindowBuilder::new()
21 | .with_inner_size(LogicalSize::new(500, 500))
22 | .with_decorations(false)
23 | .build(&event_loop)
24 | .unwrap();
25 |
26 | const HTML: &str = r#"
27 |
28 |
29 |
30 |
45 |
46 |
47 |
48 |
49 | Click the window to toggle shadows.
50 |
51 |
52 |
55 |
56 |
57 |
58 | "#;
59 |
60 | let proxy = event_loop.create_proxy();
61 | let handler = move |req: Request| {
62 | if req.body().as_str() == "toggleShadows" {
63 | proxy.send_event(UserEvent::TogglShadows).unwrap();
64 | }
65 | };
66 |
67 | let builder = WebViewBuilder::new()
68 | .with_html(HTML)
69 | .with_ipc_handler(handler)
70 | .with_accept_first_mouse(true);
71 |
72 | #[cfg(any(
73 | target_os = "windows",
74 | target_os = "macos",
75 | target_os = "ios",
76 | target_os = "android"
77 | ))]
78 | let webview = builder.build(&window)?;
79 | #[cfg(not(any(
80 | target_os = "windows",
81 | target_os = "macos",
82 | target_os = "ios",
83 | target_os = "android"
84 | )))]
85 | let webview = {
86 | use tao::platform::unix::WindowExtUnix;
87 | use wry::WebViewBuilderExtUnix;
88 | let vbox = window.default_vbox().unwrap();
89 | builder.build_gtk(vbox)?
90 | };
91 |
92 | let mut webview = Some(webview);
93 |
94 | let mut shadow = true;
95 |
96 | event_loop.run(move |event, _, control_flow| {
97 | *control_flow = ControlFlow::Wait;
98 |
99 | match event {
100 | Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
101 | Event::WindowEvent {
102 | event: WindowEvent::CloseRequested,
103 | ..
104 | } => {
105 | let _ = webview.take();
106 | *control_flow = ControlFlow::Exit
107 | }
108 |
109 | Event::UserEvent(e) => match e {
110 | UserEvent::TogglShadows => {
111 | shadow = !shadow;
112 | #[cfg(windows)]
113 | {
114 | use tao::platform::windows::WindowExtWindows;
115 | window.set_undecorated_shadow(shadow);
116 | }
117 | }
118 | },
119 | _ => (),
120 | }
121 | });
122 | }
123 |
--------------------------------------------------------------------------------
/src/web_context.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | #[cfg(gtk)]
6 | use crate::webkitgtk::WebContextImpl;
7 |
8 | use std::{
9 | collections::HashSet,
10 | path::{Path, PathBuf},
11 | };
12 |
13 | /// A context that is shared between multiple [`WebView`]s.
14 | ///
15 | /// A browser would have a context for all the normal tabs and a different context for all the
16 | /// private/incognito tabs.
17 | ///
18 | /// # Warning
19 | /// If [`WebView`] is created by a WebContext. Dropping `WebContext` will cause [`WebView`] lose
20 | /// some actions like custom protocol on Mac. Please keep both instances when you still wish to
21 | /// interact with them.
22 | ///
23 | /// [`WebView`]: crate::WebView
24 | #[derive(Debug)]
25 | pub struct WebContext {
26 | data_directory: Option,
27 | #[allow(dead_code)] // It's not needed on Windows and macOS.
28 | pub(crate) os: WebContextImpl,
29 | #[allow(dead_code)] // It's not needed on Windows and macOS.
30 | pub(crate) custom_protocols: HashSet,
31 | }
32 |
33 | impl WebContext {
34 | /// Create a new [`WebContext`].
35 | ///
36 | /// `data_directory`:
37 | /// * Whether the WebView window should have a custom user data path. This is useful in Windows
38 | /// when a bundled application can't have the webview data inside `Program Files`.
39 | pub fn new(data_directory: Option) -> Self {
40 | Self {
41 | os: WebContextImpl::new(data_directory.as_deref()),
42 | data_directory,
43 | custom_protocols: Default::default(),
44 | }
45 | }
46 |
47 | #[cfg(gtk)]
48 | pub(crate) fn new_ephemeral() -> Self {
49 | Self {
50 | os: WebContextImpl::new_ephemeral(),
51 | data_directory: None,
52 | custom_protocols: Default::default(),
53 | }
54 | }
55 |
56 | /// A reference to the data directory the context was created with.
57 | pub fn data_directory(&self) -> Option<&Path> {
58 | self.data_directory.as_deref()
59 | }
60 |
61 | #[cfg(any(
62 | target_os = "linux",
63 | target_os = "dragonfly",
64 | target_os = "freebsd",
65 | target_os = "netbsd",
66 | target_os = "openbsd",
67 | ))]
68 | pub(crate) fn register_custom_protocol(&mut self, name: String) -> Result<(), crate::Error> {
69 | if self.is_custom_protocol_registered(&name) {
70 | return Err(crate::Error::ContextDuplicateCustomProtocol(name));
71 | }
72 | self.custom_protocols.insert(name);
73 | Ok(())
74 | }
75 |
76 | /// Check if a custom protocol has been registered on this context.
77 | pub fn is_custom_protocol_registered(&self, name: &str) -> bool {
78 | self.custom_protocols.contains(name)
79 | }
80 |
81 | /// Set if this context allows automation.
82 | ///
83 | /// **Note:** This is currently only enforced on Linux, and has the stipulation that
84 | /// only 1 context allows automation at a time.
85 | pub fn set_allows_automation(&mut self, flag: bool) {
86 | self.os.set_allows_automation(flag);
87 | }
88 | }
89 |
90 | impl Default for WebContext {
91 | fn default() -> Self {
92 | Self::new(None)
93 | }
94 | }
95 |
96 | #[cfg(not(gtk))]
97 | #[derive(Debug)]
98 | pub(crate) struct WebContextImpl;
99 |
100 | #[cfg(not(gtk))]
101 | impl WebContextImpl {
102 | fn new(_: Option<&Path>) -> Self {
103 | Self
104 | }
105 |
106 | fn set_allows_automation(&mut self, _flag: bool) {}
107 | }
108 |
--------------------------------------------------------------------------------
/examples/custom_protocol.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | fn main() -> wry::Result<()> {
6 | imp::main()
7 | }
8 |
9 | #[cfg(not(feature = "protocol"))]
10 | mod imp {
11 | pub fn main() -> wry::Result<()> {
12 | unimplemented!()
13 | }
14 | }
15 |
16 | #[cfg(feature = "protocol")]
17 | mod imp {
18 | use std::path::PathBuf;
19 |
20 | use tao::{
21 | event::{Event, WindowEvent},
22 | event_loop::{ControlFlow, EventLoop},
23 | window::WindowBuilder,
24 | };
25 | use wry::{
26 | http::{header::CONTENT_TYPE, Request, Response},
27 | WebViewBuilder,
28 | };
29 |
30 | pub fn main() -> wry::Result<()> {
31 | let event_loop = EventLoop::new();
32 | let window = WindowBuilder::new().build(&event_loop).unwrap();
33 |
34 | let builder = WebViewBuilder::new()
35 | .with_custom_protocol(
36 | "wry".into(),
37 | move |_webview_id, request| match get_wry_response(request) {
38 | Ok(r) => r.map(Into::into),
39 | Err(e) => http::Response::builder()
40 | .header(CONTENT_TYPE, "text/plain")
41 | .status(500)
42 | .body(e.to_string().as_bytes().to_vec())
43 | .unwrap()
44 | .map(Into::into),
45 | },
46 | )
47 | // tell the webview to load the custom protocol
48 | .with_url("wry://localhost");
49 |
50 | #[cfg(any(
51 | target_os = "windows",
52 | target_os = "macos",
53 | target_os = "ios",
54 | target_os = "android"
55 | ))]
56 | let _webview = builder.build(&window)?;
57 | #[cfg(not(any(
58 | target_os = "windows",
59 | target_os = "macos",
60 | target_os = "ios",
61 | target_os = "android"
62 | )))]
63 | let _webview = {
64 | use tao::platform::unix::WindowExtUnix;
65 | use wry::WebViewBuilderExtUnix;
66 | let vbox = window.default_vbox().unwrap();
67 | builder.build_gtk(vbox)?
68 | };
69 |
70 | event_loop.run(move |event, _, control_flow| {
71 | *control_flow = ControlFlow::Wait;
72 |
73 | if let Event::WindowEvent {
74 | event: WindowEvent::CloseRequested,
75 | ..
76 | } = event
77 | {
78 | *control_flow = ControlFlow::Exit
79 | }
80 | });
81 | }
82 |
83 | fn get_wry_response(
84 | request: Request>,
85 | ) -> Result>, Box> {
86 | let path = request.uri().path();
87 | // Read the file content from file path
88 | let root = PathBuf::from("examples/custom_protocol");
89 | let path = if path == "/" {
90 | "index.html"
91 | } else {
92 | // removing leading slash
93 | &path[1..]
94 | };
95 | let content = std::fs::read(std::fs::canonicalize(root.join(path))?)?;
96 |
97 | // Return asset contents and mime types based on file extentions
98 | // If you don't want to do this manually, there are some crates for you.
99 | // Such as `infer` and `mime_guess`.
100 | let mimetype = if path.ends_with(".html") || path == "/" {
101 | "text/html"
102 | } else if path.ends_with(".js") {
103 | "text/javascript"
104 | } else if path.ends_with(".png") {
105 | "image/png"
106 | } else if path.ends_with(".wasm") {
107 | "application/wasm"
108 | } else {
109 | unimplemented!();
110 | };
111 |
112 | Response::builder()
113 | .header(CONTENT_TYPE, mimetype)
114 | .body(content)
115 | .map_err(Into::into)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/webview2/util.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use once_cell::sync::Lazy;
6 | use windows::{
7 | core::{HRESULT, HSTRING, PCSTR},
8 | Win32::{
9 | Foundation::{FARPROC, HWND, S_OK},
10 | Graphics::Gdi::{
11 | GetDC, GetDeviceCaps, MonitorFromWindow, HMONITOR, LOGPIXELSX, MONITOR_DEFAULTTONEAREST,
12 | },
13 | System::LibraryLoader::{GetProcAddress, LoadLibraryW},
14 | UI::{
15 | HiDpi::{MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE},
16 | WindowsAndMessaging::IsProcessDPIAware,
17 | },
18 | },
19 | };
20 |
21 | fn get_function_impl(library: &str, function: &str) -> FARPROC {
22 | let library = HSTRING::from(library);
23 | assert_eq!(function.chars().last(), Some('\0'));
24 |
25 | // Library names we will use are ASCII so we can use the A version to avoid string conversion.
26 | let module = unsafe { LoadLibraryW(&library) }.unwrap_or_default();
27 | if module.is_invalid() {
28 | return None;
29 | }
30 |
31 | unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) }
32 | }
33 |
34 | macro_rules! get_function {
35 | ($lib:expr, $func:ident) => {
36 | crate::webview2::util::get_function_impl($lib, concat!(stringify!($func), '\0'))
37 | .map(|f| unsafe { std::mem::transmute::<_, $func>(f) })
38 | };
39 | }
40 |
41 | pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
42 | pub type GetDpiForMonitor = unsafe extern "system" fn(
43 | hmonitor: HMONITOR,
44 | dpi_type: MONITOR_DPI_TYPE,
45 | dpi_x: *mut u32,
46 | dpi_y: *mut u32,
47 | ) -> HRESULT;
48 |
49 | static GET_DPI_FOR_WINDOW: Lazy