1use crate::{LogLevel, WebViewError, WebViewInputError, WebViewScriptError};
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use std::future::Future;
5use std::path::PathBuf;
6use std::pin::Pin;
7use std::sync::Arc;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct NativeWebViewId(u64);
16
17impl NativeWebViewId {
18 pub(crate) const fn new(raw: u64) -> Self {
19 Self(raw)
20 }
21
22 #[cfg(feature = "test-support")]
24 pub const fn for_test(raw: u64) -> Self {
25 Self(raw)
26 }
27
28 #[allow(dead_code)]
30 pub(crate) const fn raw(self) -> u64 {
31 self.0
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct DocumentGeneration(u64);
38
39impl DocumentGeneration {
40 pub(crate) const fn new(raw: u64) -> Self {
43 Self(raw)
44 }
45
46 #[cfg(feature = "test-support")]
48 pub const fn for_test(raw: u64) -> Self {
49 Self(raw)
50 }
51
52 pub const fn get(self) -> u64 {
54 self.0
55 }
56}
57
58#[derive(Clone, Copy, PartialEq, Eq, Hash)]
65pub struct TrustedLoadIntent(u64);
66
67impl TrustedLoadIntent {
68 pub(crate) const fn new(raw: u64) -> Self {
69 Self(raw)
70 }
71}
72
73#[derive(Clone, Copy)]
79pub struct TrustedDocumentAdmission {
80 native_view: NativeWebViewId,
81 generation: DocumentGeneration,
82 navigation_id: crate::events::NavigationId,
83 intent: TrustedLoadIntent,
84}
85
86impl TrustedDocumentAdmission {
87 pub(crate) const fn new(
88 native_view: NativeWebViewId,
89 generation: DocumentGeneration,
90 navigation_id: crate::events::NavigationId,
91 intent: TrustedLoadIntent,
92 ) -> Self {
93 Self {
94 native_view,
95 generation,
96 navigation_id,
97 intent,
98 }
99 }
100
101 pub const fn native_view(&self) -> NativeWebViewId {
102 self.native_view
103 }
104
105 pub const fn generation(&self) -> DocumentGeneration {
106 self.generation
107 }
108
109 pub const fn navigation_id(&self) -> crate::events::NavigationId {
110 self.navigation_id
111 }
112
113 pub const fn intent(&self) -> TrustedLoadIntent {
114 self.intent
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120pub enum DocumentBinding {
121 Unbound,
123 Bound(DocumentGeneration),
125}
126
127pub trait DocumentOutboundGate: Send + Sync {
133 fn with_active(&self, action: &mut dyn FnMut()) -> bool;
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
142pub enum WebMessageFrame {
143 TopLevel,
144 Subframe,
145 Unproven,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
150pub enum WebMessageTransport {
151 AppleScriptMessage,
152 AndroidMessagePort,
153 AndroidJavascriptInterface,
154 WindowsWebMessage,
155 HarmonyMessagePort,
156 Other,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Default)]
166pub struct WebMessageSource {
167 reported_url: Option<String>,
168 reported_origin: Option<String>,
169}
170
171impl WebMessageSource {
172 pub const fn unavailable() -> Self {
173 Self {
174 reported_url: None,
175 reported_origin: None,
176 }
177 }
178
179 pub fn diagnostic_url(reported_url: Option<String>) -> Self {
180 Self {
181 reported_url,
182 reported_origin: None,
183 }
184 }
185
186 pub fn diagnostic_origin(reported_origin: Option<String>) -> Self {
187 Self {
188 reported_url: None,
189 reported_origin,
190 }
191 }
192
193 pub fn diagnostic(reported_url: Option<String>, reported_origin: Option<String>) -> Self {
194 Self {
195 reported_url,
196 reported_origin,
197 }
198 }
199
200 pub fn reported_url(&self) -> Option<&str> {
202 self.reported_url.as_deref()
203 }
204
205 pub fn reported_origin(&self) -> Option<&str> {
207 self.reported_origin.as_deref()
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct WebMessageContext {
217 native_view: NativeWebViewId,
218 document: DocumentBinding,
219 frame: WebMessageFrame,
220 transport: WebMessageTransport,
221 source: WebMessageSource,
222}
223
224impl WebMessageContext {
225 pub(crate) const fn new(
226 native_view: NativeWebViewId,
227 document: DocumentBinding,
228 frame: WebMessageFrame,
229 transport: WebMessageTransport,
230 source: WebMessageSource,
231 ) -> Self {
232 Self {
233 native_view,
234 document,
235 frame,
236 transport,
237 source,
238 }
239 }
240
241 pub const fn native_view(&self) -> NativeWebViewId {
242 self.native_view
243 }
244
245 pub const fn document(&self) -> DocumentBinding {
246 self.document
247 }
248
249 pub const fn frame(&self) -> WebMessageFrame {
250 self.frame
251 }
252
253 pub const fn transport(&self) -> WebMessageTransport {
254 self.transport
255 }
256
257 pub fn source(&self) -> &WebMessageSource {
258 &self.source
259 }
260}
261
262#[derive(Debug, Clone, PartialEq, Eq)]
264pub struct IncomingWebMessage {
265 body: String,
266 context: WebMessageContext,
267}
268
269impl IncomingWebMessage {
270 pub(crate) fn new(body: String, context: WebMessageContext) -> Self {
271 Self { body, context }
272 }
273
274 pub fn body(&self) -> &str {
275 &self.body
276 }
277
278 pub fn context(&self) -> &WebMessageContext {
279 &self.context
280 }
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
289pub enum SchemeRequestFrame {
290 TopLevelDocument,
291 Subresource,
292 Unproven,
293}
294
295#[derive(Debug)]
302pub struct ContextualSchemeRequest {
303 request: http::Request<Vec<u8>>,
304 native_view: NativeWebViewId,
305 frame: SchemeRequestFrame,
306}
307
308impl ContextualSchemeRequest {
309 pub(crate) fn new(
310 request: http::Request<Vec<u8>>,
311 native_view: NativeWebViewId,
312 frame: SchemeRequestFrame,
313 ) -> Self {
314 Self {
315 request,
316 native_view,
317 frame,
318 }
319 }
320
321 pub fn request(&self) -> &http::Request<Vec<u8>> {
322 &self.request
323 }
324
325 pub fn into_request(self) -> http::Request<Vec<u8>> {
326 self.request
327 }
328
329 pub const fn native_view(&self) -> NativeWebViewId {
330 self.native_view
331 }
332
333 pub const fn frame(&self) -> SchemeRequestFrame {
334 self.frame
335 }
336}
337
338#[derive(Debug)]
340pub enum SchemeOutcome {
341 Handled(WebResourceResponse),
343 PassThrough,
345}
346
347pub(crate) type AsyncSchemeFuture = Pin<Box<dyn Future<Output = SchemeOutcome> + Send + 'static>>;
349pub(crate) type AsyncSchemeHandler =
350 Arc<dyn Fn(ContextualSchemeRequest) -> AsyncSchemeFuture + Send + Sync>;
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub enum NavigationPolicy {
355 Allow,
357 Cancel,
360}
361
362#[derive(Debug, Clone, PartialEq, Eq)]
364pub struct NavigationRequest {
365 pub url: String,
366 pub has_user_gesture: bool,
367 pub is_main_frame: bool,
368}
369
370impl NavigationRequest {
371 pub fn new(url: impl Into<String>, has_user_gesture: bool, is_main_frame: bool) -> Self {
372 Self {
373 url: url.into(),
374 has_user_gesture,
375 is_main_frame,
376 }
377 }
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382pub enum NewWindowPolicy {
383 LoadInSelf,
385 Cancel,
387}
388
389pub type NavigationHandler = Box<dyn Fn(&NavigationRequest) -> NavigationPolicy + Send + Sync>;
390pub type NewWindowHandler = Box<dyn Fn(&str) -> NewWindowPolicy + Send + Sync>;
391
392#[derive(Debug, Clone, PartialEq, Eq)]
394pub enum UserAgentOverride {
395 Default,
397 Custom(String),
401}
402
403impl UserAgentOverride {
404 pub fn validate(&self) -> Result<(), WebViewError> {
406 if let Self::Custom(value) = self {
407 if value.trim().is_empty() {
408 return Err(WebViewError::WebView(
409 "custom user-agent override must not be empty".to_string(),
410 ));
411 }
412 if value.contains(['\r', '\n', '\0']) {
413 return Err(WebViewError::WebView(
414 "custom user-agent override must not contain CR, LF, or NUL".to_string(),
415 ));
416 }
417 }
418 Ok(())
419 }
420}
421
422#[cfg(test)]
423mod user_agent_override_tests {
424 use super::*;
425
426 #[test]
427 fn custom_user_agent_must_not_be_blank() {
428 assert!(UserAgentOverride::Custom(String::new()).validate().is_err());
429 assert!(UserAgentOverride::Custom(" ".into()).validate().is_err());
430 assert!(
431 UserAgentOverride::Custom("Mozilla/5.0 valid".into())
432 .validate()
433 .is_ok()
434 );
435 for invalid in [
436 "Mozilla/5.0\rInjected",
437 "Mozilla/5.0\nInjected",
438 "Mozilla\0/5.0",
439 ] {
440 assert!(
441 UserAgentOverride::Custom(invalid.into())
442 .validate()
443 .is_err()
444 );
445 }
446 assert!(UserAgentOverride::Default.validate().is_ok());
447 }
448}
449
450#[derive(Debug, Clone, PartialEq, Eq)]
451pub struct DownloadRequest {
452 pub url: String,
454 pub user_agent: Option<String>,
456 pub content_disposition: Option<String>,
458 pub mime_type: Option<String>,
460 pub content_length: Option<u64>,
462 pub suggested_filename: Option<String>,
464 pub source_page_url: Option<String>,
466 pub cookie: Option<String>,
468}
469
470pub type DownloadHandler = Box<dyn Fn(DownloadRequest) + Send + Sync>;
475
476#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
477#[serde(rename_all = "lowercase")]
478pub enum WebViewCookieSameSite {
479 Lax,
480 Strict,
481 None,
482}
483
484impl WebViewCookieSameSite {
485 pub fn as_str(self) -> &'static str {
486 match self {
487 Self::Lax => "lax",
488 Self::Strict => "strict",
489 Self::None => "none",
490 }
491 }
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
495pub struct WebViewCookie {
496 pub name: String,
497 pub value: String,
498 pub domain: String,
499 pub path: String,
500 #[serde(default, skip_serializing_if = "is_false")]
501 pub host_only: bool,
502 #[serde(default)]
503 pub secure: bool,
504 #[serde(default)]
505 pub http_only: bool,
506 #[serde(default)]
507 pub session: bool,
508 #[serde(default, skip_serializing_if = "Option::is_none")]
509 pub expires_unix_ms: Option<i64>,
510 #[serde(default, skip_serializing_if = "Option::is_none")]
511 pub same_site: Option<WebViewCookieSameSite>,
512}
513
514#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
515pub struct WebViewCookieSetRequest {
516 #[serde(default)]
517 pub url: String,
518 pub name: String,
519 pub value: String,
520 #[serde(default, skip_serializing_if = "Option::is_none")]
521 pub domain: Option<String>,
522 #[serde(default = "default_cookie_path")]
523 pub path: String,
524 #[serde(default)]
525 pub secure: bool,
526 #[serde(default)]
527 pub http_only: bool,
528 #[serde(default, skip_serializing_if = "Option::is_none")]
529 pub expires_unix_ms: Option<i64>,
530 #[serde(default, skip_serializing_if = "Option::is_none")]
531 pub same_site: Option<WebViewCookieSameSite>,
532}
533
534fn default_cookie_path() -> String {
535 "/".to_string()
536}
537
538fn is_false(value: &bool) -> bool {
539 !*value
540}
541
542#[derive(Debug, Clone, PartialEq, Eq)]
543pub struct FileChooserRequest {
544 pub accept_types: Vec<String>,
546 pub allow_multiple: bool,
548 pub allow_directories: bool,
550 pub capture: bool,
552 pub source_page_url: Option<String>,
554}
555
556#[derive(Debug, Clone, PartialEq, Eq)]
557pub struct FileChooserFile {
558 pub path: Option<String>,
559 pub uri: Option<String>,
560}
561
562#[derive(Debug, Clone, PartialEq, Eq)]
563pub enum FileChooserResponse {
564 Cancel,
565 Error(String),
566 Files(Vec<FileChooserFile>),
567}
568
569#[derive(Debug)]
571pub enum WebResourceBody {
572 Path(PathBuf),
574 Pipe(SystemPipeReader),
576 Bytes(Vec<u8>),
578}
579
580#[derive(Debug)]
582pub struct SystemPipeReader {
583 #[cfg(unix)]
584 fd: std::os::fd::RawFd,
585 #[cfg(windows)]
586 handle: std::os::windows::io::RawHandle,
587}
588
589impl SystemPipeReader {
590 #[cfg(unix)]
593 pub fn into_raw_fd(self) -> std::os::fd::RawFd {
594 self.fd
595 }
596
597 #[cfg(unix)]
603 pub unsafe fn from_raw_fd(fd: std::os::fd::RawFd) -> Self {
604 Self { fd }
605 }
606
607 #[cfg(unix)]
609 pub fn into_file(self) -> std::fs::File {
610 use std::os::fd::FromRawFd;
611 unsafe { std::fs::File::from_raw_fd(self.into_raw_fd()) }
612 }
613
614 #[cfg(windows)]
617 pub fn into_raw_handle(self) -> std::os::windows::io::RawHandle {
618 self.handle
619 }
620
621 #[cfg(windows)]
627 pub unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
628 Self { handle }
629 }
630
631 #[cfg(windows)]
633 pub fn into_file(self) -> std::fs::File {
634 use std::os::windows::io::FromRawHandle;
635 unsafe { std::fs::File::from_raw_handle(self.into_raw_handle()) }
636 }
637}
638
639#[async_trait]
641pub trait WebViewController: Send + Sync {
642 fn load_url(&self, url: &str) -> Result<(), WebViewError>;
644
645 fn load_data(&self, request: LoadDataRequest<'_>) -> Result<(), WebViewError>;
647
648 fn exec_js(&self, js: &str) -> Result<(), WebViewError>;
650
651 async fn eval_js(&self, js: &str) -> Result<serde_json::Value, WebViewScriptError>;
661
662 async fn current_url(&self) -> Result<Option<String>, WebViewError> {
664 Err(WebViewError::WebView(
665 "current_url is not implemented for this platform".to_string(),
666 ))
667 }
668
669 fn post_message(&self, message: &str) -> Result<(), WebViewError>;
671
672 fn post_message_to_document(
678 &self,
679 _expected_generation: DocumentGeneration,
680 _gate: Arc<dyn DocumentOutboundGate>,
681 _message: &str,
682 ) -> Result<(), WebViewError> {
683 Err(WebViewError::Unsupported(
684 "document-bound message posting".to_string(),
685 ))
686 }
687
688 fn clear_browsing_data(&self) -> Result<(), WebViewError>;
690
691 fn set_user_agent_override(&self, user_agent: UserAgentOverride) -> Result<(), WebViewError>;
693
694 fn reload(&self) -> Result<(), WebViewError> {
696 Err(WebViewError::WebView(
697 "reload is not implemented for this platform".to_string(),
698 ))
699 }
700
701 fn go_back(&self) -> Result<(), WebViewError> {
703 Err(WebViewError::WebView(
704 "go_back is not implemented for this platform".to_string(),
705 ))
706 }
707
708 fn go_forward(&self) -> Result<(), WebViewError> {
710 Err(WebViewError::WebView(
711 "go_forward is not implemented for this platform".to_string(),
712 ))
713 }
714
715 async fn list_cookies(&self) -> Result<Vec<WebViewCookie>, WebViewError> {
717 Err(WebViewError::WebView(
718 "cookie store is not implemented for this platform".to_string(),
719 ))
720 }
721
722 async fn set_cookie(&self, _request: WebViewCookieSetRequest) -> Result<(), WebViewError> {
724 Err(WebViewError::WebView(
725 "cookie store is not implemented for this platform".to_string(),
726 ))
727 }
728
729 async fn delete_cookie(
731 &self,
732 _name: &str,
733 _domain: &str,
734 _path: &str,
735 ) -> Result<(), WebViewError> {
736 Err(WebViewError::WebView(
737 "cookie store is not implemented for this platform".to_string(),
738 ))
739 }
740
741 async fn clear_cookies(&self) -> Result<(), WebViewError> {
743 Err(WebViewError::WebView(
744 "cookie store is not implemented for this platform".to_string(),
745 ))
746 }
747
748 async fn clear_site_data(
752 &self,
753 _url: &str,
754 _options: ClearSiteDataOptions,
755 ) -> Result<ClearSiteDataResult, WebViewError> {
756 Err(WebViewError::WebView(
757 "site-scoped data clearing is not implemented for this platform".to_string(),
758 ))
759 }
760
761 async fn take_screenshot(&self) -> Result<Vec<u8>, WebViewError> {
764 Err(WebViewError::WebView(
765 "screenshot is not implemented for this platform".to_string(),
766 ))
767 }
768
769 async fn start_network_capture(&self) -> Result<(), WebViewError> {
774 Err(WebViewError::WebView(
775 "network capture is not implemented for this platform".to_string(),
776 ))
777 }
778
779 async fn stop_network_capture(&self) -> Result<(), WebViewError> {
782 Err(WebViewError::WebView(
783 "network capture is not implemented for this platform".to_string(),
784 ))
785 }
786
787 async fn network_entries(&self) -> Result<NetworkCaptureSnapshot, WebViewError> {
790 Err(WebViewError::WebView(
791 "network capture is not implemented for this platform".to_string(),
792 ))
793 }
794
795 async fn clear_network_capture(&self) -> Result<(), WebViewError> {
797 Err(WebViewError::WebView(
798 "network capture is not implemented for this platform".to_string(),
799 ))
800 }
801}
802
803#[derive(Debug, Clone, Copy)]
806pub struct ClearSiteDataOptions {
807 pub cache: bool,
808 pub site_data: bool,
809}
810
811#[derive(Debug, Clone, Copy)]
819pub struct ClearSiteDataResult {
820 pub cache_cleared: bool,
821 pub site_data_cleared: bool,
822}
823
824#[derive(Debug, Clone, Serialize, Deserialize)]
826pub struct NetworkEntry {
827 pub request_id: String,
829 pub url: String,
830 pub method: String,
831 pub resource_type: Option<String>,
834 pub request_headers: Vec<(String, String)>,
835 pub request_body: Option<String>,
837 pub status: Option<u16>,
838 pub response_headers: Vec<(String, String)>,
839 pub mime_type: Option<String>,
840 pub response_body: NetworkBody,
841 pub from_cache: bool,
842 pub failed: Option<String>,
845 pub wall_time: Option<f64>,
847 pub started: f64,
849 pub finished: Option<f64>,
850}
851
852impl NetworkEntry {
853 pub fn duration_ms(&self) -> Option<f64> {
855 self.finished
856 .filter(|finished| *finished >= self.started)
857 .map(|finished| (finished - self.started) * 1000.0)
858 }
859}
860
861#[derive(Debug, Clone, Default, Serialize, Deserialize)]
863#[serde(tag = "kind", rename_all = "snake_case")]
864pub enum NetworkBody {
865 #[default]
867 None,
868 Text { text: String },
870 Base64 { base64: String },
872 Skipped { reason: String },
875}
876
877#[derive(Debug, Clone, Default, Serialize, Deserialize)]
879pub struct NetworkCaptureSnapshot {
880 pub entries: Vec<NetworkEntry>,
881 pub dropped: u64,
884}
885
886#[derive(Debug, Clone, Default, Serialize, Deserialize)]
887pub struct ClickOptions {
888 #[serde(default, skip_serializing_if = "Option::is_none")]
889 pub index: Option<usize>,
890}
891
892#[derive(Debug, Clone, Default, Serialize, Deserialize)]
893pub struct TypeOptions {
894 #[serde(default, skip_serializing_if = "Option::is_none")]
895 pub index: Option<usize>,
896 #[serde(default)]
897 pub replace: bool,
898}
899
900#[derive(Debug, Clone, Default, Serialize, Deserialize)]
901pub struct FillOptions {
902 #[serde(default, skip_serializing_if = "Option::is_none")]
903 pub index: Option<usize>,
904}
905
906#[derive(Debug, Clone, Default, Serialize, Deserialize)]
907pub struct PressOptions {
908 #[serde(default, skip_serializing_if = "Option::is_none")]
909 pub selector: Option<String>,
910 #[serde(default, skip_serializing_if = "Option::is_none")]
911 pub index: Option<usize>,
912}
913
914#[derive(Debug, Clone, Default, Serialize, Deserialize)]
915pub struct ScrollOptions;
916
917#[async_trait]
918pub trait WebViewInputController: WebViewController {
919 async fn click(
920 &self,
921 _selector: &str,
922 _options: ClickOptions,
923 ) -> Result<(), WebViewInputError> {
924 Err(WebViewInputError::Unsupported(
925 "input control is not implemented for this platform",
926 ))
927 }
928
929 async fn type_text(
930 &self,
931 _selector: &str,
932 _text: &str,
933 _options: TypeOptions,
934 ) -> Result<(), WebViewInputError> {
935 Err(WebViewInputError::Unsupported(
936 "input control is not implemented for this platform",
937 ))
938 }
939
940 async fn fill(
941 &self,
942 _selector: &str,
943 _text: &str,
944 _options: FillOptions,
945 ) -> Result<(), WebViewInputError> {
946 Err(WebViewInputError::Unsupported(
947 "input control is not implemented for this platform",
948 ))
949 }
950
951 async fn press(&self, _key: &str, _options: PressOptions) -> Result<(), WebViewInputError> {
952 Err(WebViewInputError::Unsupported(
953 "input control is not implemented for this platform",
954 ))
955 }
956
957 async fn scroll(
958 &self,
959 _dx: f64,
960 _dy: f64,
961 _options: ScrollOptions,
962 ) -> Result<(), WebViewInputError> {
963 Err(WebViewInputError::Unsupported(
964 "input control is not implemented for this platform",
965 ))
966 }
967
968 async fn scroll_to(
969 &self,
970 _selector: &str,
971 _options: ScrollOptions,
972 ) -> Result<(), WebViewInputError> {
973 Err(WebViewInputError::Unsupported(
974 "input control is not implemented for this platform",
975 ))
976 }
977}
978
979#[derive(Debug, Clone, Copy)]
980pub struct LoadDataRequest<'a> {
981 pub data: &'a str,
982 pub base_url: &'a str,
983 pub history_url: Option<&'a str>,
984}
985
986impl<'a> LoadDataRequest<'a> {
987 pub fn new(data: &'a str, base_url: &'a str) -> Self {
988 Self {
989 data,
990 base_url,
991 history_url: None,
992 }
993 }
994
995 pub fn with_history_url(mut self, history_url: &'a str) -> Self {
996 self.history_url = Some(history_url);
997 self
998 }
999}
1000
1001#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1006pub enum LoadErrorKind {
1007 Dns,
1008 Network,
1009 Timeout,
1010 Security,
1011 InvalidUrl,
1012 NotFound,
1013 Unknown,
1014}
1015
1016#[derive(Debug, Clone, PartialEq, Eq)]
1022pub struct LoadError {
1023 pub failing_url: Option<String>,
1025 pub kind: LoadErrorKind,
1027 pub description: String,
1029}
1030
1031pub trait WebViewDelegate: Send + Sync {
1064 fn on_navigation_event(&self, event: crate::events::NavigationEvent);
1069
1070 fn on_webview_state_change(&self, _change: crate::events::WebViewStateChange) {}
1074
1075 fn on_document_committed(
1078 &self,
1079 _native_view: NativeWebViewId,
1080 _generation: DocumentGeneration,
1081 _navigation_id: crate::events::NavigationId,
1082 ) {
1083 }
1084
1085 fn on_trusted_document_admitted(&self, _admission: TrustedDocumentAdmission) {}
1092
1093 fn on_web_content_process_terminated(&self, _native_view: NativeWebViewId) {}
1097
1098 fn on_document_restored(&self, _native_view: NativeWebViewId, _url: &str) {}
1102
1103 fn handle_post_message(&self, message: IncomingWebMessage);
1108
1109 fn handle_native_component_message(&self, _message_json: String) {}
1115
1116 fn log(&self, level: LogLevel, message: &str);
1118}
1119
1120#[derive(Debug)]
1122pub struct WebResourceResponse {
1123 parts: http::response::Parts,
1124 body: WebResourceBody,
1125}
1126
1127impl From<Option<WebResourceResponse>> for SchemeOutcome {
1128 fn from(value: Option<WebResourceResponse>) -> Self {
1129 match value {
1130 Some(response) => SchemeOutcome::Handled(response),
1131 None => SchemeOutcome::PassThrough,
1132 }
1133 }
1134}
1135
1136impl WebResourceResponse {
1137 pub fn parts(&self) -> &http::response::Parts {
1139 &self.parts
1140 }
1141
1142 pub fn into_parts(self) -> (http::response::Parts, WebResourceBody) {
1144 (self.parts, self.body)
1145 }
1146}
1147
1148impl From<(http::response::Parts, PathBuf)> for WebResourceResponse {
1150 fn from(value: (http::response::Parts, PathBuf)) -> Self {
1151 WebResourceResponse {
1152 parts: value.0,
1153 body: WebResourceBody::Path(value.1),
1154 }
1155 }
1156}
1157
1158impl From<(http::response::Parts, SystemPipeReader)> for WebResourceResponse {
1160 fn from(value: (http::response::Parts, SystemPipeReader)) -> Self {
1161 WebResourceResponse {
1162 parts: value.0,
1163 body: WebResourceBody::Pipe(value.1),
1164 }
1165 }
1166}
1167
1168impl From<(http::response::Parts, Vec<u8>)> for WebResourceResponse {
1170 fn from(value: (http::response::Parts, Vec<u8>)) -> Self {
1171 WebResourceResponse {
1172 parts: value.0,
1173 body: WebResourceBody::Bytes(value.1),
1174 }
1175 }
1176}
1177
1178impl WebResourceResponse {
1179 fn response_parts_with_status(status: u16) -> http::response::Parts {
1180 let response = match http::Response::builder().status(status).body(()) {
1181 Ok(response) => response,
1182 Err(_) => http::Response::new(()),
1183 };
1184 let (parts, _) = response.into_parts();
1185 parts
1186 }
1187
1188 pub fn file(path: impl Into<PathBuf>) -> Self {
1190 let path = path.into();
1191 let content_length = std::fs::metadata(&path).ok().map(|m| m.len());
1192 let mut parts = Self::response_parts_with_status(200);
1193 if let Some(len) = content_length {
1194 parts
1195 .headers
1196 .insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
1197 }
1198 Self {
1199 parts,
1200 body: WebResourceBody::Path(path),
1201 }
1202 }
1203
1204 pub fn bytes(data: impl Into<Vec<u8>>) -> Self {
1206 let data = data.into();
1207 let len = data.len();
1208 let mut parts = Self::response_parts_with_status(200);
1209 parts
1210 .headers
1211 .insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
1212 Self {
1213 parts,
1214 body: WebResourceBody::Bytes(data),
1215 }
1216 }
1217
1218 pub fn stream(reader: SystemPipeReader) -> Self {
1220 let parts = Self::response_parts_with_status(200);
1221 Self {
1222 parts,
1223 body: WebResourceBody::Pipe(reader),
1224 }
1225 }
1226
1227 pub fn mime(mut self, content_type: &str) -> Self {
1229 if let Ok(value) = http::HeaderValue::from_str(content_type) {
1230 self.parts.headers.insert(http::header::CONTENT_TYPE, value);
1231 }
1232 self
1233 }
1234
1235 pub fn status(mut self, code: u16) -> Self {
1237 self.parts.status = http::StatusCode::from_u16(code).unwrap_or(self.parts.status);
1238 self
1239 }
1240
1241 pub fn header(mut self, name: &str, value: &str) -> Self {
1243 if let (Ok(header_name), Ok(header_value)) = (
1244 name.parse::<http::header::HeaderName>(),
1245 http::HeaderValue::from_str(value),
1246 ) {
1247 self.parts.headers.insert(header_name, header_value);
1248 }
1249 self
1250 }
1251
1252 pub fn cors(self) -> Self {
1254 self.header("access-control-allow-origin", "null")
1255 }
1256}
1257
1258#[cfg(test)]
1259mod tests {
1260 use super::*;
1261
1262 #[test]
1263 fn contextual_scheme_request_preserves_platform_context() {
1264 let request = http::Request::builder()
1265 .uri("lx://app/index.html")
1266 .body(vec![1, 2, 3])
1267 .unwrap();
1268 let request = ContextualSchemeRequest::new(
1269 request,
1270 NativeWebViewId::new(91),
1271 SchemeRequestFrame::TopLevelDocument,
1272 );
1273
1274 assert_eq!(request.native_view(), NativeWebViewId::new(91));
1275 assert_eq!(request.frame(), SchemeRequestFrame::TopLevelDocument);
1276 assert_eq!(request.request().uri(), "lx://app/index.html");
1277 assert_eq!(request.into_request().into_body(), vec![1, 2, 3]);
1278 }
1279}