Skip to content

Commit 7221256

Browse files
NorbirosGeometricallyamrbashir
authoredOct 29, 2024··
feat: Allow injecting JavaScript code into subframes (#1365)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> Co-authored-by: amrbashir <github@amrbashir.me>
1 parent 1d63fa3 commit 7221256

File tree

7 files changed

+61
-25
lines changed

7 files changed

+61
-25
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wry": minor
3+
---
4+
5+
Add `WebViewBuilder::with_initialization_script_for_main_only` to enable injecting JavaScript code into main frame only or all subframes.

‎src/android/main_pipe.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl<'a> MainPipe<'a> {
7373
self.env.set_object_array_element(
7474
&initialization_scripts_array,
7575
i as i32,
76-
self.env.new_string(script)?,
76+
self.env.new_string(script.0)?,
7777
)?;
7878
}
7979

@@ -427,7 +427,7 @@ pub(crate) struct CreateWebViewAttributes {
427427
pub autoplay: bool,
428428
pub on_webview_created: Option<Box<dyn Fn(super::Context) -> JniResult<()> + Send>>,
429429
pub user_agent: Option<String>,
430-
pub initialization_scripts: Vec<String>,
430+
pub initialization_scripts: Vec<(String, bool)>,
431431
}
432432

433433
// SAFETY: only use this when you are sure the span will be dropped on the same thread it was entered

‎src/android/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,10 @@ impl InnerWebView {
260260
QualName::new(None, ns!(html), "script".into()),
261261
None,
262262
);
263-
script_el.append(NodeRef::new_text(script));
263+
script_el.append(NodeRef::new_text(script.0.as_str()));
264264
head.prepend(script_el);
265265
if csp.is_some() {
266-
hashes.push(hash_script(script));
266+
hashes.push(hash_script(script.0.as_str()));
267267
}
268268
}
269269
});

‎src/lib.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -357,15 +357,19 @@ pub struct WebViewAttributes<'a> {
357357
/// - **Windows:** the string can not be larger than 2 MB (2 * 1024 * 1024 bytes) in total size
358358
pub html: Option<String>,
359359

360-
/// Initialize javascript code when loading new pages. When webview load a new page, this
361-
/// initialization code will be executed. It is guaranteed that code is executed before
362-
/// `window.onload`.
360+
/// A list of initialization javascript scripts to run when loading new pages.
361+
/// When webview load a new page, this initialization code will be executed.
362+
/// It is guaranteed that code is executed before `window.onload`.
363+
///
364+
/// Second parameter represents if script should be added to main frame only or sub frames also.
365+
/// `true` for main frame only, `false` for sub frames.
363366
///
364367
/// ## Platform-specific
365368
///
369+
/// - **Windows**: scripts are injected into sub frames.
366370
/// - **Android:** The Android WebView does not provide an API for initialization scripts,
367371
/// so we prepend them to each HTML head. They are only implemented on custom protocol URLs.
368-
pub initialization_scripts: Vec<String>,
372+
pub initialization_scripts: Vec<(String, bool)>,
369373

