use crate::{LogLevel, WebViewError, WebViewInputError, WebViewScriptError};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NativeWebViewId(u64);
impl NativeWebViewId {
pub(crate) const fn new(raw: u64) -> Self {
Self(raw)
}
#[cfg(feature = "test-support")]
pub const fn for_test(raw: u64) -> Self {
Self(raw)
}
#[allow(dead_code)]
pub(crate) const fn raw(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DocumentGeneration(u64);
impl DocumentGeneration {
pub(crate) const fn new(raw: u64) -> Self {
Self(raw)
}
#[cfg(feature = "test-support")]
pub const fn for_test(raw: u64) -> Self {
Self(raw)
}
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct TrustedLoadIntent(u64);
impl TrustedLoadIntent {
pub(crate) const fn new(raw: u64) -> Self {
Self(raw)
}
}
#[derive(Clone, Copy)]
pub struct TrustedDocumentAdmission {
native_view: NativeWebViewId,
generation: DocumentGeneration,
navigation_id: crate::events::NavigationId,
intent: TrustedLoadIntent,
}
impl TrustedDocumentAdmission {
pub(crate) const fn new(
native_view: NativeWebViewId,
generation: DocumentGeneration,
navigation_id: crate::events::NavigationId,
intent: TrustedLoadIntent,
) -> Self {
Self {
native_view,
generation,
navigation_id,
intent,
}
}
pub const fn native_view(&self) -> NativeWebViewId {
self.native_view
}
pub const fn generation(&self) -> DocumentGeneration {
self.generation
}
pub const fn navigation_id(&self) -> crate::events::NavigationId {
self.navigation_id
}
pub const fn intent(&self) -> TrustedLoadIntent {
self.intent
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DocumentBinding {
Unbound,
Bound(DocumentGeneration),
}
pub trait DocumentOutboundGate: Send + Sync {
fn with_active(&self, action: &mut dyn FnMut()) -> bool;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WebMessageFrame {
TopLevel,
Subframe,
Unproven,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WebMessageTransport {
AppleScriptMessage,
AndroidMessagePort,
AndroidJavascriptInterface,
WindowsWebMessage,
HarmonyMessagePort,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct WebMessageSource {
reported_url: Option<String>,
reported_origin: Option<String>,
}
impl WebMessageSource {
pub const fn unavailable() -> Self {
Self {
reported_url: None,
reported_origin: None,
}
}
pub fn diagnostic_url(reported_url: Option<String>) -> Self {
Self {
reported_url,
reported_origin: None,
}
}
pub fn diagnostic_origin(reported_origin: Option<String>) -> Self {
Self {
reported_url: None,
reported_origin,
}
}
pub fn diagnostic(reported_url: Option<String>, reported_origin: Option<String>) -> Self {
Self {
reported_url,
reported_origin,
}
}
pub fn reported_url(&self) -> Option<&str> {
self.reported_url.as_deref()
}
pub fn reported_origin(&self) -> Option<&str> {
self.reported_origin.as_deref()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WebMessageContext {
native_view: NativeWebViewId,
document: DocumentBinding,
frame: WebMessageFrame,
transport: WebMessageTransport,
source: WebMessageSource,
}
impl WebMessageContext {
pub(crate) const fn new(
native_view: NativeWebViewId,
document: DocumentBinding,
frame: WebMessageFrame,
transport: WebMessageTransport,
source: WebMessageSource,
) -> Self {
Self {
native_view,
document,
frame,
transport,
source,
}
}
pub const fn native_view(&self) -> NativeWebViewId {
self.native_view
}
pub const fn document(&self) -> DocumentBinding {
self.document
}
pub const fn frame(&self) -> WebMessageFrame {
self.frame
}
pub const fn transport(&self) -> WebMessageTransport {
self.transport
}
pub fn source(&self) -> &WebMessageSource {
&self.source
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncomingWebMessage {
body: String,
context: WebMessageContext,
}
impl IncomingWebMessage {
pub(crate) fn new(body: String, context: WebMessageContext) -> Self {
Self { body, context }
}
pub fn body(&self) -> &str {
&self.body
}
pub fn context(&self) -> &WebMessageContext {
&self.context
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SchemeRequestFrame {
TopLevelDocument,
Subresource,
Unproven,
}
#[derive(Debug)]
pub struct ContextualSchemeRequest {
request: http::Request<Vec<u8>>,
native_view: NativeWebViewId,
frame: SchemeRequestFrame,
}
impl ContextualSchemeRequest {
pub(crate) fn new(
request: http::Request<Vec<u8>>,
native_view: NativeWebViewId,
frame: SchemeRequestFrame,
) -> Self {
Self {
request,
native_view,
frame,
}
}
pub fn request(&self) -> &http::Request<Vec<u8>> {
&self.request
}
pub fn into_request(self) -> http::Request<Vec<u8>> {
self.request
}
pub const fn native_view(&self) -> NativeWebViewId {
self.native_view
}
pub const fn frame(&self) -> SchemeRequestFrame {
self.frame
}
}
#[derive(Debug)]
pub enum SchemeOutcome {
Handled(WebResourceResponse),
PassThrough,
}
pub(crate) type AsyncSchemeFuture = Pin<Box<dyn Future<Output = SchemeOutcome> + Send + 'static>>;
pub(crate) type AsyncSchemeHandler =
Arc<dyn Fn(ContextualSchemeRequest) -> AsyncSchemeFuture + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NavigationPolicy {
Allow,
Cancel,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NavigationRequest {
pub url: String,
pub has_user_gesture: bool,
pub is_main_frame: bool,
}
impl NavigationRequest {
pub fn new(url: impl Into<String>, has_user_gesture: bool, is_main_frame: bool) -> Self {
Self {
url: url.into(),
has_user_gesture,
is_main_frame,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NewWindowPolicy {
LoadInSelf,
Cancel,
}
pub type NavigationHandler = Box<dyn Fn(&NavigationRequest) -> NavigationPolicy + Send + Sync>;
pub type NewWindowHandler = Box<dyn Fn(&str) -> NewWindowPolicy + Send + Sync>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UserAgentOverride {
Default,
Custom(String),
}
impl UserAgentOverride {
pub fn validate(&self) -> Result<(), WebViewError> {
if let Self::Custom(value) = self {
if value.trim().is_empty() {
return Err(WebViewError::WebView(
"custom user-agent override must not be empty".to_string(),
));
}
if value.contains(['\r', '\n', '\0']) {
return Err(WebViewError::WebView(
"custom user-agent override must not contain CR, LF, or NUL".to_string(),
));
}
}
Ok(())
}
}
#[cfg(test)]
mod user_agent_override_tests {
use super::*;
#[test]
fn custom_user_agent_must_not_be_blank() {
assert!(UserAgentOverride::Custom(String::new()).validate().is_err());
assert!(UserAgentOverride::Custom(" ".into()).validate().is_err());
assert!(
UserAgentOverride::Custom("Mozilla/5.0 valid".into())
.validate()
.is_ok()
);
for invalid in [
"Mozilla/5.0\rInjected",
"Mozilla/5.0\nInjected",
"Mozilla\0/5.0",
] {
assert!(
UserAgentOverride::Custom(invalid.into())
.validate()
.is_err()
);
}
assert!(UserAgentOverride::Default.validate().is_ok());
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DownloadRequest {
pub url: String,
pub user_agent: Option<String>,
pub content_disposition: Option<String>,
pub mime_type: Option<String>,
pub content_length: Option<u64>,
pub suggested_filename: Option<String>,
pub source_page_url: Option<String>,
pub cookie: Option<String>,
}
pub type DownloadHandler = Box<dyn Fn(DownloadRequest) + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WebViewCookieSameSite {
Lax,
Strict,
None,
}
impl WebViewCookieSameSite {
pub fn as_str(self) -> &'static str {
match self {
Self::Lax => "lax",
Self::Strict => "strict",
Self::None => "none",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WebViewCookie {
pub name: String,
pub value: String,
pub domain: String,
pub path: String,
#[serde(default, skip_serializing_if = "is_false")]
pub host_only: bool,
#[serde(default)]
pub secure: bool,
#[serde(default)]
pub http_only: bool,
#[serde(default)]
pub session: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_unix_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub same_site: Option<WebViewCookieSameSite>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WebViewCookieSetRequest {
#[serde(default)]
pub url: String,
pub name: String,
pub value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(default = "default_cookie_path")]
pub path: String,
#[serde(default)]
pub secure: bool,
#[serde(default)]
pub http_only: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_unix_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub same_site: Option<WebViewCookieSameSite>,
}
fn default_cookie_path() -> String {
"/".to_string()
}
fn is_false(value: &bool) -> bool {
!*value
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileChooserRequest {
pub accept_types: Vec<String>,
pub allow_multiple: bool,
pub allow_directories: bool,
pub capture: bool,
pub source_page_url: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileChooserFile {
pub path: Option<String>,
pub uri: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileChooserResponse {
Cancel,
Error(String),
Files(Vec<FileChooserFile>),
}
#[derive(Debug)]
pub enum WebResourceBody {
Path(PathBuf),
Pipe(SystemPipeReader),
Bytes(Vec<u8>),
}
#[derive(Debug)]
pub struct SystemPipeReader {
#[cfg(unix)]
fd: std::os::fd::RawFd,
#[cfg(windows)]
handle: std::os::windows::io::RawHandle,
}
impl SystemPipeReader {
#[cfg(unix)]
pub fn into_raw_fd(self) -> std::os::fd::RawFd {
self.fd
}
#[cfg(unix)]
pub unsafe fn from_raw_fd(fd: std::os::fd::RawFd) -> Self {
Self { fd }
}
#[cfg(unix)]
pub fn into_file(self) -> std::fs::File {
use std::os::fd::FromRawFd;
unsafe { std::fs::File::from_raw_fd(self.into_raw_fd()) }
}
#[cfg(windows)]
pub fn into_raw_handle(self) -> std::os::windows::io::RawHandle {
self.handle
}
#[cfg(windows)]
pub unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
Self { handle }
}
#[cfg(windows)]
pub fn into_file(self) -> std::fs::File {
use std::os::windows::io::FromRawHandle;
unsafe { std::fs::File::from_raw_handle(self.into_raw_handle()) }
}
}
#[async_trait]
pub trait WebViewController: Send + Sync {
fn load_url(&self, url: &str) -> Result<(), WebViewError>;
fn load_data(&self, request: LoadDataRequest<'_>) -> Result<(), WebViewError>;
fn exec_js(&self, js: &str) -> Result<(), WebViewError>;
async fn eval_js(&self, js: &str) -> Result<serde_json::Value, WebViewScriptError>;
async fn current_url(&self) -> Result<Option<String>, WebViewError> {
Err(WebViewError::WebView(
"current_url is not implemented for this platform".to_string(),
))
}
fn post_message(&self, message: &str) -> Result<(), WebViewError>;
fn post_message_to_document(
&self,
_expected_generation: DocumentGeneration,
_gate: Arc<dyn DocumentOutboundGate>,
_message: &str,
) -> Result<(), WebViewError> {
Err(WebViewError::Unsupported(
"document-bound message posting".to_string(),
))
}
fn clear_browsing_data(&self) -> Result<(), WebViewError>;
fn set_user_agent_override(&self, user_agent: UserAgentOverride) -> Result<(), WebViewError>;
fn reload(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"reload is not implemented for this platform".to_string(),
))
}
fn go_back(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"go_back is not implemented for this platform".to_string(),
))
}
fn go_forward(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"go_forward is not implemented for this platform".to_string(),
))
}
async fn list_cookies(&self) -> Result<Vec<WebViewCookie>, WebViewError> {
Err(WebViewError::WebView(
"cookie store is not implemented for this platform".to_string(),
))
}
async fn set_cookie(&self, _request: WebViewCookieSetRequest) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"cookie store is not implemented for this platform".to_string(),
))
}
async fn delete_cookie(
&self,
_name: &str,
_domain: &str,
_path: &str,
) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"cookie store is not implemented for this platform".to_string(),
))
}
async fn clear_cookies(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"cookie store is not implemented for this platform".to_string(),
))
}
async fn clear_site_data(
&self,
_url: &str,
_options: ClearSiteDataOptions,
) -> Result<ClearSiteDataResult, WebViewError> {
Err(WebViewError::WebView(
"site-scoped data clearing is not implemented for this platform".to_string(),
))
}
async fn take_screenshot(&self) -> Result<Vec<u8>, WebViewError> {
Err(WebViewError::WebView(
"screenshot is not implemented for this platform".to_string(),
))
}
async fn start_network_capture(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"network capture is not implemented for this platform".to_string(),
))
}
async fn stop_network_capture(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"network capture is not implemented for this platform".to_string(),
))
}
async fn network_entries(&self) -> Result<NetworkCaptureSnapshot, WebViewError> {
Err(WebViewError::WebView(
"network capture is not implemented for this platform".to_string(),
))
}
async fn clear_network_capture(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"network capture is not implemented for this platform".to_string(),
))
}
}
#[derive(Debug, Clone, Copy)]
pub struct ClearSiteDataOptions {
pub cache: bool,
pub site_data: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct ClearSiteDataResult {
pub cache_cleared: bool,
pub site_data_cleared: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkEntry {
pub request_id: String,
pub url: String,
pub method: String,
pub resource_type: Option<String>,
pub request_headers: Vec<(String, String)>,
pub request_body: Option<String>,
pub status: Option<u16>,
pub response_headers: Vec<(String, String)>,
pub mime_type: Option<String>,
pub response_body: NetworkBody,
pub from_cache: bool,
pub failed: Option<String>,
pub wall_time: Option<f64>,
pub started: f64,
pub finished: Option<f64>,
}
impl NetworkEntry {
pub fn duration_ms(&self) -> Option<f64> {
self.finished
.filter(|finished| *finished >= self.started)
.map(|finished| (finished - self.started) * 1000.0)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum NetworkBody {
#[default]
None,
Text { text: String },
Base64 { base64: String },
Skipped { reason: String },
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NetworkCaptureSnapshot {
pub entries: Vec<NetworkEntry>,
pub dropped: u64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ClickOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TypeOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<usize>,
#[serde(default)]
pub replace: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FillOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PressOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selector: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ScrollOptions;
#[async_trait]
pub trait WebViewInputController: WebViewController {
async fn click(
&self,
_selector: &str,
_options: ClickOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn type_text(
&self,
_selector: &str,
_text: &str,
_options: TypeOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn fill(
&self,
_selector: &str,
_text: &str,
_options: FillOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn press(&self, _key: &str, _options: PressOptions) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn scroll(
&self,
_dx: f64,
_dy: f64,
_options: ScrollOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn scroll_to(
&self,
_selector: &str,
_options: ScrollOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
}
#[derive(Debug, Clone, Copy)]
pub struct LoadDataRequest<'a> {
pub data: &'a str,
pub base_url: &'a str,
pub history_url: Option<&'a str>,
}
impl<'a> LoadDataRequest<'a> {
pub fn new(data: &'a str, base_url: &'a str) -> Self {
Self {
data,
base_url,
history_url: None,
}
}
pub fn with_history_url(mut self, history_url: &'a str) -> Self {
self.history_url = Some(history_url);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadErrorKind {
Dns,
Network,
Timeout,
Security,
InvalidUrl,
NotFound,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoadError {
pub failing_url: Option<String>,
pub kind: LoadErrorKind,
pub description: String,
}
pub trait WebViewDelegate: Send + Sync {
fn on_navigation_event(&self, event: crate::events::NavigationEvent);
fn on_webview_state_change(&self, _change: crate::events::WebViewStateChange) {}
fn on_document_committed(
&self,
_native_view: NativeWebViewId,
_generation: DocumentGeneration,
_navigation_id: crate::events::NavigationId,
) {
}
fn on_trusted_document_admitted(&self, _admission: TrustedDocumentAdmission) {}
fn on_web_content_process_terminated(&self, _native_view: NativeWebViewId) {}
fn on_document_restored(&self, _native_view: NativeWebViewId, _url: &str) {}
fn handle_post_message(&self, message: IncomingWebMessage);
fn handle_native_component_message(&self, _message_json: String) {}
fn log(&self, level: LogLevel, message: &str);
}
#[derive(Debug)]
pub struct WebResourceResponse {
parts: http::response::Parts,
body: WebResourceBody,
}
impl From<Option<WebResourceResponse>> for SchemeOutcome {
fn from(value: Option<WebResourceResponse>) -> Self {
match value {
Some(response) => SchemeOutcome::Handled(response),
None => SchemeOutcome::PassThrough,
}
}
}
impl WebResourceResponse {
pub fn parts(&self) -> &http::response::Parts {
&self.parts
}
pub fn into_parts(self) -> (http::response::Parts, WebResourceBody) {
(self.parts, self.body)
}
}
impl From<(http::response::Parts, PathBuf)> for WebResourceResponse {
fn from(value: (http::response::Parts, PathBuf)) -> Self {
WebResourceResponse {
parts: value.0,
body: WebResourceBody::Path(value.1),
}
}
}
impl From<(http::response::Parts, SystemPipeReader)> for WebResourceResponse {
fn from(value: (http::response::Parts, SystemPipeReader)) -> Self {
WebResourceResponse {
parts: value.0,
body: WebResourceBody::Pipe(value.1),
}
}
}
impl From<(http::response::Parts, Vec<u8>)> for WebResourceResponse {
fn from(value: (http::response::Parts, Vec<u8>)) -> Self {
WebResourceResponse {
parts: value.0,
body: WebResourceBody::Bytes(value.1),
}
}
}
impl WebResourceResponse {
fn response_parts_with_status(status: u16) -> http::response::Parts {
let response = match http::Response::builder().status(status).body(()) {
Ok(response) => response,
Err(_) => http::Response::new(()),
};
let (parts, _) = response.into_parts();
parts
}
pub fn file(path: impl Into<PathBuf>) -> Self {
let path = path.into();
let content_length = std::fs::metadata(&path).ok().map(|m| m.len());
let mut parts = Self::response_parts_with_status(200);
if let Some(len) = content_length {
parts
.headers
.insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
}
Self {
parts,
body: WebResourceBody::Path(path),
}
}
pub fn bytes(data: impl Into<Vec<u8>>) -> Self {
let data = data.into();
let len = data.len();
let mut parts = Self::response_parts_with_status(200);
parts
.headers
.insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
Self {
parts,
body: WebResourceBody::Bytes(data),
}
}
pub fn stream(reader: SystemPipeReader) -> Self {
let parts = Self::response_parts_with_status(200);
Self {
parts,
body: WebResourceBody::Pipe(reader),
}
}
pub fn mime(mut self, content_type: &str) -> Self {
if let Ok(value) = http::HeaderValue::from_str(content_type) {
self.parts.headers.insert(http::header::CONTENT_TYPE, value);
}
self
}
pub fn status(mut self, code: u16) -> Self {
self.parts.status = http::StatusCode::from_u16(code).unwrap_or(self.parts.status);
self
}
pub fn header(mut self, name: &str, value: &str) -> Self {
if let (Ok(header_name), Ok(header_value)) = (
name.parse::<http::header::HeaderName>(),
http::HeaderValue::from_str(value),
) {
self.parts.headers.insert(header_name, header_value);
}
self
}
pub fn cors(self) -> Self {
self.header("access-control-allow-origin", "null")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn contextual_scheme_request_preserves_platform_context() {
let request = http::Request::builder()
.uri("lx://app/index.html")
.body(vec![1, 2, 3])
.unwrap();
let request = ContextualSchemeRequest::new(
request,
NativeWebViewId::new(91),
SchemeRequestFrame::TopLevelDocument,
);
assert_eq!(request.native_view(), NativeWebViewId::new(91));
assert_eq!(request.frame(), SchemeRequestFrame::TopLevelDocument);
assert_eq!(request.request().uri(), "lx://app/index.html");
assert_eq!(request.into_request().into_body(), vec![1, 2, 3]);
}
}