Skip to content

Commit

Permalink
fix(android): restore asset loading functionality to android (fix: #846
Browse files Browse the repository at this point in the history
…) (#854)

* fix(android): restore asset loading functionality to android

Implements WebViewAssetLoader to load assets from the asset folder in Android when `with_asset_loader` is called in the builder. The function also sets the desired protocol for use in `with_url`, although the url must always be `<protocol>://assets/<path>`.

Refs: #846

* docs(changes): document changes for android's fix-asset-loading patch

* Refactor to prevent additional allocation

* Fix merge conflict

* Disable default target to android

* Pass zero parameter to android fns

---------

Co-authored-by: Wu Wayne <yuweiwu@pm.me>
  • Loading branch information
lily-mosquitoes and wusyong committed Feb 7, 2023
1 parent 8e576e7 commit 077eb3a
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 17 deletions.
4 changes: 2 additions & 2 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#[build]
#target = "aarch64-linux-android"
# [build]
# target = "aarch64-linux-android"
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-mmacosx-version-min=10.12"]
5 changes: 5 additions & 0 deletions .changes/fix-android-asset-loading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wry": patch
---

On Android, `wry` can again load assets from the apk's `asset` folder via a custom protocol. This is set by `WebViewBuilder`'s method `with_asset_loader`, which is exclusive to Android (by virtue of existing within `WebViewBuilderExtAndroid`).
20 changes: 19 additions & 1 deletion src/webview/android/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ use http::{
header::{HeaderName, HeaderValue, CONTENT_TYPE},
Request,
};
pub use tao::platform::android::ndk_glue::jni::sys::{jboolean, jstring};
use tao::platform::android::ndk_glue::jni::{
errors::Error as JniError,
objects::{JClass, JMap, JObject, JString, JValue},
sys::jobject,
JNIEnv,
};

use super::{create_headers_map, IPC, REQUEST_HANDLER, TITLE_CHANGE_HANDLER};
use super::{
create_headers_map, ASSET_LOADER_DOMAIN, IPC, REQUEST_HANDLER, TITLE_CHANGE_HANDLER,
WITH_ASSET_LOADER,
};

fn handle_request(env: JNIEnv, request: JObject) -> Result<jobject, JniError> {
let mut request_builder = Request::builder();
Expand Down Expand Up @@ -165,3 +169,17 @@ pub unsafe fn handleReceivedTitle(env: JNIEnv, _: JClass, _webview: JObject, tit
Err(e) => log::warn!("Failed to parse JString: {}", e),
}
}

#[allow(non_snake_case)]
pub unsafe fn withAssetLoader(_: JNIEnv, _: JClass) -> jboolean {
(*WITH_ASSET_LOADER.get().unwrap_or(&false)).into()
}

#[allow(non_snake_case)]
pub unsafe fn assetLoaderDomain(env: JNIEnv, _: JClass) -> jstring {
if let Some(domain) = ASSET_LOADER_DOMAIN.get() {
env.new_string(domain).unwrap().into_raw()
} else {
env.new_string("wry.assets").unwrap().into_raw()
}
}
17 changes: 15 additions & 2 deletions src/webview/android/kotlin/RustWebViewClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@
package {{package}}

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

class RustWebViewClient(context: Context): WebViewClient() {
private val assetLoader = WebViewAssetLoader.Builder()
.setDomain(assetLoaderDomain())
.addPathHandler("/", WebViewAssetLoader.AssetsPathHandler(context))
.build()

class RustWebViewClient: WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
return handleRequest(request)
if (withAssetLoader()) {
return assetLoader.shouldInterceptRequest(request.url)
} else {
return handleRequest(request)
}
}

companion object {
Expand All @@ -20,6 +31,8 @@ class RustWebViewClient: WebViewClient() {
}
}

private external fun assetLoaderDomain(): String
private external fun withAssetLoader(): Boolean
private external fun handleRequest(request: WebResourceRequest): WebResourceResponse?

{{class-extension}}
Expand Down
19 changes: 15 additions & 4 deletions src/webview/android/main_pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl MainPipe<'_> {
transparent,
background_color,
headers,
pl_attrs,
on_webview_created,
} = attrs;
// Create webview
let rust_webview_class = find_class(
Expand Down Expand Up @@ -93,7 +93,11 @@ impl MainPipe<'_> {
activity,
format!("{}/RustWebViewClient", PACKAGE.get().unwrap()),
)?;
let webview_client = env.new_object(rust_webview_client_class, "()V", &[])?;
let webview_client = env.new_object(
rust_webview_client_class,
"(Landroid/content/Context;)V",
&[activity.into()],
)?;
env.call_method(
webview,
"setWebViewClient",
Expand Down Expand Up @@ -128,7 +132,7 @@ impl MainPipe<'_> {
&[webview.into()],
)?;

