From b0a08b165215823ed7a48a0a377e0f09832898df Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Mon, 14 Aug 2023 01:25:44 +0300 Subject: [PATCH] chore: update to tao@0.22 (#997) Co-authored-by: Lucas Nogueira Co-authored-by: Lucas Nogueira --- .changes/tao-0.22.md | 5 + .github/workflows/build.yml | 6 +- Cargo.toml | 5 +- README.md | 8 +- examples/README.md | 14 --- examples/custom_titlebar.rs | 10 +- examples/download_event.rs | 4 +- examples/eval_js.rs | 10 +- examples/menu.rs | 108 -------------------- examples/multi_window.rs | 18 ++-- examples/navigation_event.rs | 4 +- examples/new_window_req_event.rs | 4 +- src/lib.rs | 3 +- src/webview/android/binding.rs | 92 +++++++++-------- src/webview/android/main_pipe.rs | 170 ++++++++++++++++++------------- src/webview/android/mod.rs | 102 ++++++++----------- src/webview/mod.rs | 4 +- src/webview/webkitgtk/mod.rs | 9 +- 18 files changed, 236 insertions(+), 340 deletions(-) create mode 100644 .changes/tao-0.22.md delete mode 100644 examples/README.md delete mode 100644 examples/menu.rs diff --git a/.changes/tao-0.22.md b/.changes/tao-0.22.md new file mode 100644 index 000000000..8d29079d2 --- /dev/null +++ b/.changes/tao-0.22.md @@ -0,0 +1,5 @@ +--- +"wry": "minor" +--- + +Update `tao` to version `0.22` which has removed the global-shortcut, menus and tray features, see [tao@v0.22 release](https://github.com/tauri-apps/tao/releases/tag/tao-v0.22.0). diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b1b85493..fdfecd163 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,17 +75,17 @@ jobs: ${{ matrix.platform }}-stable-cargo-core- - name: build wry - run: cargo build --features tray --target ${{ matrix.platform.target }} + run: cargo build --target ${{ matrix.platform.target }} - name: build tests and examples shell: bash if: ( !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios')) - run: cargo test --no-run --verbose --features tray --target ${{ matrix.platform.target }} + run: cargo test --no-run --verbose --target ${{ matrix.platform.target }} - name: run tests if: ( !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios')) - run: cargo test --verbose --features tray --target ${{ matrix.platform.target }} + run: cargo test --verbose --target ${{ matrix.platform.target }} diff --git a/Cargo.toml b/Cargo.toml index 9a92cf9b9..e8a7c24c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ categories = [ "gui" ] [package.metadata.docs.rs] default-features = false -features = [ "dox", "file-drop", "protocol", "tray" ] +features = [ "dox", "file-drop", "protocol" ] targets = [ "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", @@ -27,7 +27,6 @@ objc-exception = [ "objc/exception" ] file-drop = [ ] protocol = [ ] dox = [ "tao/dox", "webkit2gtk/dox", "soup3/dox" ] -tray = [ "tao/tray" ] devtools = [ ] transparent = [ ] fullscreen = [ ] @@ -41,7 +40,7 @@ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" thiserror = "1.0" url = "2.4" -tao = { version = "0.21", default-features = false, features = [ "serde" ] } +tao = { version = "0.22", default-features = false, features = [ "serde" ] } http = "0.2.9" [dev-dependencies] diff --git a/README.md b/README.md index d99afc9f5..cd21f9933 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,6 @@ Tao uses [gtk-rs](https://gtk-rs.org/) and its related libraries for window crea ```bash sudo pacman -S webkit2gtk-4.1 -sudo pacman -S libappindicator-gtk3 # For tray feature ``` The `libayatana-indicator` package can be installed from the Arch User Repository (AUR). @@ -90,17 +89,12 @@ The `libayatana-indicator` package can be installed from the Arch User Repositor ```bash sudo apt install libwebkit2gtk-4.1-dev -# For tray feature, choose one of following package -sudo apt install libayatana-appindicator3-dev -sudo apt install libappindicator3-dev ``` #### Fedora ```bash sudo dnf install gtk3-devel webkit2gtk4.1-devel -# For tray feature -sudo dnf install libappindicator-gtk3-devel ``` Fedora does not have the Ayatana package yet, so you need to use the GTK one, see the [feature flags documentation](https://docs.rs/wry/latest/wry/#feature-flags). @@ -121,7 +115,7 @@ WebView2 provided by Microsoft Edge Chromium is used. So wry supports Windows 7, ### Android / iOS -Wry supports mobile with the help of [`tauri-mobile`](https://github.com/tauri-apps/tauri-mobile) CLI to create template project. If you are interested in playing or hacking it, please follow [MOBILE.md](MOBILE.md). +Wry supports mobile with the help of [`tauri-mobile`](https://github.com/tauri-apps/tauri-mobile) CLI to create template project. If you are interested in playing or hacking it, please follow [MOBILE.md](MOBILE.md). If you wish to create Android project yourself, there are a few kotlin files that are needed to run wry on Android and you have to set the following environment variables: diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 667dff72d..000000000 --- a/examples/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Examples - -Run the `cargo run --example ` to see how each example works. - -- `custom_protocol`: uses a custom protocol to load files from bytes. -- `custom_titlebar`: A frameless window with custom title-bar to show `drag-region` class in action. -- `detect_js_ecma`: detects which versions of ECMAScript is supported by the webview. -- `dragndrop`: example for file drop handler. -- `form`: submit form POST and get data in rust without any web server. -- `fullscreen`: full screen example demonstrates how to configure the window with attributes. -- `hello_world`: the basic example to show the types and methods to create an application. -- `multi_window`: create the window dynamically even after the application is running. -- `stream_range`: read the incoming header from the custom protocol and return part of the data. [RFC7233](https://httpwg.org/specs/rfc7233.html#header.range) -- `transparent`: transparent example that also show how to create a valid data URI. diff --git a/examples/custom_titlebar.rs b/examples/custom_titlebar.rs index e0d4b0028..8b07a125d 100644 --- a/examples/custom_titlebar.rs +++ b/examples/custom_titlebar.rs @@ -6,17 +6,17 @@ fn main() -> wry::Result<()> { use wry::{ application::{ event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + event_loop::{ControlFlow, EventLoopBuilder}, window::{Window, WindowBuilder}, }, webview::WebViewBuilder, }; - enum UserEvents { + enum UserEvent { CloseWindow, } - let event_loop = EventLoop::::with_user_event(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let window = WindowBuilder::new() .with_decorations(false) .build(&event_loop) @@ -124,7 +124,7 @@ fn main() -> wry::Result<()> { window.set_maximized(!window.is_maximized()); } if req == "close" { - let _ = proxy.send_event(UserEvents::CloseWindow); + let _ = proxy.send_event(UserEvent::CloseWindow); } if req == "drag_window" { let _ = window.drag_window(); @@ -149,7 +149,7 @@ fn main() -> wry::Result<()> { event: WindowEvent::CloseRequested, .. } - | Event::UserEvent(UserEvents::CloseWindow) => { + | Event::UserEvent(UserEvent::CloseWindow) => { let _ = webview.take(); *control_flow = ControlFlow::Exit } diff --git a/examples/download_event.rs b/examples/download_event.rs index 0ca17991b..e2715d1fc 100644 --- a/examples/download_event.rs +++ b/examples/download_event.rs @@ -7,7 +7,7 @@ fn main() -> wry::Result<()> { use wry::{ application::{ event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + event_loop::{ControlFlow, EventLoopBuilder}, window::WindowBuilder, }, webview::WebViewBuilder, @@ -29,7 +29,7 @@ fn main() -> wry::Result<()> { Rejected(String), } - let event_loop: EventLoop = EventLoop::with_user_event(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let proxy = event_loop.create_proxy(); let window = WindowBuilder::new() .with_title("Hello World") diff --git a/examples/eval_js.rs b/examples/eval_js.rs index b5ae86d54..6f4bc2299 100644 --- a/examples/eval_js.rs +++ b/examples/eval_js.rs @@ -6,17 +6,17 @@ fn main() -> wry::Result<()> { use wry::{ application::{ event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + event_loop::{ControlFlow, EventLoopBuilder}, window::{Window, WindowBuilder}, }, webview::WebViewBuilder, }; - enum UserEvents { + enum UserEvent { ExecEval, } - let event_loop = EventLoop::::with_user_event(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let proxy = event_loop.create_proxy(); let window = WindowBuilder::new() @@ -25,7 +25,7 @@ fn main() -> wry::Result<()> { let ipc_handler = move |_: &Window, req: String| { if req == "exec-eval" { - let _ = proxy.send_event(UserEvents::ExecEval); + let _ = proxy.send_event(UserEvent::ExecEval); } }; @@ -42,7 +42,7 @@ fn main() -> wry::Result<()> { *control_flow = ControlFlow::Wait; match event { - Event::UserEvent(UserEvents::ExecEval) => { + Event::UserEvent(UserEvent::ExecEval) => { // String _webview .evaluate_script_with_callback( diff --git a/examples/menu.rs b/examples/menu.rs deleted file mode 100644 index 5180f9f27..000000000 --- a/examples/menu.rs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2020-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -const PAGE1_HTML: &[u8] = include_bytes!("custom_protocol_page1.html"); - -fn main() -> wry::Result<()> { - use std::{ - fs::{canonicalize, read}, - path::PathBuf, - }; - - use wry::{ - application::{ - accelerator::Accelerator, - event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::{KeyCode, ModifiersState}, - menu::{MenuBar, MenuItemAttributes}, - window::WindowBuilder, - }, - http::{header::CONTENT_TYPE, Response}, - webview::WebViewBuilder, - }; - - let event_loop = EventLoop::new(); - - let mut menu = MenuBar::new(); - let mut file_menu = MenuBar::new(); - file_menu.add_native_item(tao::menu::MenuItem::Cut); - file_menu.add_native_item(tao::menu::MenuItem::Copy); - file_menu.add_native_item(tao::menu::MenuItem::Paste); - file_menu.add_item( - MenuItemAttributes::new("Quit").with_accelerators(&Accelerator::new( - Some(ModifiersState::CONTROL | ModifiersState::SHIFT), - KeyCode::KeyQ, - )), - ); - file_menu.add_item( - MenuItemAttributes::new("Quit").with_accelerators(&Accelerator::new(None, KeyCode::KeyQ)), - ); - file_menu.add_item( - MenuItemAttributes::new("Quit").with_accelerators(&Accelerator::new( - Some(ModifiersState::SHIFT), - KeyCode::KeyQ, - )), - ); - menu.add_submenu("File", true, file_menu); - - let window = WindowBuilder::new() - .with_title("Custom Protocol") - .with_menu(menu) - .build(&event_loop) - .unwrap(); - - let _webview = WebViewBuilder::new(window) - .unwrap() - .with_custom_protocol("wry".into(), move |request| { - let path = request.uri().path(); - // Read the file content from file path - let content = if path == "/" { - PAGE1_HTML.into() - } else { - // `1..` for removing leading slash - read(canonicalize(PathBuf::from("examples").join(&path[1..]))?)?.into() - }; - - // Return asset contents and mime types based on file extentions - // If you don't want to do this manually, there are some crates for you. - // Such as `infer` and `mime_guess`. - let (data, meta) = if path.ends_with(".html") || path == "/" { - (content, "text/html") - } else if path.ends_with(".js") { - (content, "text/javascript") - } else if path.ends_with(".png") { - (content, "image/png") - } else if path.ends_with(".wasm") { - (content, "application/wasm") - } else { - unimplemented!(); - }; - - Response::builder() - .header(CONTENT_TYPE, meta) - .body(data) - .map_err(Into::into) - }) - // tell the webview to load the custom protocol - .with_url("wry://localhost")? - .build()?; - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::NewEvents(StartCause::Init) => println!("Wry application started!"), - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - Event::MenuEvent { menu_id, .. } => { - println!("Menu clicked! {:?}", menu_id); - // *control_flow = ControlFlow::Exit; - } - _ => (), - } - }); -} diff --git a/examples/multi_window.rs b/examples/multi_window.rs index d00b9cb21..0c5db5584 100644 --- a/examples/multi_window.rs +++ b/examples/multi_window.rs @@ -7,21 +7,21 @@ fn main() -> wry::Result<()> { use wry::{ application::{ event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget}, + event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget}, window::{Window, WindowBuilder, WindowId}, }, webview::{WebView, WebViewBuilder}, }; - enum UserEvents { + enum UserEvent { CloseWindow(WindowId), NewWindow, } fn create_new_window( title: String, - event_loop: &EventLoopWindowTarget, - proxy: EventLoopProxy, + event_loop: &EventLoopWindowTarget, + proxy: EventLoopProxy, ) -> (WindowId, WebView) { let window = WindowBuilder::new() .with_title(title) @@ -30,10 +30,10 @@ fn main() -> wry::Result<()> { let window_id = window.id(); let handler = move |window: &Window, req: String| match req.as_str() { "new-window" => { - let _ = proxy.send_event(UserEvents::NewWindow); + let _ = proxy.send_event(UserEvent::NewWindow); } "close" => { - let _ = proxy.send_event(UserEvents::CloseWindow(window.id())); + let _ = proxy.send_event(UserEvent::CloseWindow(window.id())); } _ if req.starts_with("change-title") => { let title = req.replace("change-title:", ""); @@ -58,7 +58,7 @@ fn main() -> wry::Result<()> { (window_id, webview) } - let event_loop = EventLoop::::with_user_event(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let mut webviews = HashMap::new(); let proxy = event_loop.create_proxy(); @@ -82,7 +82,7 @@ fn main() -> wry::Result<()> { *control_flow = ControlFlow::Exit } } - Event::UserEvent(UserEvents::NewWindow) => { + Event::UserEvent(UserEvent::NewWindow) => { let new_window = create_new_window( format!("Window {}", webviews.len() + 1), event_loop, @@ -90,7 +90,7 @@ fn main() -> wry::Result<()> { ); webviews.insert(new_window.0, new_window.1); } - Event::UserEvent(UserEvents::CloseWindow(id)) => { + Event::UserEvent(UserEvent::CloseWindow(id)) => { webviews.remove(&id); if webviews.is_empty() { *control_flow = ControlFlow::Exit diff --git a/examples/navigation_event.rs b/examples/navigation_event.rs index c6dc5dd4d..ddaab184b 100644 --- a/examples/navigation_event.rs +++ b/examples/navigation_event.rs @@ -6,7 +6,7 @@ fn main() -> wry::Result<()> { use wry::{ application::{ event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + event_loop::{ControlFlow, EventLoopBuilder}, window::WindowBuilder, }, webview::WebViewBuilder, @@ -16,7 +16,7 @@ fn main() -> wry::Result<()> { Navigation(String), } - let event_loop: EventLoop = EventLoop::with_user_event(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let proxy = event_loop.create_proxy(); let window = WindowBuilder::new() .with_title("Hello World") diff --git a/examples/new_window_req_event.rs b/examples/new_window_req_event.rs index 0569fe07a..272d68d70 100644 --- a/examples/new_window_req_event.rs +++ b/examples/new_window_req_event.rs @@ -6,7 +6,7 @@ fn main() -> wry::Result<()> { use wry::{ application::{ event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + event_loop::{ControlFlow, EventLoopBuilder}, window::WindowBuilder, }, webview::WebViewBuilder, @@ -26,7 +26,7 @@ fn main() -> wry::Result<()> { "#; - let event_loop: EventLoop = EventLoop::with_user_event(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let proxy = event_loop.create_proxy(); let window = WindowBuilder::new() .with_title("Hello World") diff --git a/src/lib.rs b/src/lib.rs index 375682585..16526fd24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,13 +46,12 @@ //! ## Feature flags //! //! Wry uses a set of feature flags to toggle several advanced features. `file-drop`, `protocol`, -//! and `tray` are enabled by default. +//! are enabled by default. //! //! - `file-drop`: Enables [`with_file_drop_handler`] to control the behaviour when there are files //! interacting with the window. Enabled by default. //! - `protocol`: Enables [`with_custom_protocol`] to define custom URL scheme for handling tasks like //! loading assets. Enabled by default. -//! - `tray`: Enables system tray and more menu item variants on **Linux**. //! This feature requires either `libayatana-appindicator` or `libappindicator` package installed. //! You can still create those types if you disable it. They just don't create the actual objects. //! - `devtools`: Enables devtools on release builds. Devtools are always enabled in debug builds. diff --git a/src/webview/android/binding.rs b/src/webview/android/binding.rs index d5472a28f..a625e035d 100644 --- a/src/webview/android/binding.rs +++ b/src/webview/android/binding.rs @@ -9,37 +9,37 @@ use http::{ pub use tao::platform::android::ndk_glue::jni::sys::{jboolean, jstring}; use tao::platform::android::ndk_glue::jni::{ errors::Error as JniError, - objects::{JClass, JMap, JObject, JString, JValue}, + objects::{JClass, JMap, JObject, JString}, sys::jobject, JNIEnv, }; use super::{ - create_headers_map, ASSET_LOADER_DOMAIN, IPC, ON_LOAD_HANDLER, REQUEST_HANDLER, - TITLE_CHANGE_HANDLER, URL_LOADING_OVERRIDE, WITH_ASSET_LOADER, + ASSET_LOADER_DOMAIN, IPC, ON_LOAD_HANDLER, REQUEST_HANDLER, TITLE_CHANGE_HANDLER, + URL_LOADING_OVERRIDE, WITH_ASSET_LOADER, }; use crate::webview::PageLoadEvent; -fn handle_request(env: JNIEnv, request: JObject) -> Result { +fn handle_request(env: &mut JNIEnv, request: JObject) -> Result { let mut request_builder = Request::builder(); let uri = env - .call_method(request, "getUrl", "()Landroid/net/Uri;", &[])? + .call_method(&request, "getUrl", "()Landroid/net/Uri;", &[])? .l()?; let url: JString = env - .call_method(uri, "toString", "()Ljava/lang/String;", &[])? + .call_method(&uri, "toString", "()Ljava/lang/String;", &[])? .l()? .into(); - request_builder = request_builder.uri(&env.get_string(url)?.to_string_lossy().to_string()); + request_builder = request_builder.uri(&env.get_string(&url)?.to_string_lossy().to_string()); let method: JString = env - .call_method(request, "getMethod", "()Ljava/lang/String;", &[])? + .call_method(&request, "getMethod", "()Ljava/lang/String;", &[])? .l()? .into(); request_builder = request_builder.method( env - .get_string(method)? + .get_string(&method)? .to_string_lossy() .to_string() .as_str(), @@ -48,10 +48,12 @@ fn handle_request(env: JNIEnv, request: JObject) -> Result { let request_headers = env .call_method(request, "getRequestHeaders", "()Ljava/util/Map;", &[])? .l()?; - let request_headers = JMap::from_env(&env, request_headers)?; - for (header, value) in request_headers.iter()? { - let header = env.get_string(header.into())?; - let value = env.get_string(value.into())?; + let request_headers = JMap::from_env(env, &request_headers)?; + while let Some((header, value)) = request_headers.iter(env)?.next(env)? { + let header = JString::from(header); + let value = JString::from(value); + let header = env.get_string(&header)?; + let value = env.get_string(&value)?; if let (Ok(header), Ok(value)) = ( HeaderName::from_bytes(header.to_bytes()), HeaderValue::from_bytes(value.to_bytes()), @@ -71,7 +73,7 @@ fn handle_request(env: JNIEnv, request: JObject) -> Result { let response = (handler.0)(final_request); if let Some(response) = response { let status = response.status(); - let status_code = status.as_u16(); + let status_code = status.as_u16() as i32; let status_err = if status_code < 100 { Some("Status code can't be less than 100") } else if status_code > 599 { @@ -100,34 +102,42 @@ fn handle_request(env: JNIEnv, request: JObject) -> Result { } } ( - env.new_string(mime_type)?.into(), + env.new_string(mime_type)?, if let Some(encoding) = encoding { - env.new_string(&encoding)?.into() + env.new_string(encoding)? } else { - JObject::null().into() + JString::default() }, ) } else { - (JObject::null().into(), JObject::null().into()) + (JString::default(), JString::default()) }; - let response_headers = create_headers_map(&env, response.headers())?; + let headers = response.headers(); + let obj = env.new_object("java/util/HashMap", "()V", &[])?; + let response_headers = { + let headers_map = JMap::from_env(env, &obj)?; + for (name, value) in headers.iter() { + let key = env.new_string(name)?; + let value = env.new_string(value.to_str().unwrap_or_default())?; + headers_map.put(env, &key, &value)?; + } + headers_map + }; let bytes = response.body(); let byte_array_input_stream = env.find_class("java/io/ByteArrayInputStream")?; - let byte_array = env.byte_array_from_slice(&bytes)?; - let stream = env.new_object( - byte_array_input_stream, - "([B)V", - &[JValue::Object(unsafe { JObject::from_raw(byte_array) })], - )?; + let byte_array = env.byte_array_from_slice(bytes)?; + let stream = env.new_object(byte_array_input_stream, "([B)V", &[(&byte_array).into()])?; + + let reason_phrase = env.new_string(reason_phrase)?; let web_resource_response_class = env.find_class("android/webkit/WebResourceResponse")?; let web_resource_response = env.new_object( web_resource_response_class, "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/util/Map;Ljava/io/InputStream;)V", - &[mime_type, encoding, (status_code as i32).into(), env.new_string(reason_phrase)?.into(), response_headers.into(), stream.into()], + &[(&mime_type).into(), (&encoding).into(), status_code.into(), (&reason_phrase).into(), (&response_headers).into(), (&stream).into()], )?; return Ok(*web_resource_response); @@ -137,19 +147,19 @@ fn handle_request(env: JNIEnv, request: JObject) -> Result { } #[allow(non_snake_case)] -pub unsafe fn handleRequest(env: JNIEnv, _: JClass, request: JObject) -> jobject { - match handle_request(env, request) { +pub unsafe fn handleRequest(mut env: JNIEnv, _: JClass, request: JObject) -> jobject { + match handle_request(&mut env, request) { Ok(response) => response, Err(e) => { log::warn!("Failed to handle request: {}", e); - *JObject::null() + JObject::null().as_raw() } } } #[allow(non_snake_case)] -pub unsafe fn shouldOverride(env: JNIEnv, _: JClass, url: JString) -> jboolean { - match env.get_string(url) { +pub unsafe fn shouldOverride(mut env: JNIEnv, _: JClass, url: JString) -> jboolean { + match env.get_string(&url) { Ok(url) => { let url = url.to_string_lossy().to_string(); URL_LOADING_OVERRIDE @@ -169,8 +179,8 @@ pub unsafe fn shouldOverride(env: JNIEnv, _: JClass, url: JString) -> jboolean { .into() } -pub unsafe fn ipc(env: JNIEnv, _: JClass, arg: JString) { - match env.get_string(arg) { +pub unsafe fn ipc(mut env: JNIEnv, _: JClass, arg: JString) { + match env.get_string(&arg) { Ok(arg) => { let arg = arg.to_string_lossy().to_string(); if let Some(w) = IPC.get() { @@ -182,8 +192,8 @@ pub unsafe fn ipc(env: JNIEnv, _: JClass, arg: JString) { } #[allow(non_snake_case)] -pub unsafe fn handleReceivedTitle(env: JNIEnv, _: JClass, _webview: JObject, title: JString) { - match env.get_string(title) { +pub unsafe fn handleReceivedTitle(mut env: JNIEnv, _: JClass, _webview: JObject, title: JString) { + match env.get_string(&title) { Ok(title) => { let title = title.to_string_lossy().to_string(); if let Some(w) = TITLE_CHANGE_HANDLER.get() { @@ -202,15 +212,15 @@ pub unsafe fn withAssetLoader(_: JNIEnv, _: JClass) -> jboolean { #[allow(non_snake_case)] pub unsafe fn assetLoaderDomain(env: JNIEnv, _: JClass) -> jstring { if let Some(domain) = ASSET_LOADER_DOMAIN.get() { - env.new_string(domain).unwrap().into_raw() + env.new_string(domain).unwrap().as_raw() } else { - env.new_string("wry.assets").unwrap().into_raw() + env.new_string("wry.assets").unwrap().as_raw() } } #[allow(non_snake_case)] -pub unsafe fn onPageLoading(env: JNIEnv, _: JClass, url: JString) { - match env.get_string(url) { +pub unsafe fn onPageLoading(mut env: JNIEnv, _: JClass, url: JString) { + match env.get_string(&url) { Ok(url) => { let url = url.to_string_lossy().to_string(); if let Some(h) = ON_LOAD_HANDLER.get() { @@ -222,8 +232,8 @@ pub unsafe fn onPageLoading(env: JNIEnv, _: JClass, url: JString) { } #[allow(non_snake_case)] -pub unsafe fn onPageLoaded(env: JNIEnv, _: JClass, url: JString) { - match env.get_string(url) { +pub unsafe fn onPageLoaded(mut env: JNIEnv, _: JClass, url: JString) { + match env.get_string(&url) { Ok(url) => { let url = url.to_string_lossy().to_string(); if let Some(h) = ON_LOAD_HANDLER.get() { diff --git a/src/webview/android/main_pipe.rs b/src/webview/android/main_pipe.rs index 35f1e053c..c791f19c8 100644 --- a/src/webview/android/main_pipe.rs +++ b/src/webview/android/main_pipe.rs @@ -12,10 +12,10 @@ use tao::platform::android::ndk_glue::{ objects::{GlobalRef, JObject, JString}, JNIEnv, }, - PACKAGE, + JMap, PACKAGE, }; -use super::{create_headers_map, find_class}; +use super::find_class; static CHANNEL: Lazy<(Sender, Receiver)> = Lazy::new(|| bounded(8)); pub static MAIN_PIPE: Lazy<[RawFd; 2]> = Lazy::new(|| { @@ -31,7 +31,7 @@ pub struct MainPipe<'a> { pub webchrome_client: GlobalRef, } -impl MainPipe<'_> { +impl<'a> MainPipe<'a> { pub(crate) fn send(message: WebViewMessage) { let size = std::mem::size_of::(); if let Ok(()) = CHANNEL.0.send(message) { @@ -40,7 +40,6 @@ impl MainPipe<'_> { } pub fn recv(&mut self) -> Result<(), JniError> { - let env = self.env; let activity = self.activity.as_obj(); if let Ok(message) = CHANNEL.1.recv() { match message { @@ -60,159 +59,175 @@ impl MainPipe<'_> { } = attrs; // Create webview let rust_webview_class = find_class( - env, + &mut self.env, activity, format!("{}/RustWebView", PACKAGE.get().unwrap()), )?; - let webview = env.new_object( - rust_webview_class, + let webview = self.env.new_object( + &rust_webview_class, "(Landroid/content/Context;)V", &[activity.into()], )?; // set media autoplay - env.call_method(webview, "setAutoPlay", "(Z)V", &[autoplay.into()])?; + self + .env + .call_method(&webview, "setAutoPlay", "(Z)V", &[autoplay.into()])?; // set user-agent if let Some(user_agent) = user_agent { - let user_agent = env.new_string(user_agent)?; - env.call_method( - webview, + let user_agent = self.env.new_string(user_agent)?; + self.env.call_method( + &webview, "setUserAgent", "(Ljava/lang/String;)V", - &[user_agent.into()], + &[(&user_agent).into()], )?; } - env.call_method( + self.env.call_method( activity, "setWebView", format!("(L{}/RustWebView;)V", PACKAGE.get().unwrap()), - &[webview.into()], + &[(&webview).into()], )?; // Navigation if let Some(u) = url { - if let Ok(url) = env.new_string(u) { - load_url(env, webview, url, headers, true)?; + if let Ok(url) = self.env.new_string(u) { + load_url(&mut self.env, &webview, &url, headers, true)?; } } else if let Some(h) = html { - if let Ok(html) = env.new_string(h) { - load_html(env, webview, html)?; + if let Ok(html) = self.env.new_string(h) { + load_html(&mut self.env, &webview, &html)?; } } // Enable devtools #[cfg(any(debug_assertions, feature = "devtools"))] - env.call_static_method( - rust_webview_class, + self.env.call_static_method( + &rust_webview_class, "setWebContentsDebuggingEnabled", "(Z)V", &[devtools.into()], )?; if transparent { - set_background_color(env, webview, (0, 0, 0, 0))?; - } else { - if let Some(color) = background_color { - set_background_color(env, webview, color)?; - } + set_background_color(&mut self.env, &webview, (0, 0, 0, 0))?; + } else if let Some(color) = background_color { + set_background_color(&mut self.env, &webview, color)?; } // Create and set webview client let rust_webview_client_class = find_class( - env, + &mut self.env, activity, format!("{}/RustWebViewClient", PACKAGE.get().unwrap()), )?; - let webview_client = env.new_object( - rust_webview_client_class, + let webview_client = self.env.new_object( + &rust_webview_client_class, "(Landroid/content/Context;)V", &[activity.into()], )?; - env.call_method( - webview, + self.env.call_method( + &webview, "setWebViewClient", "(Landroid/webkit/WebViewClient;)V", - &[webview_client.into()], + &[(&webview_client).into()], )?; // set webchrome client - env.call_method( - webview, + self.env.call_method( + &webview, "setWebChromeClient", "(Landroid/webkit/WebChromeClient;)V", &[self.webchrome_client.as_obj().into()], )?; // Add javascript interface (IPC) - let ipc_class = find_class(env, activity, format!("{}/Ipc", PACKAGE.get().unwrap()))?; - let ipc = env.new_object(ipc_class, "()V", &[])?; - let ipc_str = env.new_string("ipc")?; - env.call_method( - webview, + let ipc_class = find_class( + &mut self.env, + activity, + format!("{}/Ipc", PACKAGE.get().unwrap()), + )?; + let ipc = self.env.new_object(ipc_class, "()V", &[])?; + let ipc_str = self.env.new_string("ipc")?; + self.env.call_method( + &webview, "addJavascriptInterface", "(Ljava/lang/Object;Ljava/lang/String;)V", - &[ipc.into(), ipc_str.into()], + &[(&ipc).into(), (&ipc_str).into()], )?; // Set content view - env.call_method( + self.env.call_method( activity, "setContentView", "(Landroid/view/View;)V", - &[webview.into()], + &[(&webview).into()], )?; if let Some(on_webview_created) = on_webview_created { if let Err(e) = on_webview_created(super::Context { - env, + env: &mut self.env, activity, - webview, + webview: &webview, }) { log::warn!("failed to run webview created hook: {e}"); } } - let webview = env.new_global_ref(webview)?; + let webview = self.env.new_global_ref(webview)?; self.webview = Some(webview); } WebViewMessage::Eval(script) => { if let Some(webview) = &self.webview { - let s = env.new_string(script)?; - env.call_method( + let s = self.env.new_string(script)?; + self.env.call_method( webview.as_obj(), "evaluateJavascript", "(Ljava/lang/String;Landroid/webkit/ValueCallback;)V", - &[s.into(), JObject::null().into()], + &[(&s).into(), JObject::null().as_ref().into()], )?; } } WebViewMessage::SetBackgroundColor(background_color) => { if let Some(webview) = &self.webview { - set_background_color(env, webview.as_obj(), background_color)?; + set_background_color(&mut self.env, webview.as_obj(), background_color)?; } } WebViewMessage::GetWebViewVersion(tx) => { - match env + match self + .env .call_method(activity, "getVersion", "()Ljava/lang/String;", &[]) .and_then(|v| v.l()) - .and_then(|s| env.get_string(s.into())) - { + .and_then(|s| { + let s = JString::from(s); + self + .env + .get_string(&s) + .map(|v| v.to_string_lossy().to_string()) + }) { Ok(version) => { - tx.send(Ok(version.to_string_lossy().into())).unwrap(); + tx.send(Ok(version)).unwrap(); } Err(e) => tx.send(Err(e.into())).unwrap(), } } WebViewMessage::GetUrl(tx) => { if let Some(webview) = &self.webview { - let url = env + let url = self + .env .call_method(webview.as_obj(), "getUrl", "()Ljava/lang/String;", &[]) .and_then(|v| v.l()) - .and_then(|s| env.get_string(s.into())) - .map(|u| u.to_string_lossy().into()) + .and_then(|s| { + let s = JString::from(s); + self + .env + .get_string(&s) + .map(|v| v.to_string_lossy().to_string()) + }) .unwrap_or_default(); tx.send(url).unwrap() @@ -220,20 +235,22 @@ impl MainPipe<'_> { } WebViewMessage::Jni(f) => { if let Some(w) = &self.webview { - f(env, activity, w.as_obj()); + f(&mut self.env, activity, w.as_obj()); } else { - f(env, activity, JObject::null()); + f(&mut self.env, activity, &JObject::null()); } } WebViewMessage::LoadUrl(url, headers) => { if let Some(webview) = &self.webview { - let url = env.new_string(url)?; - load_url(env, webview.as_obj(), url, headers, false)?; + let url = self.env.new_string(url)?; + load_url(&mut self.env, webview.as_obj(), &url, headers, false)?; } } WebViewMessage::ClearAllBrowsingData => { if let Some(webview) = &self.webview { - env.call_method(webview, "clearAllBrowsingData", "()V", &[])?; + self + .env + .call_method(webview, "clearAllBrowsingData", "()V", &[])?; } } } @@ -243,9 +260,9 @@ impl MainPipe<'_> { } fn load_url<'a>( - env: JNIEnv<'a>, - webview: JObject<'a>, - url: JString<'a>, + env: &mut JNIEnv<'a>, + webview: &JObject<'a>, + url: &JString<'a>, headers: Option, main_thread: bool, ) -> Result<(), JniError> { @@ -255,12 +272,21 @@ fn load_url<'a>( "loadUrl" }; if let Some(headers) = headers { - let headers_map = create_headers_map(&env, &headers)?; + let obj = env.new_object("java/util/HashMap", "()V", &[])?; + let headers_map = { + let headers_map = JMap::from_env(env, &obj)?; + for (name, value) in headers.iter() { + let key = env.new_string(name)?; + let value = env.new_string(value.to_str().unwrap_or_default())?; + headers_map.put(env, &key, &value)?; + } + headers_map + }; env.call_method( webview, function, "(Ljava/lang/String;Ljava/util/Map;)V", - &[url.into(), headers_map.into()], + &[url.into(), (&headers_map).into()], )?; } else { env.call_method(webview, function, "(Ljava/lang/String;)V", &[url.into()])?; @@ -268,7 +294,11 @@ fn load_url<'a>( Ok(()) } -fn load_html<'a>(env: JNIEnv<'a>, webview: JObject<'a>, html: JString<'a>) -> Result<(), JniError> { +fn load_html<'a>( + env: &mut JNIEnv<'a>, + webview: &JObject<'a>, + html: &JString<'a>, +) -> Result<(), JniError> { env.call_method( webview, "loadHTMLMainThread", @@ -279,8 +309,8 @@ fn load_html<'a>(env: JNIEnv<'a>, webview: JObject<'a>, html: JString<'a>) -> Re } fn set_background_color<'a>( - env: JNIEnv<'a>, - webview: JObject<'a>, + env: &mut JNIEnv<'a>, + webview: &JObject<'a>, background_color: RGBA, ) -> Result<(), JniError> { let color_class = env.find_class("android/graphics/Color")?; @@ -295,7 +325,7 @@ fn set_background_color<'a>( background_color.2.into(), ], )?; - env.call_method(webview, "setBackgroundColor", "(I)V", &[color])?; + env.call_method(webview, "setBackgroundColor", "(I)V", &[color.borrow()])?; Ok(()) } @@ -305,7 +335,7 @@ pub(crate) enum WebViewMessage { SetBackgroundColor(RGBA), GetWebViewVersion(Sender>), GetUrl(Sender), - Jni(Box), + Jni(Box), LoadUrl(String, Option), ClearAllBrowsingData, } diff --git a/src/webview/android/mod.rs b/src/webview/android/mod.rs index 04c74f44a..b2f396c06 100644 --- a/src/webview/android/mod.rs +++ b/src/webview/android/mod.rs @@ -22,7 +22,7 @@ use tao::platform::android::ndk_glue::{ JNIEnv, }, ndk::looper::{FdEvent, ForeignLooper}, - JMap, PACKAGE, + PACKAGE, }; use url::Url; @@ -30,10 +30,10 @@ pub(crate) mod binding; mod main_pipe; use main_pipe::{CreateWebViewAttributes, MainPipe, WebViewMessage, MAIN_PIPE}; -pub struct Context<'a> { - pub env: JNIEnv<'a>, - pub activity: JObject<'a>, - pub webview: JObject<'a>, +pub struct Context<'a, 'b> { + pub env: &'a mut JNIEnv<'b>, + pub activity: &'a JObject<'b>, + pub webview: &'a JObject<'b>, } #[macro_export] @@ -170,28 +170,29 @@ impl UnsafeOnPageLoadHandler { unsafe impl Send for UnsafeOnPageLoadHandler {} unsafe impl Sync for UnsafeOnPageLoadHandler {} -pub unsafe fn setup(env: JNIEnv, looper: &ForeignLooper, activity: GlobalRef) { +pub unsafe fn setup(mut env: JNIEnv, looper: &ForeignLooper, activity: GlobalRef) { // we must create the WebChromeClient here because it calls `registerForActivityResult`, // which gives an `LifecycleOwners must call register before they are STARTED.` error when called outside the onCreate hook let rust_webchrome_client_class = find_class( - env, + &mut env, activity.as_obj(), format!("{}/RustWebChromeClient", PACKAGE.get().unwrap()), ) .unwrap(); let webchrome_client = env .new_object( - rust_webchrome_client_class, + &rust_webchrome_client_class, &format!("(L{}/WryActivity;)V", PACKAGE.get().unwrap()), &[activity.as_obj().into()], ) .unwrap(); + let webchrome_client = env.new_global_ref(webchrome_client).unwrap(); let mut main_pipe = MainPipe { env, activity, webview: None, - webchrome_client: env.new_global_ref(webchrome_client).unwrap(), + webchrome_client, }; looper @@ -199,10 +200,7 @@ pub unsafe fn setup(env: JNIEnv, looper: &ForeignLooper, activity: GlobalRef) { let size = std::mem::size_of::(); let mut wake = false; if libc::read(MAIN_PIPE[0], &mut wake as *mut _ as *mut _, size) == size as libc::ssize_t { - match main_pipe.recv() { - Ok(_) => true, - Err(_) => false, - } + main_pipe.recv().is_ok() } else { false } @@ -306,37 +304,35 @@ impl InnerWebView { .map(|content_type_str| content_type_str.to_lowercase().starts_with("text/html")) .unwrap_or_default(); - if should_inject_scripts { - if !initialization_scripts.is_empty() { - let mut document = - kuchiki::parse_html().one(String::from_utf8_lossy(response.body()).into_owned()); - let csp = response.headers_mut().get_mut(CONTENT_SECURITY_POLICY); - let mut hashes = Vec::new(); - with_html_head(&mut document, |head| { - // iterate in reverse order since we are prepending each script to the head tag - for script in initialization_scripts.iter().rev() { - let script_el = - NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None); - script_el.append(NodeRef::new_text(script)); - head.prepend(script_el); - if csp.is_some() { - hashes.push(hash_script(script)); - } + if should_inject_scripts && !initialization_scripts.is_empty() { + let mut document = + kuchiki::parse_html().one(String::from_utf8_lossy(response.body()).into_owned()); + let csp = response.headers_mut().get_mut(CONTENT_SECURITY_POLICY); + let mut hashes = Vec::new(); + with_html_head(&mut document, |head| { + // iterate in reverse order since we are prepending each script to the head tag + for script in initialization_scripts.iter().rev() { + let script_el = + NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None); + script_el.append(NodeRef::new_text(script)); + head.prepend(script_el); + if csp.is_some() { + hashes.push(hash_script(script)); } - }); - - if let Some(csp) = csp { - let csp_string = csp.to_str().unwrap().to_string(); - let csp_string = if csp_string.contains("script-src") { - csp_string.replace("script-src", &format!("script-src {}", hashes.join(" "))) - } else { - format!("{} script-src {}", csp_string, hashes.join(" ")) - }; - *csp = HeaderValue::from_str(&csp_string).unwrap(); } - - *response.body_mut() = document.to_string().into_bytes().into(); + }); + + if let Some(csp) = csp { + let csp_string = csp.to_str().unwrap().to_string(); + let csp_string = if csp_string.contains("script-src") { + csp_string.replace("script-src", &format!("script-src {}", hashes.join(" "))) + } else { + format!("{} script-src {}", csp_string, hashes.join(" ")) + }; + *csp = HeaderValue::from_str(&csp_string).unwrap(); } + + *response.body_mut() = document.to_string().into_bytes().into(); } return Some(response); } @@ -421,7 +417,7 @@ impl JniHandle { /// Provided function will be provided with the jni evironment, Android activity and WebView pub fn exec(&self, func: F) where - F: FnOnce(JNIEnv, JObject, JObject) + Send + 'static, + F: FnOnce(&mut JNIEnv, &JObject, &JObject) + Send + 'static, { MainPipe::send(WebViewMessage::Jni(Box::new(func))); } @@ -455,8 +451,8 @@ fn hash_script(script: &str) -> String { /// Finds a class in the project scope. pub fn find_class<'a>( - env: JNIEnv<'a>, - activity: JObject<'a>, + env: &mut JNIEnv<'a>, + activity: &JObject<'_>, name: String, ) -> std::result::Result, JniError> { let class_name = env.new_string(name.replace('/', "."))?; @@ -465,7 +461,7 @@ pub fn find_class<'a>( activity, "getAppClass", "(Ljava/lang/String;)Ljava/lang/Class;", - &[class_name.into()], + &[(&class_name).into()], )? .l()?; Ok(my_class.into()) @@ -476,21 +472,7 @@ pub fn find_class<'a>( /// The closure takes the JNI env, the Android activity instance and the possibly null webview. pub fn dispatch(func: F) where - F: FnOnce(JNIEnv, JObject, JObject) + Send + 'static, + F: FnOnce(&mut JNIEnv, &JObject, &JObject) + Send + 'static, { MainPipe::send(WebViewMessage::Jni(Box::new(func))); } - -fn create_headers_map<'a, 'b>( - env: &'a JNIEnv, - headers: &http::HeaderMap, -) -> std::result::Result, JniError> { - let obj = env.new_object("java/util/HashMap", "()V", &[])?; - let headers_map = JMap::from_env(&env, obj)?; - for (name, value) in headers.iter() { - let key = env.new_string(name)?; - let value = env.new_string(value.to_str().unwrap_or_default())?; - headers_map.put(key.into(), value.into())?; - } - Ok(headers_map) -} diff --git a/src/webview/mod.rs b/src/webview/mod.rs index ededc87cf..72d980a70 100644 --- a/src/webview/mod.rs +++ b/src/webview/mod.rs @@ -719,7 +719,7 @@ impl WebViewBuilderExtWindows for WebViewBuilder<'_> { pub trait WebViewBuilderExtAndroid { fn on_webview_created< F: Fn( - prelude::Context<'_>, + prelude::Context<'_, '_>, ) -> std::result::Result<(), tao::platform::android::ndk_glue::jni::errors::Error> + Send + 'static, @@ -742,7 +742,7 @@ pub trait WebViewBuilderExtAndroid { impl WebViewBuilderExtAndroid for WebViewBuilder<'_> { fn on_webview_created< F: Fn( - prelude::Context<'_>, + prelude::Context<'_, '_>, ) -> std::result::Result<(), tao::platform::android::ndk_glue::jni::errors::Error> + Send + 'static, diff --git a/src/webview/webkitgtk/mod.rs b/src/webview/webkitgtk/mod.rs index a4b16fd83..93fa6fc7c 100644 --- a/src/webview/webkitgtk/mod.rs +++ b/src/webview/webkitgtk/mod.rs @@ -203,12 +203,11 @@ impl InnerWebView { ) } - // Gtk application window can only contain one widget at a time. - // In tao, we add a GtkBox to pack menu bar. So we check if - // there's a box widget here. - if let Some(widget) = window.children().pop() { - let vbox = widget.downcast::().unwrap(); + // tao adds a default vertical box so we check for that first + if let Some(vbox) = window_rc.default_vbox() { vbox.pack_start(&*webview, true, true, 0); + } else { + window.add(&*webview); } webview.grab_focus();