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

Feature: Implement Callback Functions for JavaScript Evaluation Result #474

Open
Toromyx opened this issue Jan 31, 2022 · 3 comments
Open

Comments

@Toromyx
Copy link

Toromyx commented Jan 31, 2022

Problem
I use Tauri to open windows of external sites as well as the "main" application window of local HTML/JS/CSS. I still want to interact with the DOM of those external sites and know the result of that interaction. This doesn't have to be done via JavaScript.

Solution
All three webview APIs on the main operating systems support some kind of callback when executing JavaScript. For each os I added a link to the documentation and the place in the code where that callback possibility is just ignored.
Is there a (security) reason this functionality is not provided?

Linux

Windows

Apple

Alternatives
An alternative I considered was to build a JavaScript wrapper which sends the result of the evaluated JavaScript over RPC. But this would be a security problem because now external sites have access to RPC.

Would you assign yourself to implement this feature?
I'm only just starting with Rust and would assign myself to do a proof of concept for WebView2 under Windows. I currently don't have the capability to develop under Linux and never will have the capability to develop under Apple.

Toromyx added a commit to Toromyx/wry that referenced this issue Jan 31, 2022
Toromyx added a commit to Toromyx/wry that referenced this issue Jan 31, 2022
@wusyong
Copy link
Member

wusyong commented Feb 2, 2022

I think this one is just because of history reason. When webview repo provide the API, it just want one that can run js script.
We do can support this but it won't be a trivial task IMHO. It's better wait for v1 launch and see what feature requests are wanted by most.

Toromyx added a commit to Toromyx/tauri that referenced this issue Feb 3, 2022
@Toromyx
Copy link
Author

Toromyx commented Feb 3, 2022

I hacked together something which "works on my machine" (meaning Windows):

I'm open for feedback, but I also understand if you say that now is not the right time to implement this feature.

@amrbashir amrbashir added platform: All status: needs triage This issue or pull request needs to be investigated type: feature request priority: medium Great to have good first issue Good for newcomers and removed status: needs triage This issue or pull request needs to be investigated labels Feb 13, 2022
Toromyx added a commit to Toromyx/wry that referenced this issue Apr 5, 2022
Toromyx added a commit to Toromyx/wry that referenced this issue Sep 15, 2022
@yyon
Copy link

yyon commented Nov 22, 2022

Hello,
I happened to implement this on all 3 OS's (using Tauri's with_webview function). If you ever happen to want to implement this feature, feel free to use this code as a reference.

let webview_result = browser_window.with_webview(|webview| {
    #[cfg(target_os = "linux")]
    {
        use webkit2gtk::traits::WebViewExt;
        use gio;
        let cancellable: Option<&gio::Cancellable> = None;
        webview.inner().run_javascript("JSON.stringify(\"Hello, world!\");", cancellable, |result| {
            if result.is_err() {
                println!("Error evaluating javascript");
                return;
            }

            let javascript_result = result.unwrap();

            let value_opt = javascript_result.js_value();

            if value_opt.is_none() {
                println!("Javascript returned nothing");
                return;
            }

            let value_val = value_opt.unwrap();

            let value_str = value_val.to_string();

            do_callback(value_str);
        });
    }

    #[cfg(windows)]
    unsafe {
        use webview2_com::{Microsoft::Web::WebView2::Win32::*, *};
        use windows::{
            core::{PCWSTR, PWSTR},
        };
        use std::os::windows::ffi::OsStrExt;

        let core_result = webview.controller().CoreWebView2();

        if core_result.is_err() {
            println!("Error getting core webview");
            return;
        }

        let core = core_result.unwrap();

        fn encode_wide(string: impl AsRef<std::ffi::OsStr>) -> Vec<u16> {
            string.as_ref().encode_wide().chain(std::iter::once(0)).collect()
        }

        let _res = core.ExecuteScript(
            PCWSTR::from_raw(encode_wide("JSON.stringify(\"Hello, world!\");".to_string()).as_ptr()),
            &ExecuteScriptCompletedHandler::create(Box::new(|result, result_object_as_json: std::string::String| {
                // note: result_object_as_json is wrapped in JSON twice, as opposed to other OS's, because for some reason it can only return strings
                // so you should probably parse JSON here
                do_callback(result_object_as_json);
                Ok(())
            }))
        );
    }

    #[cfg(target_os = "macos")]
    unsafe {
        use objc;
        use objc::sel;
        use objc::sel_impl;
        use cocoa::base::id;
        const UTF8_ENCODING: usize = 4;
        struct NSString(id);
        impl NSString {
            fn new(s: &str) -> Self {
                NSString(unsafe {
                let ns_string: id = objc::msg_send![objc::class!(NSString), alloc];
                let ns_string: id = objc::msg_send![ns_string,
                                        initWithBytes:s.as_ptr()
                                        length:s.len()
                                        encoding:UTF8_ENCODING];
        
                let _: () = objc::msg_send![ns_string, autorelease];
        
                ns_string
                })
            }
        
            fn to_str(&self) -> &str {
                unsafe {
                let bytes: *const std::ffi::c_char = objc::msg_send![self.0, UTF8String];
                let len = objc::msg_send![self.0, lengthOfBytesUsingEncoding: UTF8_ENCODING];
                let bytes = std::slice::from_raw_parts(bytes as *const u8, len);
                std::str::from_utf8_unchecked(bytes)
                }
            }
        }
        struct NSError(id);
        use block2::{Block, ConcreteBlock};

        let controller = webview.inner();
        let javascript_string = NSString::new("JSON.stringify(\"Hello, world!\");");
        let handler = ConcreteBlock::new(|val: NSString, err| {
            do_callback(val.to_str().to_string());
        });
        let handler = handler.copy();
        let handler: &Block<(NSString, NSError), ()> = &handler;

        let _: id = objc::msg_send![controller, evaluateJavaScript:javascript_string completionHandler:handler];
    }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 📬Proposal
Development

No branches or pull requests

4 participants