Skip to main content

windows_webview/
protocol.rs

1use super::*;
2use std::cell::RefCell;
3use windows_core::implement_decl;
4
5/// Filter context matching every resource type (`COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL`).
6pub(crate) const WEB_RESOURCE_CONTEXT_ALL: COREWEBVIEW2_WEB_RESOURCE_CONTEXT = 0;
7
8/// Request sources matching every kind (`COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL`),
9/// so a filter also intercepts requests from iframes and workers.
10const WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL: COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS =
11    u32::MAX;
12
13/// Registers a filter for every request source when the runtime supports it.
14pub(crate) unsafe fn add_requested_filter(
15    webview: &ICoreWebView2,
16    uri: impl Param<PCWSTR>,
17) -> Result<()> {
18    unsafe {
19        match webview.cast::<ICoreWebView2_22>() {
20            Ok(webview) => webview
21                .AddWebResourceRequestedFilterWithRequestSourceKinds(
22                    uri,
23                    WEB_RESOURCE_CONTEXT_ALL,
24                    WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL,
25                )
26                .ok(),
27            Err(_) => webview
28                .AddWebResourceRequestedFilter(uri, WEB_RESOURCE_CONTEXT_ALL)
29                .ok(),
30        }
31    }
32}
33
34/// Removes a filter registered with [`add_requested_filter`].
35pub(crate) unsafe fn remove_requested_filter(webview: &ICoreWebView2, uri: impl Param<PCWSTR>) {
36    let _ = unsafe {
37        match webview.cast::<ICoreWebView2_22>() {
38            Ok(webview) => webview.RemoveWebResourceRequestedFilterWithRequestSourceKinds(
39                uri,
40                WEB_RESOURCE_CONTEXT_ALL,
41                WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL,
42            ),
43            Err(_) => webview.RemoveWebResourceRequestedFilter(uri, WEB_RESOURCE_CONTEXT_ALL),
44        }
45    };
46}
47
48/// A resource request intercepted by [`WebView::on_web_resource_requested`],
49/// exposing the requested URI, HTTP method, and headers.
50pub struct WebResourceRequest(pub(crate) ICoreWebView2WebResourceRequest);
51
52impl WebResourceRequest {
53    /// Returns the absolute URI of the requested resource.
54    pub fn uri(&self) -> String {
55        unsafe { string::take_result(self.0.Uri()) }
56    }
57
58    /// Returns the HTTP method of the request, such as `GET` or `POST`.
59    pub fn method(&self) -> String {
60        unsafe { string::take_result(self.0.Method()) }
61    }
62
63    /// Returns the request headers as `(name, value)` pairs.
64    pub fn headers(&self) -> Vec<(String, String)> {
65        unsafe { self.collect_headers() }.unwrap_or_default()
66    }
67
68    unsafe fn collect_headers(&self) -> Result<Vec<(String, String)>> {
69        let iterator = unsafe { self.0.Headers()?.GetIterator()? };
70        let mut headers = Vec::new();
71
72        while unsafe { iterator.HasCurrentHeader()? }.as_bool() {
73            let mut name: LPWSTR = std::ptr::null_mut();
74            let mut value: LPWSTR = std::ptr::null_mut();
75            unsafe { iterator.GetCurrentHeader(&mut name, &mut value).ok()? };
76            headers.push((unsafe { string::take(name) }, unsafe {
77                string::take(value)
78            }));
79            let _ = unsafe { iterator.MoveNext()? };
80        }
81
82        Ok(headers)
83    }
84}
85
86/// The response returned from a [`WebView::on_web_resource_requested`] handler
87/// to fulfill a request from memory. Defaults to `200 OK` with no headers; add a
88/// [`content_type`](Self::content_type) so the page interprets the body
89/// correctly.
90pub struct WebResourceResponse {
91    body: Vec<u8>,
92    status_code: i32,
93    reason_phrase: String,
94    headers: Vec<(String, String)>,
95}
96
97impl WebResourceResponse {
98    /// Creates a `200 OK` response with the given body bytes.
99    pub fn new(body: impl Into<Vec<u8>>) -> Self {
100        Self {
101            body: body.into(),
102            status_code: 200,
103            reason_phrase: "OK".to_string(),
104            headers: Vec::new(),
105        }
106    }
107
108    /// Sets the HTTP status code and reason phrase, for example `404` and
109    /// `Not Found`.
110    pub fn status(mut self, code: u16, reason: &str) -> Self {
111        self.status_code = i32::from(code);
112        self.reason_phrase = reason.to_string();
113        self
114    }
115
116    /// Adds a response header.
117    pub fn header(mut self, name: &str, value: &str) -> Self {
118        self.headers.push((name.to_string(), value.to_string()));
119        self
120    }
121
122    /// Sets the `Content-Type` header, for example `text/html`.
123    pub fn content_type(self, value: &str) -> Self {
124        self.header("Content-Type", value)
125    }
126
127    unsafe fn into_response(
128        self,
129        environment: &ICoreWebView2Environment,
130    ) -> Result<ICoreWebView2WebResourceResponse> {
131        let stream = if self.body.is_empty() {
132            None
133        } else {
134            unsafe { SHCreateMemStream(self.body.as_ptr(), self.body.len() as u32) }
135        };
136
137        let mut headers = String::new();
138        for (name, value) in &self.headers {
139            headers.push_str(name);
140            headers.push_str(": ");
141            headers.push_str(value);
142            headers.push_str("\r\n");
143        }
144
145        let reason = HSTRING::from(&self.reason_phrase);
146        let headers = HSTRING::from(&headers);
147
148        unsafe {
149            environment.CreateWebResourceResponse(
150                stream.as_ref(),
151                self.status_code,
152                &reason,
153                &headers,
154            )
155        }
156    }
157}
158
159/// Adapts a Rust request handler to WebView2's COM event interface.
160pub(crate) struct WebResourceRequested {
161    handler: RefCell<Box<dyn FnMut(WebResourceRequest) -> Option<WebResourceResponse>>>,
162    environment: ICoreWebView2Environment,
163}
164
165implement_decl! {
166    impl WebResourceRequested as pub(crate) WebResourceRequested_Impl:
167        [ICoreWebView2WebResourceRequestedEventHandler]
168}
169
170impl WebResourceRequested {
171    pub(crate) fn create<F>(
172        environment: ICoreWebView2Environment,
173        handler: F,
174    ) -> ICoreWebView2WebResourceRequestedEventHandler
175    where
176        F: FnMut(WebResourceRequest) -> Option<WebResourceResponse> + 'static,
177    {
178        Self {
179            handler: RefCell::new(Box::new(handler)),
180            environment,
181        }
182        .into()
183    }
184}
185
186impl ICoreWebView2WebResourceRequestedEventHandler_Impl for WebResourceRequested_Impl {
187    fn Invoke(
188        &self,
189        _sender: Ref<ICoreWebView2>,
190        args: Ref<ICoreWebView2WebResourceRequestedEventArgs>,
191    ) -> Result<()> {
192        let args = args.ok()?;
193        let request = WebResourceRequest(unsafe { args.Request()? });
194
195        if let Some(response) = (*self.handler.borrow_mut())(request) {
196            let response = unsafe { response.into_response(&self.environment)? };
197            unsafe { args.SetResponse(&response).ok()? };
198        }
199
200        Ok(())
201    }
202}