From 15957a97ae4cec5d8b448e2454ce9d3b851fd63d Mon Sep 17 00:00:00 2001 From: norbiros Date: Sat, 21 Sep 2024 17:51:40 +0200 Subject: [PATCH] feat: Allow injecting JavaScript code into subframes Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> --- .changes/allow-injecting-into-subframes.md | 5 +++++ src/lib.rs | 15 +++++++++++++++ src/webkitgtk/mod.rs | 9 ++++----- src/webview2/mod.rs | 6 ++++++ src/wkwebview/mod.rs | 18 +++++++++++------- 5 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 .changes/allow-injecting-into-subframes.md diff --git a/.changes/allow-injecting-into-subframes.md b/.changes/allow-injecting-into-subframes.md new file mode 100644 index 000000000..685e7777d --- /dev/null +++ b/.changes/allow-injecting-into-subframes.md @@ -0,0 +1,5 @@ +--- +"wry": minor +--- + +Add option to injecting JavaScript code into subframes. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3f052be27..c722fe6aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -253,6 +253,7 @@ pub use error::*; pub use http; pub use proxy::{ProxyConfig, ProxyEndpoint}; pub use web_context::WebContext; +use webkit2gtk::UserContentInjectedFrames; /// A rectangular region. #[derive(Clone, Copy, Debug)] @@ -503,6 +504,9 @@ pub struct WebViewAttributes { /// This is only effective if the webview was created by [`WebView::new_as_child`] or [`WebViewBuilder::new_as_child`] /// or on Linux, if was created by [`WebViewExtUnix::new_gtk`] or [`WebViewBuilderExtUnix::new_gtk`] with [`gtk::Fixed`]. pub bounds: Option, + + /// Whether JavaScript code should be injected into only main or all subframes + pub inject_into_subframes: UserContentInjectedFrames, } impl Default for WebViewAttributes { @@ -541,6 +545,7 @@ impl Default for WebViewAttributes { position: dpi::LogicalPosition::new(0, 0).into(), size: dpi::LogicalSize::new(200, 200).into(), }), + inject_into_subframes: UserContentInjectedFrames::TopFrame, } } } @@ -891,6 +896,16 @@ impl<'a> WebViewBuilder<'a> { self } + /// Whether JavaScript code should be injected to all subframes + pub fn with_inject_into_subframes(mut self, inject_into_subframes: bool) -> Self { + self.attrs.inject_into_subframes = if inject_into_subframes { + UserContentInjectedFrames::AllFrames + } else { + UserContentInjectedFrames::TopFrame + }; + self + } + /// Set a navigation handler to decide if incoming url is allowed to navigate. /// /// The closure take a `String` parameter as url and returns a `bool` to determine whether the navigation should happen. diff --git a/src/webkitgtk/mod.rs b/src/webkitgtk/mod.rs index e92ee5651..75dd3c7f2 100644 --- a/src/webkitgtk/mod.rs +++ b/src/webkitgtk/mod.rs @@ -292,11 +292,11 @@ impl InnerWebView { }; // Initialize message handler - w.init("Object.defineProperty(window, 'ipc', { value: Object.freeze({ postMessage: function(x) { window.webkit.messageHandlers['ipc'].postMessage(x) } }) })")?; + w.init("Object.defineProperty(window, 'ipc', { value: Object.freeze({ postMessage: function(x) { window.webkit.messageHandlers['ipc'].postMessage(x) } }) })", attributes.inject_into_subframes)?; // Initialize scripts for js in attributes.initialization_scripts { - w.init(&js)?; + w.init(&js, attributes.inject_into_subframes)?; } // Run pending webview.eval() scripts once webview loads. @@ -598,12 +598,11 @@ impl InnerWebView { Ok(()) } - fn init(&self, js: &str) -> Result<()> { + fn init(&self, js: &str, injected_frames: UserContentInjectedFrames) -> Result<()> { if let Some(manager) = self.webview.user_content_manager() { let script = UserScript::new( js, - // TODO: feature to allow injecting into subframes - UserContentInjectedFrames::TopFrame, + injected_frames, UserScriptInjectionTime::Start, &[], &[], diff --git a/src/webview2/mod.rs b/src/webview2/mod.rs index 2dfb53474..3052a853b 100644 --- a/src/webview2/mod.rs +++ b/src/webview2/mod.rs @@ -578,6 +578,8 @@ impl InnerWebView { if let Some(on_page_load_handler) = attributes.on_page_load_handler.take() { let on_page_load_handler = Rc::new(on_page_load_handler); let on_page_load_handler_ = on_page_load_handler.clone(); + let scripts = attributes.initialization_scripts.clone(); + webview.add_ContentLoading( &ContentLoadingEventHandler::create(Box::new(move |webview, _| { let Some(webview) = webview else { @@ -586,6 +588,10 @@ impl InnerWebView { on_page_load_handler_(PageLoadEvent::Started, Self::url_from_webview(&webview)?); + for script in &scripts { + Self::execute_script(&webview, script.clone(), |_| ())?; + } + Ok(()) })), token, diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 752211a48..ce763d6e3 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -66,14 +66,14 @@ use crate::{ Error, PageLoadEvent, Rect, RequestAsyncResponder, Result, WebContext, WebViewAttributes, RGBA, }; +use self::util::Counter; use http::{ header::{CONTENT_LENGTH, CONTENT_TYPE}, status::StatusCode, version::Version, Request, Response as HttpResponse, }; - -use self::util::Counter; +use webkit2gtk::UserContentInjectedFrames; const IPC_MESSAGE_HANDLER_NAME: &str = "ipc"; #[cfg(target_os = "macos")] @@ -986,9 +986,10 @@ impl InnerWebView { r#"Object.defineProperty(window, 'ipc', { value: Object.freeze({postMessage: function(s) {window.webkit.messageHandlers.ipc.postMessage(s);}}) });"#, + attributes.inject_into_subframes ); for js in attributes.initialization_scripts { - w.init(&js); + w.init(&js, attributes.inject_into_subframes); } // Set user agent @@ -1109,15 +1110,18 @@ r#"Object.defineProperty(window, 'ipc', { Ok(()) } - fn init(&self, js: &str) { + fn init(&self, js: &str, injected_frames: UserContentInjectedFrames) { + let for_main_frame_only = match injected_frames { + UserContentInjectedFrames::AllFrames => 0, + _ => 1, + }; + // Safety: objc runtime calls are unsafe // Equivalent Obj-C: // [manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString stringWithUTF8String:js.c_str()] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]] unsafe { let userscript: id = msg_send![class!(WKUserScript), alloc]; - let script: id = - // TODO: feature to allow injecting into subframes - msg_send![userscript, initWithSource:NSString::new(js) injectionTime:0 forMainFrameOnly:1]; + let script: id = msg_send![userscript, initWithSource:NSString::new(js) injectionTime:0 forMainFrameOnly:for_main_frame_only]; let _: () = msg_send![self.manager, addUserScript: script]; } }