if let Some(on_webview_created) = pl_attrs.on_webview_created {
if let Some(on_webview_created) = on_webview_created {
if let Err(e) = on_webview_created(super::Context {
env,
activity,
Expand Down Expand Up @@ -264,5 +268,12 @@ pub(crate) struct CreateWebViewAttributes {
pub transparent: bool,
pub background_color: Option<RGBA>,
pub headers: Option<http::HeaderMap>,
pub pl_attrs: crate::webview::PlatformSpecificWebViewAttributes,
pub on_webview_created: Option<
Box<
dyn Fn(
super::Context,
) -> std::result::Result<(), tao::platform::android::ndk_glue::jni::errors::Error>
+ Send,
>,
>,
}
31 changes: 30 additions & 1 deletion src/webview/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ macro_rules! android_binding {
[JObject],
jobject
);
android_fn!(
$domain,
$package,
RustWebViewClient,
withAssetLoader,
[],
jboolean
);
android_fn!(
$domain,
$package,
RustWebViewClient,
assetLoaderDomain,
[],
jstring
);
android_fn!($domain, $package, Ipc, ipc, [JString]);
android_fn!(
$domain,
Expand All @@ -71,6 +87,8 @@ macro_rules! android_binding {
pub static IPC: OnceCell<UnsafeIpc> = OnceCell::new();
pub static REQUEST_HANDLER: OnceCell<UnsafeRequestHandler> = OnceCell::new();
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 struct UnsafeIpc(Box<dyn Fn(&Window, String)>, Rc<Window>);
impl UnsafeIpc {
Expand Down Expand Up @@ -165,6 +183,12 @@ impl InnerWebView {
..
} = attributes;

let super::PlatformSpecificWebViewAttributes {
on_webview_created,
with_asset_loader,
asset_loader_domain,
} = pl_attrs;

if let Some(u) = url {
let mut url_string = String::from(u.as_str());
let name = u.scheme();
Expand All @@ -181,10 +205,15 @@ impl InnerWebView {
background_color,
transparent,
headers,
pl_attrs,
on_webview_created,
}));
}

WITH_ASSET_LOADER.get_or_init(move || with_asset_loader);
if let Some(domain) = asset_loader_domain {
ASSET_LOADER_DOMAIN.get_or_init(move || domain);
}

REQUEST_HANDLER.get_or_init(move || {
UnsafeRequestHandler::new(Box::new(move |mut request| {
if let Some(custom_protocol) = custom_protocols.iter().find(|(name, _)| {
Expand Down
38 changes: 31 additions & 7 deletions src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ use wkwebview::*;
pub(crate) mod webview2;
#[cfg(target_os = "windows")]
use self::webview2::*;
use crate::application::dpi::PhysicalPosition;
use crate::Result;
use crate::{application::dpi::PhysicalPosition, Result};
#[cfg(target_os = "windows")]
use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller;
#[cfg(target_os = "windows")]
Expand Down Expand Up @@ -302,6 +301,8 @@ pub(crate) struct PlatformSpecificWebViewAttributes {
+ Send,
>,
>,
with_asset_loader: bool,
asset_loader_domain: Option<String>,
}

/// Type alias for a color in the RGBA format.
Expand Down Expand Up @@ -408,11 +409,11 @@ impl<'a> WebViewBuilder<'a> {
/// - Linux: Though it's same as macOS, there's a [bug] that Origin header in the request will be
/// empty. So the only way to pass the server is setting `Access-Control-Allow-Origin: *`.
/// - Windows: `https://<scheme_name>.<path>` (so it will be `https://wry.examples` in `custom_protocol` example)
/// - Android: Custom protocol on Android is fixed to `https://tauri.wry/` due to its design and
/// our approach to use it. On Android, We only handle the scheme name and ignore the closure. So
/// when you load the url like `wry://assets/index.html`, it will become
/// `https://tauri.wry/assets/index.html`. Android has `assets` and `resource` path finder to
/// locate your files in those directories. For more information, see [Loading in-app content](https://developer.android.com/guide/webapps/load-local-content) page.
/// - Android: For loading content from the `assets` folder (which is copied to the Andorid apk) please
/// use the function [`with_asset_loader`] from [`WebViewBuilderExtAndroid`] instead.
/// This function on Android can only be used to serve assets you can embed in the binary or are
/// elsewhere in Android (provided the app has appropriate access), but not from the `assets`
/// folder which lives within the apk. For the cases where this can be used, it works the same as in macOS and Linux.
/// - iOS: Same as macOS. To get the path of your assets, you can call [`CFBundle::resources_path`](https://docs.rs/core-foundation/latest/core_foundation/bundle/struct.CFBundle.html#method.resources_path). So url like `wry://assets/index.html` could get the html file in assets directory.
///
/// [bug]: https://bugs.webkit.org/show_bug.cgi?id=229034
Expand Down Expand Up @@ -689,6 +690,15 @@ pub trait WebViewBuilderExtAndroid {
self,
f: F,
) -> Self;

/// Use [WebviewAssetLoader](https://developer.android.com/reference/kotlin/androidx/webkit/WebViewAssetLoader)
/// to load assets from Android's `asset` folder when using `with_url` as `<protocol>://assets/` (e.g.:
/// `wry://assets/index.html`). Note that this registers a custom protocol with the provided
/// String, similar to [`with_custom_protocol`], but also sets the WebViewAssetLoader with the
/// necessary domain (which is fixed as `<protocol>.assets`). This cannot be used in conjunction
/// to `with_custom_protocol` for Android, as it changes the way in which requests are handled.
#[cfg(feature = "protocol")]
fn with_asset_loader(self, protocol: String) -> Self;
}

#[cfg(target_os = "android")]
Expand All @@ -706,6 +716,20 @@ impl WebViewBuilderExtAndroid for WebViewBuilder<'_> {
self.platform_specific.on_webview_created = Some(Box::new(f));
self
}

#[cfg(feature = "protocol")]
fn with_asset_loader(mut self, protocol: String) -> Self {
// register custom protocol with empty Response return,
// this is necessary due to the need of fixing a domain
// in WebViewAssetLoader.
self.webview.custom_protocols.push((
protocol.clone(),
Box::new(|_| Ok(Response::builder().body(Vec::new().into())?)),
));
self.platform_specific.with_asset_loader = true;
self.platform_specific.asset_loader_domain = Some(format!("{}.assets", protocol));
self
}
}

/// The fundamental type to present a [`WebView`].
Expand Down

0 comments on commit 077eb3a

Please sign in to comment.