Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support callback function in eval #778

Merged
merged 12 commits into from Mar 23, 2023
45 changes: 23 additions & 22 deletions Cargo.toml
@@ -1,47 +1,47 @@
workspace = { }
workspace = {}

wusyong marked this conversation as resolved.
Show resolved Hide resolved
[package]
name = "wry"
version = "0.27.0"
authors = [ "Tauri Programme within The Commons Conservancy" ]
authors = ["Tauri Programme within The Commons Conservancy"]
edition = "2021"
license = "Apache-2.0 OR MIT"
description = "Cross-platform WebView rendering library"
readme = "README.md"
repository = "https://github.com/tauri-apps/wry"
documentation = "https://docs.rs/wry"
categories = [ "gui" ]
categories = ["gui"]

[package.metadata.docs.rs]
default-features = false
features = [ "dox", "file-drop", "protocol", "tray" ]
features = ["dox", "file-drop", "protocol", "tray"]
targets = [
"x86_64-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
"x86_64-apple-darwin"
"x86_64-apple-darwin",
]

[features]
default = [ "file-drop", "objc-exception", "protocol" ]
objc-exception = [ "objc/exception" ]
file-drop = [ ]
protocol = [ ]
dox = [ "tao/dox", "webkit2gtk/dox", "soup3/dox" ]
tray = [ "tao/tray" ]
devtools = [ ]
transparent = [ ]
fullscreen = [ ]
linux-headers = [ "webkit2gtk/v2_36" ]
default = ["file-drop", "objc-exception", "protocol"]
objc-exception = ["objc/exception"]
file-drop = []
protocol = []
dox = ["tao/dox", "webkit2gtk/dox", "soup3/dox"]
tray = ["tao/tray"]
devtools = []
transparent = []
fullscreen = []
linux-headers = ["webkit2gtk/v2_36"]

