Skip to content

Commit

Permalink
feat: on page load handler (#980)
Browse files Browse the repository at this point in the history
* Add new API

* Implement for Android

* Implement for Linux

* Implement for Windows

* Update API to take a Url instead of String

* Implement for MacOS/iOS

* Android fixes

* Add change file

* Address feedback

* Update change file

* Update src/webview/mod.rs

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>

* Change method name

* Goodbye navigaation started handler

* Update src/webview/webview2/mod.rs

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>

* Fix the android build

* Fix memory leak and refactor wkwebview navigation handlers

* Fix clippy lint and change public API

* unify on page load handlers

* fix macos

* fix cast

* return directly

---------

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
  • Loading branch information
3 people authored Jul 23, 2023
1 parent 7631c68 commit 7a353c7
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 49 deletions.
5 changes: 5 additions & 0 deletions .changes/on-load-handler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wry": "minor"
---

Add `WebViewBuilder::with_on_page_load_handler` for providing a callback for handling various page loading events.
32 changes: 30 additions & 2 deletions src/webview/android/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ use tao::platform::android::ndk_glue::jni::{
};

use super::{
create_headers_map, ASSET_LOADER_DOMAIN, IPC, REQUEST_HANDLER, TITLE_CHANGE_HANDLER,
URL_LOADING_OVERRIDE, WITH_ASSET_LOADER,
create_headers_map, 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<jobject, JniError> {
let mut request_builder = Request::builder();

Expand Down Expand Up @@ -205,3 +207,29 @@ pub unsafe fn assetLoaderDomain(env: JNIEnv, _: JClass) -> jstring {
env.new_string("wry.assets").unwrap().into_raw()
}
}

#[allow(non_snake_case)]
pub unsafe fn onPageLoading(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() {
(h.0)(PageLoadEvent::Started, url)
}
}
Err(e) => log::warn!("Failed to parse JString: {}", e),
}
}

#[allow(non_snake_case)]
pub unsafe fn onPageLoaded(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() {
(h.0)(PageLoadEvent::Finished, url)
}
}
Err(e) => log::warn!("Failed to parse JString: {}", e),
}
}
11 changes: 11 additions & 0 deletions src/webview/android/kotlin/RustWebViewClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package {{package}}

import android.webkit.*
import android.content.Context
import android.graphics.Bitmap
import androidx.webkit.WebViewAssetLoader

class RustWebViewClient(context: Context): WebViewClient() {
Expand All @@ -32,6 +33,14 @@ class RustWebViewClient(context: Context): WebViewClient() {
return shouldOverride(request.url.toString())
}

override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?): Unit {
return onPageLoading(url)
}

override fun onPageFinished(view: WebView, url: String): Unit {
return onPageLoaded(url)
}


