mirror of
https://github.com/Drop-OSS/wry-cef.git
synced 2026-01-30 20:55:24 +01:00
* 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>
This commit is contained in:
@@ -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/fix-android-asset-loading.md
Normal file
5
.changes/fix-android-asset-loading.md
Normal file
@@ -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`).
|
||||
@@ -6,6 +6,7 @@ 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},
|
||||
@@ -13,7 +14,10 @@ use tao::platform::android::ndk_glue::jni::{
|
||||
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();
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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}}
|
||||
|
||||
@@ -51,7 +51,7 @@ impl MainPipe<'_> {
|
||||
transparent,
|
||||
background_color,
|
||||
headers,
|
||||
pl_attrs,
|
||||
on_webview_created,
|
||||
} = attrs;
|
||||
// Create webview
|
||||
let rust_webview_class = find_class(
|
||||
@@ -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",
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -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 {
|
||||
@@ -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();
|
||||
@@ -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, _)| {
|
||||
|
||||
@@ -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")]
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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")]
|
||||
@@ -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`].
|
||||
|
||||
Reference in New Issue
Block a user