[dependencies]
libc = "0.2"
log = "0.4"
once_cell = "1"
serde = { version = "1.0", features = [ "derive" ] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
url = "2.3"
tao = { version = "0.18.0", default-features = false, features = [ "serde" ] }
tao = { version = "0.18.0", default-features = false, features = ["serde"] }
http = "0.2.9"

[dev-dependencies]
Expand All @@ -51,7 +51,8 @@ dirs = "4.0.0"
base64 = "0.13.1"

[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
webkit2gtk = { version = "0.19.2", features = [ "v2_38" ] }
javascriptcore-rs-sys = { version = "0.4.0", features = ["v2_28"] }
webkit2gtk = { version = "0.19.2", features = ["v2_38"] }
webkit2gtk-sys = "0.19.1"
gio = "0.16"
glib = "0.16"
Expand All @@ -64,9 +65,9 @@ webview2-com = "0.22"
windows-implement = "0.44"
dunce = "1"

[target."cfg(target_os = \"windows\")".dependencies.windows]
version = "0.44.0"
features = [
[target."cfg(target_os = \"windows\")".dependencies.windows]
version = "0.44.0"
features = [
"implement",
"Win32_Foundation",
"Win32_Graphics_Gdi",
Expand All @@ -78,7 +79,7 @@ dunce = "1"
"Win32_System_SystemServices",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging",
"Win32_Globalization"
"Win32_Globalization",
]

[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies]
Expand Down
59 changes: 59 additions & 0 deletions examples/eval_js.rs
@@ -0,0 +1,59 @@
// Copyright 2020-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

fn main() -> wry::Result<()> {
use wry::{
application::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder},
},
webview::WebViewBuilder,
};

enum UserEvents {
ExecEval(),
}

let event_loop = EventLoop::<UserEvents>::with_user_event();
let proxy = event_loop.create_proxy();

let window = WindowBuilder::new()
.with_title("Hello World")
.build(&event_loop)?;

let ipc_handler = move |_: &Window, req: String| match req.as_str() {
"exec-eval" => {
let _ = proxy.send_event(UserEvents::ExecEval());
}
_ => {}
};

let _webview = WebViewBuilder::new(window)?
.with_html(
r#"
<button onclick="window.ipc.postMessage('exec-eval')">Exec eval</button>
"#,
)?
.with_ipc_handler(ipc_handler)
.build()?;

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

match event {
Event::UserEvent(UserEvents::ExecEval()) => {
_webview
.evaluate_script("if (!foo) { var foo = 'morbin'; } `${foo} time`")
.unwrap();
}
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}
2 changes: 1 addition & 1 deletion src/webview/android/mod.rs
Expand Up @@ -306,7 +306,7 @@ impl InnerWebView {
Url::parse(uri.as_str()).unwrap()
}

pub fn eval(&self, js: &str) -> Result<()> {
pub fn eval(&self, js: &str, _callback: Option<impl Fn(String) + Send + 'static>) -> Result<()> {
MainPipe::send(WebViewMessage::Eval(js.into()));
Ok(())
}
Expand Down
6 changes: 5 additions & 1 deletion src/webview/mod.rs
Expand Up @@ -799,8 +799,12 @@ impl WebView {
/// [`WebView`]. Use [`EventLoopProxy`] and a custom event to send scripts from other threads.
///
/// [`EventLoopProxy`]: crate::application::event_loop::EventLoopProxy
///
/// - **Windows / Android:** Callback function not implemented.
pub fn evaluate_script(&self, js: &str) -> Result<()> {
self.webview.eval(js)
self
.webview
.eval(js, None::<Box<dyn Fn(String) + Send + 'static>>)
}

/// Launch print modal for the webview content.
Expand Down
35 changes: 31 additions & 4 deletions src/webview/webkitgtk/mod.rs
Expand Up @@ -4,8 +4,9 @@

use gdk::{Cursor, EventMask, WindowEdge};
use gio::Cancellable;
use glib::signal::Inhibit;
use glib::{signal::Inhibit, translate::from_glib_full};
use gtk::prelude::*;
use javascriptcore_rs_sys::jsc_value_to_json;
#[cfg(any(debug_assertions, feature = "devtools"))]
use std::sync::atomic::{AtomicBool, Ordering};
use std::{
Expand Down Expand Up @@ -391,7 +392,10 @@ impl InnerWebView {
}

pub fn print(&self) {
let _ = self.eval("window.print()");
let _ = self.eval(
"window.print()",
None::<Box<dyn FnOnce(String) + Send + 'static>>,
);
}

pub fn url(&self) -> Url {
Expand All @@ -400,13 +404,36 @@ impl InnerWebView {
Url::parse(uri.as_str()).unwrap()
}

pub fn eval(&self, js: &str) -> Result<()> {
pub fn eval(
&self,
js: &str,
callback: Option<impl FnOnce(String) + Send + 'static>,
) -> Result<()> {
if let Some(pending_scripts) = &mut *self.pending_scripts.lock().unwrap() {
pending_scripts.push(js.into());
} else {
let cancellable: Option<&Cancellable> = None;
self.webview.run_javascript(js, cancellable, |_| ());

match callback {
Some(callback) => {
self.webview.run_javascript(js, cancellable, |result| {
let mut result_str = String::new();

if let Ok(js_result) = result {
if let Some(js_value) = js_result.js_value() {
unsafe {
result_str = from_glib_full(jsc_value_to_json(js_value.as_ptr(), 0));
wusyong marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

callback(result_str);
});
}
None => self.webview.run_javascript(js, cancellable, |_| ()),
};
}

Ok(())
}

Expand Down
33 changes: 27 additions & 6 deletions src/webview/webview2/mod.rs
Expand Up @@ -804,17 +804,30 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_
)
}

fn execute_script(webview: &ICoreWebView2, js: String) -> windows::core::Result<()> {
fn execute_script(
webview: &ICoreWebView2,
js: String,
callback: impl FnOnce(String) + Send + 'static,
) -> windows::core::Result<()> {
unsafe {
webview.ExecuteScript(
PCWSTR::from_raw(encode_wide(js).as_ptr()),
&ExecuteScriptCompletedHandler::create(Box::new(|_, _| (Ok(())))),
&ExecuteScriptCompletedHandler::create(Box::new(|_, return_str| {
let json_value: serde_json::Value = serde_json::from_str(&return_str).unwrap();
wusyong marked this conversation as resolved.
Show resolved Hide resolved

callback(json_value.to_string());

Ok(())
})),
)
}
}

pub fn print(&self) {
let _ = self.eval("window.print()");
let _ = self.eval(
"window.print()",
None::<Box<dyn FnOnce(String) + Send + 'static>>,
);
}

pub fn url(&self) -> Url {
Expand All @@ -827,9 +840,17 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_
Url::parse(&uri).unwrap()
}

pub fn eval(&self, js: &str) -> Result<()> {
Self::execute_script(&self.webview, js.to_string())
.map_err(|err| Error::WebView2Error(webview2_com::Error::WindowsError(err)))
pub fn eval(
&self,
js: &str,
callback: Option<impl FnOnce(String) + Send + 'static>,
) -> Result<()> {
match callback {
Some(callback) => Self::execute_script(&self.webview, js.to_string(), callback)
.map_err(|err| Error::WebView2Error(webview2_com::Error::WindowsError(err))),
None => Self::execute_script(&self.webview, js.to_string(), |_| ())
.map_err(|err| Error::WebView2Error(webview2_com::Error::WindowsError(err))),
}
}

#[cfg(any(debug_assertions, feature = "devtools"))]
Expand Down
42 changes: 40 additions & 2 deletions src/webview/wkwebview/mod.rs
Expand Up @@ -67,6 +67,8 @@ use http::{
const IPC_MESSAGE_HANDLER_NAME: &str = "ipc";
const ACCEPT_FIRST_MOUSE: &str = "accept_first_mouse";

const NS_JSON_WRITING_FRAGMENTS_ALLOWED: u64 = 4;

pub(crate) struct InnerWebView {
pub webview: id,
#[cfg(target_os = "macos")]
Expand Down Expand Up @@ -848,15 +850,37 @@ r#"Object.defineProperty(window, 'ipc', {
Url::parse(std::str::from_utf8(bytes).unwrap()).unwrap()
}

pub fn eval(&self, js: &str) -> Result<()> {
pub fn eval(&self, js: &str, callback: Option<impl Fn(String) + Send + 'static>) -> Result<()> {
if let Some(scripts) = &mut *self.pending_scripts.lock().unwrap() {
scripts.push(js.into());
} else {
// Safety: objc runtime calls are unsafe
unsafe {
let _: id = msg_send![self.webview, evaluateJavaScript:NSString::new(js) completionHandler:null::<*const c_void>()];
let _: id = match callback {
Some(callback) => {
let handler = block::ConcreteBlock::new(|val: id, _err: id| {
let mut result = String::new();

if val != nil {
let serializer = class!(NSJSONSerialization);
let json_ns_data: NSData = msg_send![serializer, dataWithJSONObject:val options:NS_JSON_WRITING_FRAGMENTS_ALLOWED error:nil];
let json_string = NSString::from(json_ns_data);

result = json_string.to_str().to_string();
}

callback(result)
});

msg_send![self.webview, evaluateJavaScript:NSString::new(js) completionHandler:handler]
}
None => {
msg_send![self.webview, evaluateJavaScript:NSString::new(js) completionHandler:null::<*const c_void>()]
}
};
}
}

Ok(())
}

Expand Down Expand Up @@ -1078,3 +1102,17 @@ impl NSString {
self.0
}
}

impl From<NSData> for NSString {
fn from(value: NSData) -> Self {
Self(unsafe {
let ns_string: id = msg_send![class!(NSString), alloc];
let ns_string: id = msg_send![ns_string, initWithData:value encoding:UTF8_ENCODING];
let _: () = msg_send![ns_string, autorelease];

ns_string
})
}
}

struct NSData(id);