companion object {
init {
Expand All @@ -43,6 +52,8 @@ class RustWebViewClient(context: Context): WebViewClient() {
private external fun withAssetLoader(): Boolean
private external fun handleRequest(request: WebResourceRequest): WebResourceResponse?
private external fun shouldOverride(url: String): Boolean
private external fun onPageLoading(url: String)
private external fun onPageLoaded(url: String)

{{class-extension}}
}
30 changes: 29 additions & 1 deletion src/webview/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use super::{WebContext, WebViewAttributes, RGBA};
use super::{PageLoadEvent, WebContext, WebViewAttributes, RGBA};
use crate::{application::window::Window, Result};
use base64::{engine::general_purpose, Engine};
use crossbeam_channel::*;
Expand Down Expand Up @@ -90,6 +90,20 @@ macro_rules! android_binding {
[JString],
jboolean
);
android_fn!(
$domain,
$package,
RustWebViewClient,
onPageLoading,
[JString]
);
android_fn!(
$domain,
$package,
RustWebViewClient,
onPageLoaded,
[JString]
);
android_fn!($domain, $package, Ipc, ipc, [JString]);
android_fn!(
$domain,
Expand All @@ -107,6 +121,7 @@ pub static TITLE_CHANGE_HANDLER: OnceCell<UnsafeTitleHandler> = OnceCell::new();
pub static WITH_ASSET_LOADER: OnceCell<bool> = OnceCell::new();
pub static ASSET_LOADER_DOMAIN: OnceCell<String> = OnceCell::new();
pub static URL_LOADING_OVERRIDE: OnceCell<UnsafeUrlLoadingOverride> = OnceCell::new();
pub static ON_LOAD_HANDLER: OnceCell<UnsafeOnPageLoadHandler> = OnceCell::new();

pub struct UnsafeIpc(Box<dyn Fn(&Window, String)>, Rc<Window>);
impl UnsafeIpc {
Expand Down Expand Up @@ -146,6 +161,15 @@ impl UnsafeUrlLoadingOverride {
unsafe impl Send for UnsafeUrlLoadingOverride {}
unsafe impl Sync for UnsafeUrlLoadingOverride {}

pub struct UnsafeOnPageLoadHandler(Box<dyn Fn(PageLoadEvent, String)>);
impl UnsafeOnPageLoadHandler {
pub fn new(f: Box<dyn Fn(PageLoadEvent, String)>) -> Self {
Self(f)
}
}
unsafe impl Send for UnsafeOnPageLoadHandler {}
unsafe impl Sync for UnsafeOnPageLoadHandler {}

pub unsafe fn setup(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
Expand Down Expand Up @@ -336,6 +360,10 @@ impl InnerWebView {
URL_LOADING_OVERRIDE.get_or_init(move || UnsafeUrlLoadingOverride::new(i));
}

if let Some(h) = attributes.on_page_load_handler {
ON_LOAD_HANDLER.get_or_init(move || UnsafeOnPageLoadHandler::new(h));
}

Ok(Self { window })
}

Expand Down
24 changes: 24 additions & 0 deletions src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ pub struct WebViewAttributes {

/// Whether all media can be played without user interaction.
pub autoplay: bool,

/// Set a handler closure to process page load events.
pub on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent, String)>>,
}

impl Default for WebViewAttributes {
Expand Down Expand Up @@ -263,6 +266,7 @@ impl Default for WebViewAttributes {
document_title_changed_handler: None,
incognito: false,
autoplay: true,
on_page_load_handler: None,
}
}
}
Expand Down Expand Up @@ -316,6 +320,14 @@ pub(crate) struct PlatformSpecificWebViewAttributes {
/// Each value can be 0..255 inclusive.
pub type RGBA = (u8, u8, u8, u8);

/// Type of of page loading event
pub enum PageLoadEvent {
/// Indicates that the content of the page has started loading
Started,
/// Indicates that the page content has finished loading
Finished,
}

/// Builder type of [`WebView`].
///
/// [`WebViewBuilder`] / [`WebView`] are the basic building blocks to construct WebView contents and
Expand All @@ -333,6 +345,7 @@ impl<'a> WebViewBuilder<'a> {
pub fn new(window: Window) -> Result<Self> {
let webview = WebViewAttributes::default();
let web_context = None;
#[allow(clippy::default_constructed_unit_structs)]
let platform_specific = PlatformSpecificWebViewAttributes::default();

Ok(Self {
Expand Down Expand Up @@ -629,6 +642,17 @@ impl<'a> WebViewBuilder<'a> {
self
}

/// Set a handler to process page loading events.
///
/// The handler will be called when the webview begins the indicated loading event.
pub fn with_on_page_load_handler(
mut self,
handler: impl Fn(PageLoadEvent, String) + 'static,
) -> Self {
self.webview.on_page_load_handler = Some(Box::new(handler));
self
}

/// Consume the builder and create the [`WebView`].
///
/// Platform-specific behavior:
Expand Down
19 changes: 18 additions & 1 deletion src/webview/webkitgtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub use web_context::WebContextImpl;

use crate::{
application::{platform::unix::*, window::Window},
webview::{web_context::WebContext, WebViewAttributes, RGBA},
webview::{web_context::WebContext, PageLoadEvent, WebViewAttributes, RGBA},
Error, Result,
};

Expand Down Expand Up @@ -136,6 +136,23 @@ impl InnerWebView {
});
}

let on_page_load_handler = attributes.on_page_load_handler.take();
if on_page_load_handler.is_some() {
webview.connect_load_changed(move |webview, load_event| match load_event {
LoadEvent::Committed => {
if let Some(ref f) = on_page_load_handler {
f(PageLoadEvent::Started, webview.uri().unwrap().to_string());
}
}
LoadEvent::Finished => {
if let Some(ref f) = on_page_load_handler {
f(PageLoadEvent::Finished, webview.uri().unwrap().to_string());
}
}
_ => (),
});
}

webview.add_events(
EventMask::POINTER_MOTION_MASK
| EventMask::BUTTON1_MOTION_MASK
Expand Down
51 changes: 42 additions & 9 deletions src/webview/webview2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
mod file_drop;

use crate::{
webview::{WebContext, WebViewAttributes, RGBA},
webview::{PageLoadEvent, WebContext, WebViewAttributes, RGBA},
Error, Result,
};

Expand All @@ -14,7 +14,7 @@ use url::Url;

use std::{
collections::HashSet, fmt::Write, iter::once, mem::MaybeUninit, os::windows::prelude::OsStrExt,
path::PathBuf, rc::Rc, sync::mpsc,
path::PathBuf, rc::Rc, sync::mpsc, sync::Arc,
};

use once_cell::unsync::OnceCell;
Expand Down Expand Up @@ -311,6 +311,39 @@ impl InnerWebView {
}
}

if let Some(on_page_load_handler) = attributes.on_page_load_handler {
let on_page_load_handler = Arc::new(on_page_load_handler);
let on_page_load_handler_ = on_page_load_handler.clone();

unsafe {
webview
.add_ContentLoading(
&ContentLoadingEventHandler::create(Box::new(move |webview, _| {
if let Some(webview) = webview {
on_page_load_handler_(PageLoadEvent::Started, url_from_webview(&webview))
}
Ok(())
})),
&mut token,
)
.map_err(webview2_com::Error::WindowsError)?;
}

unsafe {
webview
.add_NavigationCompleted(
&NavigationCompletedEventHandler::create(Box::new(move |webview, _| {
if let Some(webview) = webview {
on_page_load_handler(PageLoadEvent::Finished, url_from_webview(&webview))
}
Ok(())
})),
&mut token,
)
.map_err(webview2_com::Error::WindowsError)?;
}
}

// Initialize scripts
Self::add_script_to_execute_on_document_created(
&webview,
Expand Down Expand Up @@ -834,13 +867,7 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_
}

pub fn url(&self) -> Url {
let mut pwstr = PWSTR::null();

unsafe { self.webview.Source(&mut pwstr).unwrap() };

let uri = take_pwstr(pwstr);

Url::parse(&uri).unwrap()
Url::parse(&url_from_webview(&self.webview)).unwrap()
}

pub fn eval(
Expand Down Expand Up @@ -1049,3 +1076,9 @@ fn get_windows_ver() -> Option<(u32, u32, u32)> {
None
}
}

fn url_from_webview(webview: &ICoreWebView2) -> String {
let mut pwstr = PWSTR::null();
unsafe { webview.Source(&mut pwstr).unwrap() };
take_pwstr(pwstr)
}
Loading

0 comments on commit 7a353c7

Please sign in to comment.