servo/
webview_delegate.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::path::PathBuf;
6
7use base::generic_channel::{GenericSender, SendResult};
8use base::id::PipelineId;
9use constellation_traits::EmbedderToConstellationMessage;
10use embedder_traits::{
11    AllowOrDeny, AuthenticationResponse, ContextMenuResult, Cursor, EmbedderControlId,
12    FilterPattern, FormControlResponse, GamepadHapticEffectType, InputEventId, InputEventResult,
13    InputMethodType, LoadStatus, MediaSessionEvent, Notification, PermissionFeature, RgbColor,
14    ScreenGeometry, SelectElementOptionOrOptgroup, SimpleDialog, TraversalId, WebResourceRequest,
15    WebResourceResponse, WebResourceResponseMsg,
16};
17use ipc_channel::ipc::IpcSender;
18use serde::Serialize;
19use url::Url;
20use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
21
22use crate::responders::ServoErrorSender;
23use crate::{ConstellationProxy, WebView};
24
25/// A request to navigate a [`WebView`] or one of its inner frames. This can be handled
26/// asynchronously. If not handled, the request will automatically be allowed.
27pub struct NavigationRequest {
28    pub url: Url,
29    pub(crate) pipeline_id: PipelineId,
30    pub(crate) constellation_proxy: ConstellationProxy,
31    pub(crate) response_sent: bool,
32}
33
34impl NavigationRequest {
35    pub fn allow(mut self) {
36        self.constellation_proxy
37            .send(EmbedderToConstellationMessage::AllowNavigationResponse(
38                self.pipeline_id,
39                true,
40            ));
41        self.response_sent = true;
42    }
43
44    pub fn deny(mut self) {
45        self.constellation_proxy
46            .send(EmbedderToConstellationMessage::AllowNavigationResponse(
47                self.pipeline_id,
48                false,
49            ));
50        self.response_sent = true;
51    }
52}
53
54impl Drop for NavigationRequest {
55    fn drop(&mut self) {
56        if !self.response_sent {
57            self.constellation_proxy
58                .send(EmbedderToConstellationMessage::AllowNavigationResponse(
59                    self.pipeline_id,
60                    true,
61                ));
62        }
63    }
64}
65
66/// Sends a response over an IPC channel, or a default response on [`Drop`] if no response was sent.
67pub(crate) struct IpcResponder<T: Serialize> {
68    response_sender: GenericSender<T>,
69    response_sent: bool,
70    /// Always present, except when taken by [`Drop`].
71    default_response: Option<T>,
72}
73
74impl<T: Serialize> IpcResponder<T> {
75    pub(crate) fn new(response_sender: GenericSender<T>, default_response: T) -> Self {
76        Self {
77            response_sender,
78            response_sent: false,
79            default_response: Some(default_response),
80        }
81    }
82
83    pub(crate) fn send(&mut self, response: T) -> SendResult {
84        let result = self.response_sender.send(response);
85        self.response_sent = true;
86        result
87    }
88
89    pub(crate) fn into_inner(self) -> GenericSender<T> {
90        self.response_sender.clone()
91    }
92}
93
94impl<T: Serialize> Drop for IpcResponder<T> {
95    fn drop(&mut self) {
96        if !self.response_sent {
97            let response = self
98                .default_response
99                .take()
100                .expect("Guaranteed by inherent impl");
101            // Don’t notify embedder about send errors for the default response,
102            // since they didn’t send anything and probably don’t care.
103            let _ = self.response_sender.send(response);
104        }
105    }
106}
107
108/// A permissions request for a [`WebView`] The embedder should allow or deny the request,
109/// either by reading a cached value or querying the user for permission via the user
110/// interface.
111pub struct PermissionRequest {
112    pub(crate) requested_feature: PermissionFeature,
113    pub(crate) allow_deny_request: AllowOrDenyRequest,
114}
115
116impl PermissionRequest {
117    pub fn feature(&self) -> PermissionFeature {
118        self.requested_feature
119    }
120
121    pub fn allow(self) {
122        self.allow_deny_request.allow();
123    }
124
125    pub fn deny(self) {
126        self.allow_deny_request.deny();
127    }
128}
129
130pub struct AllowOrDenyRequest(IpcResponder<AllowOrDeny>, ServoErrorSender);
131
132impl AllowOrDenyRequest {
133    pub(crate) fn new(
134        response_sender: GenericSender<AllowOrDeny>,
135        default_response: AllowOrDeny,
136        error_sender: ServoErrorSender,
137    ) -> Self {
138        Self(
139            IpcResponder::new(response_sender, default_response),
140            error_sender,
141        )
142    }
143
144    pub fn allow(mut self) {
145        if let Err(error) = self.0.send(AllowOrDeny::Allow) {
146            self.1.raise_response_send_error(error);
147        }
148    }
149
150    pub fn deny(mut self) {
151        if let Err(error) = self.0.send(AllowOrDeny::Deny) {
152            self.1.raise_response_send_error(error);
153        }
154    }
155}
156
157/// A request to authenticate a [`WebView`] navigation. Embedders may choose to prompt
158/// the user to enter credentials or simply ignore this request (in which case credentials
159/// will not be used).
160pub struct AuthenticationRequest {
161    pub(crate) url: Url,
162    pub(crate) for_proxy: bool,
163    pub(crate) responder: IpcResponder<Option<AuthenticationResponse>>,
164    pub(crate) error_sender: ServoErrorSender,
165}
166
167impl AuthenticationRequest {
168    pub(crate) fn new(
169        url: Url,
170        for_proxy: bool,
171        response_sender: GenericSender<Option<AuthenticationResponse>>,
172        error_sender: ServoErrorSender,
173    ) -> Self {
174        Self {
175            url,
176            for_proxy,
177            responder: IpcResponder::new(response_sender, None),
178            error_sender,
179        }
180    }
181
182    /// The URL of the request that triggered this authentication.
183    pub fn url(&self) -> &Url {
184        &self.url
185    }
186    /// Whether or not this authentication request is associated with a proxy server authentication.
187    pub fn for_proxy(&self) -> bool {
188        self.for_proxy
189    }
190    /// Respond to the [`AuthenticationRequest`] with the given username and password.
191    pub fn authenticate(mut self, username: String, password: String) {
192        if let Err(error) = self
193            .responder
194            .send(Some(AuthenticationResponse { username, password }))
195        {
196            self.error_sender.raise_response_send_error(error);
197        }
198    }
199}
200
201/// Information related to the loading of a web resource. These are created for all HTTP requests.
202/// The client may choose to intercept the load of web resources and send an alternate response
203/// by calling [`WebResourceLoad::intercept`].
204pub struct WebResourceLoad {
205    pub request: WebResourceRequest,
206    pub(crate) responder: IpcResponder<WebResourceResponseMsg>,
207    pub(crate) error_sender: ServoErrorSender,
208}
209
210impl WebResourceLoad {
211    pub(crate) fn new(
212        web_resource_request: WebResourceRequest,
213        response_sender: GenericSender<WebResourceResponseMsg>,
214        error_sender: ServoErrorSender,
215    ) -> Self {
216        Self {
217            request: web_resource_request,
218            responder: IpcResponder::new(response_sender, WebResourceResponseMsg::DoNotIntercept),
219            error_sender,
220        }
221    }
222
223    /// The [`WebResourceRequest`] associated with this [`WebResourceLoad`].
224    pub fn request(&self) -> &WebResourceRequest {
225        &self.request
226    }
227    /// Intercept this [`WebResourceLoad`] and control the response via the returned
228    /// [`InterceptedWebResourceLoad`].
229    pub fn intercept(mut self, response: WebResourceResponse) -> InterceptedWebResourceLoad {
230        if let Err(error) = self.responder.send(WebResourceResponseMsg::Start(response)) {
231            self.error_sender.raise_response_send_error(error);
232        }
233        InterceptedWebResourceLoad {
234            request: self.request.clone(),
235            response_sender: self.responder.into_inner(),
236            finished: false,
237            error_sender: self.error_sender,
238        }
239    }
240}
241
242/// An intercepted web resource load. This struct allows the client to send an alternative response
243/// after calling [`WebResourceLoad::intercept`]. In order to send chunks of body data, the client
244/// must call [`InterceptedWebResourceLoad::send_body_data`]. When the interception is complete, the client
245/// should call [`InterceptedWebResourceLoad::finish`]. If neither `finish()` or `cancel()` are called,
246/// this interception will automatically be finished when dropped.
247pub struct InterceptedWebResourceLoad {
248    pub request: WebResourceRequest,
249    pub(crate) response_sender: GenericSender<WebResourceResponseMsg>,
250    pub(crate) finished: bool,
251    pub(crate) error_sender: ServoErrorSender,
252}
253
254impl InterceptedWebResourceLoad {
255    /// Send a chunk of response body data. It's possible to make subsequent calls to
256    /// this method when streaming body data.
257    pub fn send_body_data(&self, data: Vec<u8>) {
258        if let Err(error) = self
259            .response_sender
260            .send(WebResourceResponseMsg::SendBodyData(data))
261        {
262            self.error_sender.raise_response_send_error(error);
263        }
264    }
265    /// Finish this [`InterceptedWebResourceLoad`] and complete the response.
266    pub fn finish(mut self) {
267        if let Err(error) = self
268            .response_sender
269            .send(WebResourceResponseMsg::FinishLoad)
270        {
271            self.error_sender.raise_response_send_error(error);
272        }
273        self.finished = true;
274    }
275    /// Cancel this [`InterceptedWebResourceLoad`], which will trigger a network error.
276    pub fn cancel(mut self) {
277        if let Err(error) = self
278            .response_sender
279            .send(WebResourceResponseMsg::CancelLoad)
280        {
281            self.error_sender.raise_response_send_error(error);
282        }
283        self.finished = true;
284    }
285}
286
287impl Drop for InterceptedWebResourceLoad {
288    fn drop(&mut self) {
289        if !self.finished {
290            if let Err(error) = self
291                .response_sender
292                .send(WebResourceResponseMsg::FinishLoad)
293            {
294                self.error_sender.raise_response_send_error(error);
295            }
296        }
297    }
298}
299
300/// The controls of an interactive form element.
301pub enum FormControl {
302    /// The picker of a `<select>` element.
303    SelectElement(SelectElement),
304    /// The picker of a `<input type=color>` element.
305    ColorPicker(ColorPicker),
306}
307
308impl FormControl {
309    pub fn id(&self) -> EmbedderControlId {
310        match self {
311            FormControl::SelectElement(select_element) => select_element.id,
312            FormControl::ColorPicker(color_picker) => color_picker.id,
313        }
314    }
315}
316
317/// Represents a dialog triggered by clicking a `<select>` element.
318pub struct SelectElement {
319    pub(crate) id: EmbedderControlId,
320    pub(crate) options: Vec<SelectElementOptionOrOptgroup>,
321    pub(crate) selected_option: Option<usize>,
322    pub(crate) position: DeviceIntRect,
323    pub(crate) constellation_proxy: ConstellationProxy,
324    pub(crate) response_sent: bool,
325}
326
327impl SelectElement {
328    /// Return the area occupied by the `<select>` element that triggered the prompt.
329    ///
330    /// The embedder should use this value to position the prompt that is shown to the user.
331    pub fn position(&self) -> DeviceIntRect {
332        self.position
333    }
334
335    /// Consecutive `<option>` elements outside of an `<optgroup>` will be combined
336    /// into a single anonymous group, whose [`label`](SelectElementGroup::label) is `None`.
337    pub fn options(&self) -> &[SelectElementOptionOrOptgroup] {
338        &self.options
339    }
340
341    /// Mark a single option as selected.
342    ///
343    /// If there is already a selected option and the `<select>` element does not
344    /// support selecting multiple options, then the previous option will be unselected.
345    pub fn select(&mut self, id: Option<usize>) {
346        self.selected_option = id;
347    }
348
349    pub fn selected_option(&self) -> Option<usize> {
350        self.selected_option
351    }
352
353    /// Resolve the prompt with the options that have been selected by calling [select] previously.
354    pub fn submit(mut self) {
355        self.response_sent = true;
356        self.constellation_proxy
357            .send(EmbedderToConstellationMessage::EmbedderControlResponse(
358                self.id,
359                FormControlResponse::SelectElement(self.selected_option()),
360            ));
361    }
362}
363
364impl Drop for SelectElement {
365    fn drop(&mut self) {
366        if !self.response_sent {
367            self.constellation_proxy
368                .send(EmbedderToConstellationMessage::EmbedderControlResponse(
369                    self.id,
370                    FormControlResponse::SelectElement(self.selected_option()),
371                ));
372        }
373    }
374}
375
376/// Represents a dialog triggered by clicking a `<input type=color>` element.
377pub struct ColorPicker {
378    pub(crate) id: EmbedderControlId,
379    pub(crate) current_color: Option<RgbColor>,
380    pub(crate) position: DeviceIntRect,
381    pub(crate) constellation_proxy: ConstellationProxy,
382    pub(crate) response_sent: bool,
383}
384
385impl ColorPicker {
386    /// Get the area occupied by the `<input>` element that triggered the prompt.
387    ///
388    /// The embedder should use this value to position the prompt that is shown to the user.
389    pub fn position(&self) -> DeviceIntRect {
390        self.position
391    }
392
393    /// Get the currently selected color for this [`ColorPicker`]. This is initially the selected color
394    /// before the picker is opened.
395    pub fn current_color(&self) -> Option<RgbColor> {
396        self.current_color
397    }
398
399    pub fn select(&mut self, color: Option<RgbColor>) {
400        self.current_color = color;
401    }
402
403    /// Resolve the prompt with the options that have been selected by calling [select] previously.
404    pub fn submit(mut self) {
405        self.response_sent = true;
406        self.constellation_proxy
407            .send(EmbedderToConstellationMessage::EmbedderControlResponse(
408                self.id,
409                FormControlResponse::ColorPicker(self.current_color),
410            ));
411    }
412}
413
414impl Drop for ColorPicker {
415    fn drop(&mut self) {
416        if !self.response_sent {
417            self.constellation_proxy
418                .send(EmbedderToConstellationMessage::EmbedderControlResponse(
419                    self.id,
420                    FormControlResponse::ColorPicker(self.current_color),
421                ));
422        }
423    }
424}
425
426pub trait WebViewDelegate {
427    /// Get the [`ScreenGeometry`] for this [`WebView`]. If this is unimplemented or returns `None`
428    /// the screen will have the size of the [`WebView`]'s `RenderingContext` and `WebView` will be
429    /// considered to be positioned at the screen's origin.
430    fn screen_geometry(&self, _webview: WebView) -> Option<ScreenGeometry> {
431        None
432    }
433    /// The URL of the currently loaded page in this [`WebView`] has changed. The new
434    /// URL can accessed via [`WebView::url`].
435    fn notify_url_changed(&self, _webview: WebView, _url: Url) {}
436    /// The page title of the currently loaded page in this [`WebView`] has changed. The new
437    /// title can accessed via [`WebView::page_title`].
438    fn notify_page_title_changed(&self, _webview: WebView, _title: Option<String>) {}
439    /// The status text of the currently loaded page in this [`WebView`] has changed. The new
440    /// status text can accessed via [`WebView::status_text`].
441    fn notify_status_text_changed(&self, _webview: WebView, _status: Option<String>) {}
442    /// This [`WebView`] has either become focused or lost focus. Whether or not the
443    /// [`WebView`] is focused can be accessed via [`WebView::focused`].
444    fn notify_focus_changed(&self, _webview: WebView, _focused: bool) {}
445    /// This [`WebView`] has either started to animate or stopped animating. When a
446    /// [`WebView`] is animating, it is up to the embedding application ensure that
447    /// `Servo::spin_event_loop` is called at regular intervals in order to update the
448    /// painted contents of the [`WebView`].
449    fn notify_animating_changed(&self, _webview: WebView, _animating: bool) {}
450    /// The `LoadStatus` of the currently loading or loaded page in this [`WebView`] has changed. The new
451    /// status can accessed via [`WebView::load_status`].
452    fn notify_load_status_changed(&self, _webview: WebView, _status: LoadStatus) {}
453    /// The [`Cursor`] of the currently loaded page in this [`WebView`] has changed. The new
454    /// cursor can accessed via [`WebView::cursor`].
455    fn notify_cursor_changed(&self, _webview: WebView, _: Cursor) {}
456    /// The favicon of the currently loaded page in this [`WebView`] has changed. The new
457    /// favicon [`Image`] can accessed via [`WebView::favicon`].
458    fn notify_favicon_changed(&self, _webview: WebView) {}
459    /// Notify the embedder that it needs to present a new frame.
460    fn notify_new_frame_ready(&self, _webview: WebView) {}
461    /// The navigation history of this [`WebView`] has changed. The navigation history is represented
462    /// as a `Vec<Url>` and `_current` denotes the current index in the history. New navigations,
463    /// back navigation, and forward navigation modify this index.
464    fn notify_history_changed(&self, _webview: WebView, _entries: Vec<Url>, _current: usize) {}
465    /// A history traversal operation is complete.
466    fn notify_traversal_complete(&self, _webview: WebView, _: TraversalId) {}
467    /// Page content has closed this [`WebView`] via `window.close()`. It's the embedder's
468    /// responsibility to remove the [`WebView`] from the interface when this notification
469    /// occurs.
470    fn notify_closed(&self, _webview: WebView) {}
471
472    /// An input event passed to this [`WebView`] via [`WebView::notify_input_event`] has been handled
473    /// by Servo. This allows post-procesing of input events, such as chaining up unhandled events
474    /// to parent UI elements.
475    fn notify_input_event_handled(&self, _webview: WebView, _: InputEventId, _: InputEventResult) {}
476    /// A pipeline in the webview panicked. First string is the reason, second one is the backtrace.
477    fn notify_crashed(&self, _webview: WebView, _reason: String, _backtrace: Option<String>) {}
478    /// Notifies the embedder about media session events
479    /// (i.e. when there is metadata for the active media session, playback state changes...).
480    fn notify_media_session_event(&self, _webview: WebView, _event: MediaSessionEvent) {}
481    /// A notification that the [`WebView`] has entered or exited fullscreen mode. This is an
482    /// opportunity for the embedder to transition the containing window into or out of fullscreen
483    /// mode and to show or hide extra UI elements. Regardless of how the notification is handled,
484    /// the page will enter or leave fullscreen state internally according to the [Fullscreen
485    /// API](https://fullscreen.spec.whatwg.org/).
486    fn notify_fullscreen_state_changed(&self, _webview: WebView, _: bool) {}
487
488    /// Whether or not to allow a [`WebView`] to load a URL in its main frame or one of its
489    /// nested `<iframe>`s. [`NavigationRequest`]s are accepted by default.
490    fn request_navigation(&self, _webview: WebView, _navigation_request: NavigationRequest) {}
491    /// Whether or not to allow a [`WebView`]  to unload a `Document` in its main frame or one
492    /// of its nested `<iframe>`s. By default, unloads are allowed.
493    fn request_unload(&self, _webview: WebView, _unload_request: AllowOrDenyRequest) {}
494    /// Move the window to a point.
495    fn request_move_to(&self, _webview: WebView, _: DeviceIntPoint) {}
496    /// Try to resize the window that contains this [`WebView`] to the provided outer
497    /// size. These resize requests can come from page content. Servo will ensure that the
498    /// values are greater than zero, but it is up to the embedder to limit the maximum
499    /// size. For instance, a reasonable limitation might be that the final size is no
500    /// larger than the screen size.
501    fn request_resize_to(&self, _webview: WebView, _requested_outer_size: DeviceIntSize) {}
502    /// Whether or not to allow script to open a new `WebView`. If not handled by the
503    /// embedder, these requests are automatically denied.
504    fn request_open_auxiliary_webview(&self, _parent_webview: WebView) -> Option<WebView> {
505        None
506    }
507
508    /// Content in a [`WebView`] is requesting permission to access a feature requiring
509    /// permission from the user. The embedder should allow or deny the request, either by
510    /// reading a cached value or querying the user for permission via the user interface.
511    fn request_permission(&self, _webview: WebView, _: PermissionRequest) {}
512
513    fn request_authentication(
514        &self,
515        _webview: WebView,
516        _authentication_request: AuthenticationRequest,
517    ) {
518    }
519
520    /// Show the user a [simple dialog](https://html.spec.whatwg.org/multipage/#simple-dialogs) (`alert()`, `confirm()`,
521    /// or `prompt()`). Since their messages are controlled by web content, they should be presented to the user in a
522    /// way that makes them impossible to mistake for browser UI.
523    /// TODO: This API needs to be reworked to match the new model of how responses are sent.
524    fn show_simple_dialog(&self, _webview: WebView, dialog: SimpleDialog) {
525        // Return the DOM-specified default value for when we **cannot show simple dialogs**.
526        let _ = match dialog {
527            SimpleDialog::Alert {
528                response_sender, ..
529            } => response_sender.send(Default::default()),
530            SimpleDialog::Confirm {
531                response_sender, ..
532            } => response_sender.send(Default::default()),
533            SimpleDialog::Prompt {
534                response_sender, ..
535            } => response_sender.send(Default::default()),
536        };
537    }
538
539    /// Show a context menu to the user
540    fn show_context_menu(
541        &self,
542        _webview: WebView,
543        result_sender: GenericSender<ContextMenuResult>,
544        _: Option<String>,
545        _: Vec<String>,
546    ) {
547        let _ = result_sender.send(ContextMenuResult::Ignored);
548    }
549
550    /// Open dialog to select bluetooth device.
551    /// TODO: This API needs to be reworked to match the new model of how responses are sent.
552    fn show_bluetooth_device_dialog(
553        &self,
554        _webview: WebView,
555        _: Vec<String>,
556        response_sender: GenericSender<Option<String>>,
557    ) {
558        let _ = response_sender.send(None);
559    }
560
561    /// Open file dialog to select files. Set boolean flag to true allows to select multiple files.
562    fn show_file_selection_dialog(
563        &self,
564        _webview: WebView,
565        _filter_pattern: Vec<FilterPattern>,
566        _allow_select_mutiple: bool,
567        response_sender: GenericSender<Option<Vec<PathBuf>>>,
568    ) {
569        let _ = response_sender.send(None);
570    }
571
572    /// Request to present an IME to the user when an editable element is focused.
573    /// If `type` is [`InputMethodType::Text`], then the `text` parameter specifies
574    /// the pre-existing text content and the zero-based index into the string
575    /// of the insertion point.
576    fn show_ime(
577        &self,
578        _webview: WebView,
579        _type: InputMethodType,
580        _text: Option<(String, i32)>,
581        _multiline: bool,
582        _position: DeviceIntRect,
583    ) {
584    }
585
586    /// Request to hide the IME when the editable element is blurred.
587    fn hide_ime(&self, _webview: WebView) {}
588
589    /// Request that the embedder show UI elements for form controls that are not integrated
590    /// into page content, such as dropdowns for `<select>` elements.
591    fn show_form_control(&self, _webview: WebView, _form_control: FormControl) {}
592
593    /// Request to play a haptic effect on a connected gamepad.
594    fn play_gamepad_haptic_effect(
595        &self,
596        _webview: WebView,
597        _: usize,
598        _: GamepadHapticEffectType,
599        _: IpcSender<bool>,
600    ) {
601    }
602    /// Request to stop a haptic effect on a connected gamepad.
603    fn stop_gamepad_haptic_effect(&self, _webview: WebView, _: usize, _: IpcSender<bool>) {}
604
605    /// Triggered when this [`WebView`] will load a web (HTTP/HTTPS) resource. The load may be
606    /// intercepted and alternate contents can be loaded by the client by calling
607    /// [`WebResourceLoad::intercept`]. If not handled, the load will continue as normal.
608    ///
609    /// Note: This delegate method is called for all resource loads associated with a [`WebView`].
610    /// For loads not associated with a [`WebView`], such as those for service workers, Servo
611    /// will call [`crate::ServoDelegate::load_web_resource`].
612    fn load_web_resource(&self, _webview: WebView, _load: WebResourceLoad) {}
613
614    /// Request to display a notification.
615    fn show_notification(&self, _webview: WebView, _notification: Notification) {}
616}
617
618pub(crate) struct DefaultWebViewDelegate;
619impl WebViewDelegate for DefaultWebViewDelegate {}
620
621#[test]
622fn test_allow_deny_request() {
623    use base::generic_channel;
624
625    use crate::ServoErrorChannel;
626
627    for default_response in [AllowOrDeny::Allow, AllowOrDeny::Deny] {
628        // Explicit allow yields allow and nothing else
629        let errors = ServoErrorChannel::default();
630        let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
631        let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
632        request.allow();
633        assert_eq!(receiver.try_recv().ok(), Some(AllowOrDeny::Allow));
634        assert_eq!(receiver.try_recv().ok(), None);
635        assert!(errors.try_recv().is_none());
636
637        // Explicit deny yields deny and nothing else
638        let errors = ServoErrorChannel::default();
639        let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
640        let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
641        request.deny();
642        assert_eq!(receiver.try_recv().ok(), Some(AllowOrDeny::Deny));
643        assert_eq!(receiver.try_recv().ok(), None);
644        assert!(errors.try_recv().is_none());
645
646        // No response yields default response and nothing else
647        let errors = ServoErrorChannel::default();
648        let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
649        let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
650        drop(request);
651        assert_eq!(receiver.try_recv().ok(), Some(default_response));
652        assert_eq!(receiver.try_recv().ok(), None);
653        assert!(errors.try_recv().is_none());
654
655        // Explicit allow when receiver disconnected yields error
656        let errors = ServoErrorChannel::default();
657        let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
658        let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
659        drop(receiver);
660        request.allow();
661        assert!(errors.try_recv().is_some());
662
663        // Explicit deny when receiver disconnected yields error
664        let errors = ServoErrorChannel::default();
665        let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
666        let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
667        drop(receiver);
668        request.deny();
669        assert!(errors.try_recv().is_some());
670
671        // No response when receiver disconnected yields no error
672        let errors = ServoErrorChannel::default();
673        let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
674        let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
675        drop(receiver);
676        drop(request);
677        assert!(errors.try_recv().is_none());
678    }
679}
680
681#[test]
682fn test_authentication_request() {
683    use base::generic_channel;
684
685    use crate::ServoErrorChannel;
686
687    let url = Url::parse("https://example.com").expect("Guaranteed by argument");
688
689    // Explicit response yields that response and nothing else
690    let errors = ServoErrorChannel::default();
691    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
692    let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
693    request.authenticate("diffie".to_owned(), "hunter2".to_owned());
694    assert_eq!(
695        receiver.try_recv().ok(),
696        Some(Some(AuthenticationResponse {
697            username: "diffie".to_owned(),
698            password: "hunter2".to_owned(),
699        }))
700    );
701    assert_eq!(receiver.try_recv().ok(), None);
702    assert!(errors.try_recv().is_none());
703
704    // No response yields None response and nothing else
705    let errors = ServoErrorChannel::default();
706    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
707    let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
708    drop(request);
709    assert_eq!(receiver.try_recv().ok(), Some(None));
710    assert_eq!(receiver.try_recv().ok(), None);
711    assert!(errors.try_recv().is_none());
712
713    // Explicit response when receiver disconnected yields error
714    let errors = ServoErrorChannel::default();
715    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
716    let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
717    drop(receiver);
718    request.authenticate("diffie".to_owned(), "hunter2".to_owned());
719    assert!(errors.try_recv().is_some());
720
721    // No response when receiver disconnected yields no error
722    let errors = ServoErrorChannel::default();
723    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
724    let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
725    drop(receiver);
726    drop(request);
727    assert!(errors.try_recv().is_none());
728}
729
730#[test]
731fn test_web_resource_load() {
732    use base::generic_channel;
733    use http::{HeaderMap, Method, StatusCode};
734
735    use crate::ServoErrorChannel;
736
737    let web_resource_request = || WebResourceRequest {
738        method: Method::GET,
739        headers: HeaderMap::default(),
740        url: Url::parse("https://example.com").expect("Guaranteed by argument"),
741        is_for_main_frame: false,
742        is_redirect: false,
743    };
744    let web_resource_response = || {
745        WebResourceResponse::new(Url::parse("https://diffie.test").expect("Guaranteed by argument"))
746            .status_code(StatusCode::IM_A_TEAPOT)
747    };
748
749    // Explicit intercept with explicit cancel yields Start and Cancel and nothing else
750    let errors = ServoErrorChannel::default();
751    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
752    let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
753    request.intercept(web_resource_response()).cancel();
754    assert!(matches!(
755        receiver.try_recv(),
756        Ok(WebResourceResponseMsg::Start(_))
757    ));
758    assert!(matches!(
759        receiver.try_recv(),
760        Ok(WebResourceResponseMsg::CancelLoad)
761    ));
762    assert!(matches!(receiver.try_recv(), Err(_)));
763    assert!(errors.try_recv().is_none());
764
765    // Explicit intercept with no further action yields Start and FinishLoad and nothing else
766    let errors = ServoErrorChannel::default();
767    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
768    let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
769    drop(request.intercept(web_resource_response()));
770    assert!(matches!(
771        receiver.try_recv(),
772        Ok(WebResourceResponseMsg::Start(_))
773    ));
774    assert!(matches!(
775        receiver.try_recv(),
776        Ok(WebResourceResponseMsg::FinishLoad)
777    ));
778    assert!(matches!(receiver.try_recv(), Err(_)));
779    assert!(errors.try_recv().is_none());
780
781    // No response yields DoNotIntercept and nothing else
782    let errors = ServoErrorChannel::default();
783    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
784    let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
785    drop(request);
786    assert!(matches!(
787        receiver.try_recv(),
788        Ok(WebResourceResponseMsg::DoNotIntercept)
789    ));
790    assert!(matches!(receiver.try_recv(), Err(_)));
791    assert!(errors.try_recv().is_none());
792
793    // Explicit intercept with explicit cancel when receiver disconnected yields error
794    let errors = ServoErrorChannel::default();
795    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
796    let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
797    drop(receiver);
798    request.intercept(web_resource_response()).cancel();
799    assert!(errors.try_recv().is_some());
800
801    // Explicit intercept with no further action when receiver disconnected yields error
802    let errors = ServoErrorChannel::default();
803    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
804    let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
805    drop(receiver);
806    drop(request.intercept(web_resource_response()));
807    assert!(errors.try_recv().is_some());
808
809    // No response when receiver disconnected yields no error
810    let errors = ServoErrorChannel::default();
811    let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel");
812    let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
813    drop(receiver);
814    drop(request);
815    assert!(errors.try_recv().is_none());
816}