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.
68 changes: 52 additions & 16 deletions src/webview/android/main_pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use tao::platform::android::ndk_glue::{
objects::{GlobalRef, JObject},
JNIEnv,
},
PACKAGE,
JMap, PACKAGE,
};

use super::find_my_class;
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,22 @@ impl MainPipe<'_> {

// Load URL
if let Ok(url) = env.new_string(url) {
env.call_method(
webview,
"loadUrlMainThread",
"(Ljava/lang/String;)V",
&[url.into()],
)?;
if let Some(headers) = headers {
let headers_map = create_headers_map(&env, headers)?;
env.call_method(
webview,
"loadUrlMainThread",
"(Ljava/lang/String;Ljava/util/Map;)V",
&[url.into(), headers_map.into()],
)?;
} else {
env.call_method(
webview,
"loadUrlMainThread",
"(Ljava/lang/String;)V",
&[url.into()],
)?;
}
}

// Enable devtools
Expand Down Expand Up @@ -178,15 +189,25 @@ impl MainPipe<'_> {
f(env, activity, webview.as_obj());
}
}
WebViewMessage::LoadUrl(url) => {
WebViewMessage::LoadUrlWithHeaders(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)?;
if let Some(headers) = headers {
let headers_map = create_headers_map(&env, headers)?;
env.call_method(
webview.as_obj(),
"loadUrl",
"(Ljava/lang/String;Ljava/util/Map;)V",
&[url.into(), headers_map.into()],
)?;
} else {
env.call_method(
webview.as_obj(),
"loadUrl",
"(Ljava/lang/String;)V",
&[url.into()],
)?;
}
}
}
}
Expand Down Expand Up @@ -223,7 +244,7 @@ pub enum WebViewMessage {
GetWebViewVersion(Sender<Result<String, Error>>),
GetUrl(Sender<String>),
Jni(Box<dyn FnOnce(JNIEnv, JObject, JObject) + Send>),
LoadUrl(String),
LoadUrlWithHeaders(String, Option<http::HeaderMap>),
}

#[derive(Debug)]
Expand All @@ -232,4 +253,19 @@ pub struct CreateWebViewAttributes {
pub devtools: bool,
pub transparent: bool,
pub background_color: Option<RGBA>,
pub headers: Option<http::HeaderMap>,
}

fn create_headers_map<'a, 'b>(
env: &'a JNIEnv,
headers: http::HeaderMap,
) -> 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)
}
11 changes: 10 additions & 1 deletion src/webview/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ impl InnerWebView {
custom_protocols,
background_color,
transparent,
headers,
..
} = attributes;

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

Expand Down Expand Up @@ -260,7 +262,14 @@ impl InnerWebView {
}

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

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

Expand Down
17 changes: 15 additions & 2 deletions src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,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 @@ -234,6 +236,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 @@ -433,15 +436,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 @@ -785,6 +794,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
21 changes: 18 additions & 3 deletions src/webview/webkitgtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use std::{
};
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 @@ -350,7 +350,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 @@ -450,6 +450,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
37 changes: 26 additions & 11 deletions src/webview/webkitgtk/web_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ use std::{
};
use url::Url;
use webkit2gtk::{
traits::*, ApplicationInfo, CookiePersistentStorage, LoadEvent, UserContentManager, WebContext,
WebContextBuilder, WebView, WebsiteDataManagerBuilder,
traits::*, ApplicationInfo, CookiePersistentStorage, LoadEvent, URIRequest, UserContentManager,
WebContext, WebContextBuilder, WebView, WebsiteDataManagerBuilder,
};

#[derive(Debug)]
Expand Down Expand Up @@ -124,7 +124,7 @@ pub trait WebContextExt {
/// Add a [`WebView`] to the queue waiting to be opened.
///
/// See the `WebviewUriLoader` for more information.
fn queue_load_uri(&self, webview: Rc<WebView>, url: Url);
fn queue_load_uri(&self, webview: Rc<WebView>, url: Url, headers: Option<http::HeaderMap>);

/// Flush all queued [`WebView`]s waiting to load a uri.
///
Expand Down Expand Up @@ -177,8 +177,8 @@ impl WebContextExt for super::WebContext {
}
}

fn queue_load_uri(&self, webview: Rc<WebView>, url: Url) {
self.os.webview_uri_loader.push(webview, url)
fn queue_load_uri(&self, webview: Rc<WebView>, url: Url, headers: Option<http::HeaderMap>) {
self.os.webview_uri_loader.push(webview, url, headers)
}

fn flush_queue_loader(&self) {
Expand Down Expand Up @@ -402,7 +402,7 @@ where
#[derive(Debug, Default)]
struct WebviewUriLoader {
lock: AtomicBool,
queue: Mutex<VecDeque<(Rc<WebView>, Url)>>,
queue: Mutex<VecDeque<(Rc<WebView>, Url, Option<http::HeaderMap>)>>,
}

impl WebviewUriLoader {
Expand All @@ -417,21 +417,21 @@ impl WebviewUriLoader {
}

/// Add a [`WebView`] to the queue.
fn push(&self, webview: Rc<WebView>, url: Url) {
fn push(&self, webview: Rc<WebView>, url: Url, headers: Option<http::HeaderMap>) {
let mut queue = self.queue.lock().expect("poisoned load queue");
queue.push_back((webview, url))
queue.push_back((webview, url, headers))
}

/// Remove a [`WebView`] from the queue and return it.
fn pop(&self) -> Option<(Rc<WebView>, Url)> {
fn pop(&self) -> Option<(Rc<WebView>, Url, Option<http::HeaderMap>)> {
let mut queue = self.queue.lock().expect("poisoned load queue");
queue.pop_front()
}

/// Load the next uri to load if the lock is not engaged.
fn flush(self: Rc<Self>) {
if !self.is_locked() {
if let Some((webview, url)) = self.pop() {
if let Some((webview, url, headers)) = self.pop() {
// we do not need to listen to failed events because those will finish the change event anyways
webview.connect_load_changed(move |_, event| {
if let LoadEvent::Finished = event {
Expand All @@ -440,7 +440,22 @@ impl WebviewUriLoader {
};
});

webview.load_uri(url.as_str());
if let Some(headers) = headers {
let req = URIRequest::builder().uri(url.as_str()).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(),
);
}
}

webview.load_request(&req);
} else {
webview.load_uri(url.as_str());
}
} else {
self.unlock();
}
Expand Down
Loading