Skip to content

Commit

Permalink
feat: html string attributes (#368)
Browse files Browse the repository at this point in the history
* Add html attribute

* Implement loading html string on Linux

* Implement loading html string on mac and windows

* Add documentation about origin header

* Cargo fmt

* Add example documentation

* Add change file

* Fix typo

* Fix more typos
  • Loading branch information
Ngo Iok Ui (Wu Yu Wei) authored Aug 12, 2021
1 parent 2d9f5c9 commit 02ad372
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 3 deletions.
6 changes: 6 additions & 0 deletions .changes/load-html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"wry": patch
---

Add html attributes as another method to load the page. This can provide some other origin header and make CORS request
possible.
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Run the `cargo run --example <file_name>` to see how each example works.
- `dragndrop`: example for file drop handler.
- `custom_titlebar`: A frameless window with custom title-bar to show `drag-region` class in action.
- `custom_protocol`: uses a custom protocol to load files from bytes.
- `html`: load the html string and load other assets with custom protocol.
- `detect_js_ecma`: detects which versions of ECMAScript is supported by the webview.
- `menu_bar`: uses a custom menu for the application in macOS and the Window and Linux/Windows.
- `system_tray`: sample tray application with different behaviours.
Expand Down
77 changes: 77 additions & 0 deletions examples/html.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

fn main() -> wry::Result<()> {
use std::fs::{canonicalize, read};

use wry::{
application::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
},
webview::WebViewBuilder,
};

let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Hello World")
.build(&event_loop)
.unwrap();

let _webview = WebViewBuilder::new(window)
.unwrap()
// We still register custom protocol here to show that how the page with http:// origin can
// load them.
.with_custom_protocol("wry".into(), move |requested_asset_path| {
// Remove url scheme
let path = requested_asset_path.replace("wry://", "");
// Read the file content from file path
let content = read(canonicalize(&path)?)?;

// Return asset contents and mime types based on file extentions
// If you don't want to do this manually, there are some crates for you.
// Such as `infer` and `mime_guess`.
if path.ends_with(".html") {
Ok((content, String::from("text/html")))
} else if path.ends_with(".js") {
Ok((content, String::from("text/javascript")))
} else if path.ends_with(".png") {
Ok((content, String::from("image/png")))
} else {
unimplemented!();
}
})
// tell the webview to load the html string
.with_html(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<h1>Welcome to WRY!</h1>
<a href="wry://examples/hello.html">Link</a>
<script type="text/javascript" src="wry://examples/hello.js"></script>
<img src="wry://examples/icon.png" />
</body>
</html>"#,
)?
.build()?;

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

match event {
Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}
57 changes: 57 additions & 0 deletions src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ pub struct WebViewAttributes {
pub transparent: bool,
/// Whether load the provided URL to [`WebView`].
pub url: Option<Url>,
/// Whether load the provided html string to [`WebView`].
/// This will be ignored if the `url` is provided.
///
/// # Warning
/// The loaded from html string will have different Origin on different platforms. And
/// servers which enforce CORS will need to add exact same Origin header in `Access-Control-Allow-Origin`
/// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
/// different Origin headers across platforms:
///
/// - macOS: `http://localhost`
/// - Linux: `http://localhost`
/// - Windows: `null`
pub html: Option<String>,
/// Initialize javascript code when loading new pages. When webview load a new page, this
/// initialization code will be executed. It is guaranteed that code is executed before
/// `window.onload`.
Expand All @@ -60,6 +73,19 @@ pub struct WebViewAttributes {
///
/// The closure takes a url string slice, and returns a two item tuple of a vector of
/// bytes which is the content and a mimetype string of the content.
///
/// # Warning
/// Pages loaded from custom protocol will have different Origin on different platforms. And
/// servers which enforce CORS will need to add exact same Origin header in `Access-Control-Allow-Origin`
/// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
/// different Origin headers across platforms:
///
/// - macOS: `<scheme_name>://<path>` (so it will be `wry://examples` in `custom_protocol` example)
/// - 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)
///
/// [bug]: https://bugs.webkit.org/show_bug.cgi?id=229034
pub custom_protocols: Vec<(String, Box<dyn Fn(&str) -> Result<(Vec<u8>, String)>>)>,
/// Set the RPC handler to Communicate between the host Rust code and Javascript on webview.
///
Expand Down Expand Up @@ -91,6 +117,7 @@ impl Default for WebViewAttributes {
visible: true,
transparent: false,
url: None,
html: None,
initialization_scripts: vec![],
custom_protocols: vec![],
rpc_handler: None,
Expand Down Expand Up @@ -148,6 +175,19 @@ impl<'a> WebViewBuilder<'a> {
///
/// The closure takes a url string slice, and returns a two item tuple of a
/// vector of bytes which is the content and a mimetype string of the content.
///
/// # Warning
/// Pages loaded from custom protocol will have different Origin on different platforms. And
/// servers which enforce CORS will need to add exact same Origin header in `Access-Control-Allow-Origin`
/// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
/// different Origin headers across platforms:
///
/// - macOS: `<scheme_name>://<path>` (so it will be `wry://examples` in `custom_protocol` example)
/// - 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)
///
/// [bug]: https://bugs.webkit.org/show_bug.cgi?id=229034
#[cfg(feature = "protocol")]
pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
where
Expand Down Expand Up @@ -201,6 +241,23 @@ impl<'a> WebViewBuilder<'a> {
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.
///
/// # Warning
/// The Page loaded from html string will have different Origin on different platforms. And
/// servers which enforce CORS will need to add exact same Origin header in `Access-Control-Allow-Origin`
/// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
/// different Origin headers across platforms:
///
/// - macOS: `http://localhost`
/// - Linux: `http://localhost`
/// - Windows: `null`
pub fn with_html(mut self, html: impl Into<String>) -> Result<Self> {
self.webview.html = Some(html.into());
Ok(self)
}

/// Set the web context that can share with multiple [`WebView`]s.
pub fn with_web_context(mut self, web_context: &'a mut WebContext) -> Self {
self.web_context = Some(web_context);
Expand Down
2 changes: 2 additions & 0 deletions src/webview/webkitgtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ impl InnerWebView {
if let Some(url) = attributes.url {
web_context.queue_load_uri(Rc::clone(&w.webview), url);
web_context.flush_queue_loader();
} else if let Some(html) = attributes.html {
w.webview.load_html(&html, Some("http://localhost"));
}

Ok(w)
Expand Down
2 changes: 2 additions & 0 deletions src/webview/webview2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ impl InnerWebView {
}
w.navigate(&url_string)?;
}
} else if let Some(html) = attributes.html {
w.navigate_to_string(&html)?;
}

controller.put_is_visible(true)?;
Expand Down
8 changes: 5 additions & 3 deletions src/webview/wkwebview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ impl InnerWebView {
} else {
w.navigate(url.as_str());
}
} else if let Some(html) = attributes.html {
w.navigate_to_string(&html);
}

// Inject the web view into the window as main content
Expand Down Expand Up @@ -338,11 +340,11 @@ impl InnerWebView {
}
}

fn navigate_to_string(&self, url: &str) {
fn navigate_to_string(&self, html: &str) {
// Safety: objc runtime calls are unsafe
unsafe {
let empty: id = msg_send![class!(NSURL), URLWithString: NSString::new("")];
let () = msg_send![self.webview, loadHTMLString:NSString::new(url) baseURL:empty];
let url: id = msg_send![class!(NSURL), URLWithString: NSString::new("http://localhost")];
let () = msg_send![self.webview, loadHTMLString:NSString::new(html) baseURL:url];
}
}

Expand Down

0 comments on commit 02ad372

Please sign in to comment.