370374
/// A list of custom loading protocols with pairs of scheme uri string and a handling
371375
/// closure.
@@ -693,16 +697,28 @@ impl<'a> WebViewBuilder<'a> {
693697
///
694698
/// ## Platform-specific
695699
///
700+
/// - **Windows:** scripts are added to subframes as well.
696701
/// - **Android:** When [addDocumentStartJavaScript] is not supported,
697702
/// we prepend them to each HTML head (implementation only supported on custom protocol URLs).
698703
/// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
699704
///
700705
/// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
701706
/// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
702707
pub fn with_initialization_script(self, js: &str) -> Self {
708+
self.with_initialization_script_for_main_only(js, true)
709+
}
710+
711+
/// Same as [`with_initialization_script`](Self::with_initialization_script) but with option to inject into main frame only or sub frames.
712+
///
713+
/// ## Platform-specific:
714+
///
715+
/// - **Windows:** scripts are always added to subframes regardless of the option.
716+
pub fn with_initialization_script_for_main_only(self, js: &str, main_only: bool) -> Self {
703717
self.and_then(|mut b| {
704718
if !js.is_empty() {
705-
b.attrs.initialization_scripts.push(js.to_string());
719+
b.attrs
720+
.initialization_scripts
721+
.push((js.to_string(), main_only));
706722
}
707723
Ok(b)
708724
})

‎src/webkitgtk/mod.rs

+9-6
Original file line numberDiff line numberDiff line change
@@ -304,11 +304,11 @@ impl InnerWebView {
304304
};
305305

306306
// Initialize message handler
307-
w.init("Object.defineProperty(window, 'ipc', { value: Object.freeze({ postMessage: function(x) { window.webkit.messageHandlers['ipc'].postMessage(x) } }) })")?;
307+
w.init("Object.defineProperty(window, 'ipc', { value: Object.freeze({ postMessage: function(x) { window.webkit.messageHandlers['ipc'].postMessage(x) } }) })", true)?;
308308

309309
// Initialize scripts
310-
for js in attributes.initialization_scripts {
311-
w.init(&js)?;
310+
for (js, for_main_only) in attributes.initialization_scripts {
311+
w.init(&js, for_main_only)?;
312312
}
313313

314314
// Run pending webview.eval() scripts once webview loads.
@@ -614,12 +614,15 @@ impl InnerWebView {
614614
Ok(())
615615
}
616616

617-
fn init(&self, js: &str) -> Result<()> {
617+
fn init(&self, js: &str, for_main_only: bool) -> Result<()> {
618618
if let Some(manager) = self.webview.user_content_manager() {
619619
let script = UserScript::new(
620620
js,
621-
// TODO: feature to allow injecting into subframes
622-
UserContentInjectedFrames::TopFrame,
621+
if for_main_only {
622+
UserContentInjectedFrames::TopFrame
623+
} else {
624+
UserContentInjectedFrames::AllFrames
625+
},
623626
UserScriptInjectionTime::Start,
624627
&[],
625628
&[],

‎src/webview2/mod.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,9 @@ impl InnerWebView {
440440
};
441441
}
442442

443-
// Initialize scripts
444-
for js in attributes.initialization_scripts {
445-
Self::add_script_to_execute_on_document_created(&webview, js)?;
443+
// Initialize main frame scripts
444+
for (js, for_main_only) in attributes.initialization_scripts {
445+
Self::add_script_to_execute_on_document_created(&webview, js, for_main_only)?;
446446
}
447447

448448
// Enable clipboard
@@ -589,6 +589,8 @@ impl InnerWebView {
589589
if let Some(on_page_load_handler) = attributes.on_page_load_handler.take() {
590590
let on_page_load_handler = Rc::new(on_page_load_handler);
591591
let on_page_load_handler_ = on_page_load_handler.clone();
592+
let scripts = attributes.initialization_scripts.clone();
593+
592594
webview.add_ContentLoading(
593595
&ContentLoadingEventHandler::create(Box::new(move |webview, _| {
594596
let Some(webview) = webview else {
@@ -597,6 +599,12 @@ impl InnerWebView {
597599

598600
on_page_load_handler_(PageLoadEvent::Started, Self::url_from_webview(&webview)?);
599601

602+
for (script, inject_into_sub_frames) in &scripts {
603+
if *inject_into_sub_frames {
604+
Self::execute_script(&webview, script.clone(), |_| ())?;
605+
}
606+
}
607+
600608
Ok(())
601609
})),
602610
token,
@@ -757,6 +765,7 @@ impl InnerWebView {
757765
String::from(
758766
r#"Object.defineProperty(window, 'ipc', { value: Object.freeze({ postMessage: s=> window.chrome.webview.postMessage(s) }) });"#,
759767
),
768+
true,
760769
)?;
761770

762771
let ipc_handler = attributes.ipc_handler.take();
@@ -1140,9 +1149,12 @@ impl InnerWebView {
11401149
);
11411150
}
11421151

1143-
// TODO: feature to allow injecting into (specific) subframes
11441152
#[inline]
1145-
fn add_script_to_execute_on_document_created(webview: &ICoreWebView2, js: String) -> Result<()> {
1153+
fn add_script_to_execute_on_document_created(
1154+
webview: &ICoreWebView2,
1155+
js: String,
1156+
_for_main_only: bool,
1157+
) -> Result<()> {
11461158
let webview = webview.clone();
11471159
AddScriptToExecuteOnDocumentCreatedCompletedHandler::wait_for_async_operation(
11481160
Box::new(move |handler| unsafe {

‎src/wkwebview/mod.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -480,9 +480,10 @@ impl InnerWebView {
480480
r#"Object.defineProperty(window, 'ipc', {
481481
value: Object.freeze({postMessage: function(s) {window.webkit.messageHandlers.ipc.postMessage(s);}})
482482
});"#,
483+
true
483484
);
484-
for js in attributes.initialization_scripts {
485-
w.init(&js);
485+
for (js, for_main_only) in attributes.initialization_scripts {
486+
w.init(&js, for_main_only);
486487
}
487488

488489
// Set user agent
@@ -602,16 +603,15 @@ r#"Object.defineProperty(window, 'ipc', {
602603
Ok(())
603604
}
604605

605-
fn init(&self, js: &str) {
606+
fn init(&self, js: &str, for_main_only: bool) {
606607
// Safety: objc runtime calls are unsafe
607608
unsafe {
608609
let userscript = WKUserScript::alloc();
609-
// TODO: feature to allow injecting into subframes
610610
let script = WKUserScript::initWithSource_injectionTime_forMainFrameOnly(
611611
userscript,
612612
&NSString::from_str(js),
613613
WKUserScriptInjectionTime::AtDocumentStart,
614-
true,
614+
for_main_only,
615615
);
616616
self.manager.addUserScript(&script);
617617
}

0 commit comments

Comments
 (0)
Please sign in to comment.