Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add headers when loading URLs, closes #816 #826

Merged
merged 15 commits into from
Dec 31, 2022
5 changes: 5 additions & 0 deletions .changes/load_url_with_headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wry": "patch"
---

Add `WebViewBuilder::with_headers` and `WebView::load_url_with_headers` to navigate to urls with headers.
19 changes: 2 additions & 17 deletions src/webview/android/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use tao::platform::android::ndk_glue::jni::{
JNIEnv,
};

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

fn handle_request(env: JNIEnv, request: JObject) -> Result<jobject, JniError> {
let mut request_builder = Request::builder();
Expand Down Expand Up @@ -105,22 +105,7 @@ fn handle_request(env: JNIEnv, request: JObject) -> Result<jobject, JniError> {
(JObject::null().into(), JObject::null().into())
};

let hashmap = env.find_class("java/util/HashMap")?;
let response_headers = env.new_object(hashmap, "()V", &[])?;
for (key, value) in response.headers().iter() {
env.call_method(
response_headers,
"put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
&[
env.new_string(key.as_str())?.into(),
// TODO can we handle this better?
env
.new_string(String::from_utf8_lossy(value.as_bytes()))?
.into(),
],
)?;
}
let response_headers = create_headers_map(&env, response.headers())?;

let bytes = response.body();

Expand Down
7 changes: 7 additions & 0 deletions src/webview/android/kotlin/RustWebView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.annotation.SuppressLint
import android.webkit.*
import android.content.Context
import android.os.Build
import kotlin.collections.Map

class RustWebView(context: Context): WebView(context) {
init {
Expand All @@ -26,5 +27,11 @@ class RustWebView(context: Context): WebView(context) {
}
}

fun loadUrlMainThread(url: String, additionalHttpHeaders: Map<String, String>) {
post {
super.loadUrl(url, additionalHttpHeaders)
}
}

{{class-extension}}
}
52 changes: 35 additions & 17 deletions src/webview/android/main_pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ use std::os::unix::prelude::*;
use tao::platform::android::ndk_glue::{
jni::{
errors::Error as JniError,
objects::{GlobalRef, JObject},
objects::{GlobalRef, JObject, JString},
JNIEnv,
},
PACKAGE,
};

use super::find_my_class;
use super::{create_headers_map, find_my_class};

static CHANNEL: Lazy<(Sender<WebViewMessage>, Receiver<WebViewMessage>)> = Lazy::new(|| bounded(8));
pub static MAIN_PIPE: Lazy<[RawFd; 2]> = Lazy::new(|| {
Expand Down Expand Up @@ -50,6 +50,7 @@ impl MainPipe<'_> {
devtools,
transparent,
background_color,
headers,
} = attrs;
// Create webview
let rust_webview_class = find_my_class(
Expand All @@ -65,12 +66,7 @@ impl MainPipe<'_> {

// Load URL
if let Ok(url) = env.new_string(url) {
env.call_method(
webview,
"loadUrlMainThread",
"(Ljava/lang/String;)V",
&[url.into()],
)?;
load_url(env, webview, url, headers, true)?;
}

// Enable devtools
Expand Down Expand Up @@ -178,15 +174,10 @@ impl MainPipe<'_> {
f(env, activity, webview.as_obj());
}
}
WebViewMessage::LoadUrl(url) => {
WebViewMessage::LoadUrl(url, headers) => {
if let Some(webview) = &self.webview {
let s = env.new_string(url)?;
env.call_method(
webview.as_obj(),
"loadUrl",
"(Ljava/lang/String;)V",
&[s.into()],
)?;
let url = env.new_string(url)?;
load_url(env, webview.as_obj(), url, headers, false)?;
}
}
}
Expand All @@ -195,6 +186,32 @@ impl MainPipe<'_> {
}
}

fn load_url<'a>(
env: JNIEnv<'a>,
webview: JObject<'a>,
url: JString<'a>,
headers: Option<http::HeaderMap>,
main_thread: bool,
) -> Result<(), JniError> {
let function = if main_thread {
"loadUrlMainThread"
} else {
"loadUrl"
};
if let Some(headers) = headers {
let headers_map = create_headers_map(&env, &headers)?;
env.call_method(
webview,
function,
"(Ljava/lang/String;Ljava/util/Map;)V",
&[url.into(), headers_map.into()],
)?;
} else {
env.call_method(webview, function, "(Ljava/lang/String;)V", &[url.into()])?;
}
Ok(())
}

fn set_background_color<'a>(
env: JNIEnv<'a>,
webview: JObject<'a>,
Expand Down Expand Up @@ -223,7 +240,7 @@ pub enum WebViewMessage {
GetWebViewVersion(Sender<Result<String, Error>>),
GetUrl(Sender<String>),
Jni(Box<dyn FnOnce(JNIEnv, JObject, JObject) + Send>),
LoadUrl(String),
LoadUrl(String, Option<http::HeaderMap>),
}

#[derive(Debug)]
Expand All @@ -232,4 +249,5 @@ pub struct CreateWebViewAttributes {
pub devtools: bool,
pub transparent: bool,
pub background_color: Option<RGBA>,
pub headers: Option<http::HeaderMap>,
}
24 changes: 22 additions & 2 deletions src/webview/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use tao::platform::android::ndk_glue::{
JNIEnv,
},
ndk::looper::{FdEvent, ForeignLooper},
PACKAGE,
JMap, PACKAGE,
};
use url::Url;

Expand Down Expand Up @@ -155,6 +155,7 @@ impl InnerWebView {
custom_protocols,
background_color,
transparent,
headers,
..
} = attributes;

Expand All @@ -173,6 +174,7 @@ impl InnerWebView {
devtools,
background_color,
transparent,
headers,
}));
}

Expand Down Expand Up @@ -282,7 +284,11 @@ impl InnerWebView {
}

pub fn load_url(&self, url: &str) {
MainPipe::send(WebViewMessage::LoadUrl(url.to_string()));
MainPipe::send(WebViewMessage::LoadUrl(url.to_string(), None));
}

pub fn load_url_with_headers(&self, url: &str, headers: http::HeaderMap) {
MainPipe::send(WebViewMessage::LoadUrl(url.to_string(), Some(headers)));
}
}

Expand Down Expand Up @@ -342,3 +348,17 @@ fn find_my_class<'a>(
.l()?;
Ok(my_class.into())
}

fn create_headers_map<'a, 'b>(
env: &'a JNIEnv,
headers: &http::HeaderMap,
) -> std::result::Result<JMap<'a, 'b>, JniError> {
let obj = env.new_object("java/util/HashMap", "()V", &[])?;
let headers_map = JMap::from_env(&env, obj)?;
for (name, value) in headers.iter() {
let key = env.new_string(name)?;
let value = env.new_string(value.to_str().unwrap_or_default())?;
headers_map.put(key.into(), value.into())?;
}
Ok(headers_map)
}
20 changes: 16 additions & 4 deletions src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller;
#[cfg(target_os = "windows")]
use windows::{Win32::Foundation::HWND, Win32::UI::WindowsAndMessaging::DestroyWindow};

use std::borrow::Cow;
use std::{path::PathBuf, rc::Rc};
use std::{borrow::Cow, path::PathBuf, rc::Rc};

pub use url::Url;

Expand Down Expand Up @@ -83,6 +82,8 @@ pub struct WebViewAttributes {
pub background_color: Option<RGBA>,
/// Whether load the provided URL to [`WebView`].
pub url: Option<Url>,
/// Headers used when loading the requested `url`.
pub headers: Option<http::HeaderMap>,
/// Whether page zooming by hotkeys is enabled
///
/// ## Platform-specific
Expand Down Expand Up @@ -237,6 +238,7 @@ impl Default for WebViewAttributes {
transparent: false,
background_color: None,
url: None,
headers: None,
html: None,
initialization_scripts: vec![],
custom_protocols: vec![],
Expand Down Expand Up @@ -437,15 +439,21 @@ impl<'a> WebViewBuilder<'a> {
self
}

/// Specify headers used when loading the requested `url`.
pub fn with_headers(mut self, headers: http::HeaderMap) -> Self {
self.webview.headers = Some(headers);
self
}

/// Load the provided URL when the builder calling [`WebViewBuilder::build`] to create the
/// [`WebView`]. The provided URL must be valid.
/// [`WebView`]. The provided URL must be valid. This will be ignored if `url` or `request` is already provided.
pub fn with_url(mut self, url: &str) -> Result<Self> {
self.webview.url = Some(Url::parse(url)?);
Ok(self)
}

/// Load the provided HTML string when the builder calling [`WebViewBuilder::build`] to create the
/// [`WebView`]. This will be ignored if `url` is already provided.
/// [`WebView`]. This will be ignored if `url` or `request` is already provided.
///
/// # Warning
/// The Page loaded from html string will have different Origin on different platforms. And
Expand Down Expand Up @@ -798,6 +806,10 @@ impl WebView {
pub fn load_url(&self, url: &str) {
self.webview.load_url(url)
}

pub fn load_url_with_headers(&self, url: &str, headers: http::HeaderMap) {
self.webview.load_url_with_headers(url, headers)
}
}

/// An event enumeration sent to [`FileDropHandler`].
Expand Down
24 changes: 19 additions & 5 deletions src/webview/webkitgtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ use glib::signal::Inhibit;
use gtk::prelude::*;
#[cfg(any(debug_assertions, feature = "devtools"))]
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
rc::Rc,
sync::Mutex,
sync::{Arc, Mutex},
};
use url::Url;
use webkit2gtk::{
traits::*, LoadEvent, NavigationPolicyDecision, PolicyDecisionType, UserContentInjectedFrames,
UserScript, UserScriptInjectionTime, WebView, WebViewBuilder,
traits::*, LoadEvent, NavigationPolicyDecision, PolicyDecisionType, URIRequest,
UserContentInjectedFrames, UserScript, UserScriptInjectionTime, WebView, WebViewBuilder,
};
use webkit2gtk_sys::{
webkit_get_major_version, webkit_get_micro_version, webkit_get_minor_version,
Expand Down Expand Up @@ -361,7 +360,7 @@ impl InnerWebView {

// Navigation
if let Some(url) = attributes.url {
web_context.queue_load_uri(Rc::clone(&w.webview), url);
web_context.queue_load_uri(Rc::clone(&w.webview), url, attributes.headers);
web_context.flush_queue_loader();
} else if let Some(html) = attributes.html {
w.webview.load_html(&html, Some("http://localhost"));
Expand Down Expand Up @@ -461,6 +460,21 @@ impl InnerWebView {
pub fn load_url(&self, url: &str) {
self.webview.load_uri(url)
}

pub fn load_url_with_headers(&self, url: &str, headers: http::HeaderMap) {
let req = URIRequest::builder().uri(url).build();

if let Some(ref mut req_headers) = req.http_headers() {
for (header, value) in headers.iter() {
req_headers.append(
header.to_string().as_str(),
value.to_str().unwrap_or_default(),
);
}
}

self.webview.load_request(&req);
}
}

pub fn platform_webview_version() -> Result<String> {
Expand Down
Loading