Skip to main content

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;
6use std::rc::Rc;
7
8use embedder_traits::{
9    AlertResponse, AllowOrDeny, AuthenticationResponse, BluetoothDeviceDescription,
10    ConfirmResponse, ConsoleLogLevel, ContextMenuAction, ContextMenuElementInformation,
11    ContextMenuItem, Cursor, EmbedderControlId, EmbedderControlResponse, FilePickerRequest,
12    FilterPattern, InputEventId, InputEventResult, InputMethodType, LoadStatus, MediaSessionEvent,
13    NewWebViewDetails, Notification, PermissionFeature, PromptResponse, RgbColor, ScreenGeometry,
14    SelectElementOptionOrOptgroup, SelectElementRequest, SimpleDialogRequest, TraversalId,
15    WebResourceRequest, WebResourceResponse, WebResourceResponseMsg,
16};
17use paint_api::rendering_context::RenderingContext;
18use servo_base::generic_channel::{GenericCallback, GenericSender, SendError};
19use servo_base::id::PipelineId;
20use servo_constellation_traits::EmbedderToConstellationMessage;
21use tokio::sync::mpsc::UnboundedSender as TokioSender;
22use tokio::sync::oneshot::Sender;
23use url::Url;
24use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
25
26use crate::proxies::ConstellationProxy;
27use crate::responders::{IpcResponder, OneshotSender, ServoErrorSender};
28use crate::{RegisterOrUnregister, Servo, WebView, WebViewBuilder};
29
30/// A request to navigate a [`WebView`] or one of its inner frames. This can be handled
31/// asynchronously. If not handled, the request will automatically be allowed.
32pub struct NavigationRequest {
33    pub url: Url,
34    pub(crate) pipeline_id: PipelineId,
35    pub(crate) constellation_proxy: ConstellationProxy,
36    pub(crate) response_sent: bool,
37}
38
39impl NavigationRequest {
40    /// Allow this navigation request to proceed.
41    pub fn allow(mut self) {
42        self.constellation_proxy
43            .send(EmbedderToConstellationMessage::AllowNavigationResponse(
44                self.pipeline_id,
45                true,
46            ));
47        self.response_sent = true;
48    }
49
50    /// Deny this navigation request, preventing the navigation from proceeding.
51    pub fn deny(mut self) {
52        self.constellation_proxy
53            .send(EmbedderToConstellationMessage::AllowNavigationResponse(
54                self.pipeline_id,
55                false,
56            ));
57        self.response_sent = true;
58    }
59}
60
61impl Drop for NavigationRequest {
62    fn drop(&mut self) {
63        if !self.response_sent {
64            self.constellation_proxy
65                .send(EmbedderToConstellationMessage::AllowNavigationResponse(
66                    self.pipeline_id,
67                    true,
68                ));
69        }
70    }
71}
72
73/// A permissions request for a [`WebView`]. The embedder should allow or deny the request,
74/// either by reading a cached value or querying the user for permission via the user
75/// interface.
76pub struct PermissionRequest {
77    pub(crate) requested_feature: PermissionFeature,
78    pub(crate) allow_deny_request: AllowOrDenyRequest,
79}
80
81impl PermissionRequest {
82    /// Get the [`PermissionFeature`] for which permission is being requested by page content.
83    pub fn feature(&self) -> PermissionFeature {
84        self.requested_feature
85    }
86
87    /// Grant permission to the web content to access the requested feature.
88    pub fn allow(self) {
89        self.allow_deny_request.allow();
90    }
91
92    /// Deny permission to the web content to access the requested feature.
93    pub fn deny(self) {
94        self.allow_deny_request.deny();
95    }
96}
97
98/// A type used for communicating an allow-or-deny decision from the embedder
99/// to Servo. This is used as a part of requests from Servo to the embedder
100/// to perform certain actions and the request can either be allowed or denied
101/// by the embedder.
102pub struct AllowOrDenyRequest(IpcResponder<AllowOrDeny>, ServoErrorSender);
103
104impl AllowOrDenyRequest {
105    pub(crate) fn new(
106        response_sender: GenericSender<AllowOrDeny>,
107        default_response: AllowOrDeny,
108        error_sender: ServoErrorSender,
109    ) -> Self {
110        Self(
111            IpcResponder::new(response_sender, default_response),
112            error_sender,
113        )
114    }
115
116    pub(crate) fn new_from_callback(
117        callback: GenericCallback<AllowOrDeny>,
118        default_response: AllowOrDeny,
119        error_sender: ServoErrorSender,
120    ) -> Self {
121        Self(
122            IpcResponder::new_same_process(Box::new(callback), default_response),
123            error_sender,
124        )
125    }
126
127    /// Allow the action requested by Servo.
128    pub fn allow(mut self) {
129        if let Err(error) = self.0.send(AllowOrDeny::Allow) {
130            self.1.raise_response_send_error(error);
131        }
132    }
133
134    /// Deny the action requested by Servo.
135    pub fn deny(mut self) {
136        if let Err(error) = self.0.send(AllowOrDeny::Deny) {
137            self.1.raise_response_send_error(error);
138        }
139    }
140}
141
142/// A request to register or unregister a custom handler for a scheme.
143/// See <https://html.spec.whatwg.org/multipage/#custom-handlers>
144#[derive(Clone, Debug, Eq, PartialEq)]
145pub struct ProtocolHandlerRegistration {
146    /// The scheme for which the hander is being registered or unregistered.
147    pub scheme: String,
148    /// The URL to navigate to when loading resources for the given 'scheme'.
149    /// The string "%s" in this URL is used as a placeholder. It will be replaced
150    /// by the URL of the resource to be handled.
151    pub url: Url,
152    /// Whether this request is for a new registration or unregistering
153    /// a previously registered handler.
154    pub register_or_unregister: RegisterOrUnregister,
155}
156
157/// A request to let the user chose a Bluetooth device.
158pub struct BluetoothDeviceSelectionRequest {
159    devices: Vec<BluetoothDeviceDescription>,
160    responder: IpcResponder<Option<String>>,
161}
162
163impl BluetoothDeviceSelectionRequest {
164    pub(crate) fn new(
165        devices: Vec<BluetoothDeviceDescription>,
166        responder: GenericSender<Option<String>>,
167    ) -> Self {
168        Self {
169            devices,
170            responder: IpcResponder::new(responder, None),
171        }
172    }
173
174    /// Set the device chosen by the user.
175    pub fn pick_device(mut self, device: &BluetoothDeviceDescription) -> Result<(), SendError> {
176        self.responder.send(Some(device.address.clone()))
177    }
178
179    /// Cancel this request.
180    pub fn cancel(mut self) -> Result<(), SendError> {
181        self.responder.send(None)
182    }
183
184    /// The set of devices that the user can chose from.
185    pub fn devices(&self) -> &Vec<BluetoothDeviceDescription> {
186        &self.devices
187    }
188}
189
190/// A request to authenticate a [`WebView`] navigation. Embedders may choose to prompt
191/// the user to enter credentials or simply ignore this request (in which case credentials
192/// will not be used).
193pub struct AuthenticationRequest {
194    pub(crate) url: Url,
195    pub(crate) for_proxy: bool,
196    pub(crate) responder: IpcResponder<Option<AuthenticationResponse>>,
197    pub(crate) error_sender: ServoErrorSender,
198}
199
200impl AuthenticationRequest {
201    pub(crate) fn new(
202        url: Url,
203        for_proxy: bool,
204        response_sender: Sender<Option<AuthenticationResponse>>,
205        error_sender: ServoErrorSender,
206    ) -> Self {
207        Self {
208            url,
209            for_proxy,
210            responder: IpcResponder::new_same_process(
211                Box::new(OneshotSender::from(response_sender)),
212                None,
213            ),
214            error_sender,
215        }
216    }
217
218    /// The URL of the request that triggered this authentication.
219    pub fn url(&self) -> &Url {
220        &self.url
221    }
222    /// Whether or not this authentication request is associated with a proxy server authentication.
223    pub fn for_proxy(&self) -> bool {
224        self.for_proxy
225    }
226    /// Respond to the [`AuthenticationRequest`] with the given username and password.
227    pub fn authenticate(mut self, username: String, password: String) {
228        if let Err(error) = self
229            .responder
230            .send(Some(AuthenticationResponse { username, password }))
231        {
232            self.error_sender.raise_response_send_error(error);
233        }
234    }
235}
236
237/// Information related to the loading of a web resource. These are created for all HTTP requests.
238/// The client may choose to intercept the load of web resources and send an alternate response
239/// by calling [`WebResourceLoad::intercept`].
240pub struct WebResourceLoad {
241    pub request: WebResourceRequest,
242    pub(crate) responder: IpcResponder<WebResourceResponseMsg>,
243    pub(crate) error_sender: ServoErrorSender,
244}
245
246impl WebResourceLoad {
247    pub(crate) fn new(
248        web_resource_request: WebResourceRequest,
249        response_sender: TokioSender<WebResourceResponseMsg>,
250        error_sender: ServoErrorSender,
251    ) -> Self {
252        Self {
253            request: web_resource_request,
254            responder: IpcResponder::new_same_process(
255                Box::new(response_sender),
256                WebResourceResponseMsg::DoNotIntercept,
257            ),
258            error_sender,
259        }
260    }
261
262    /// The [`WebResourceRequest`] associated with this [`WebResourceLoad`].
263    pub fn request(&self) -> &WebResourceRequest {
264        &self.request
265    }
266
267    /// Intercept this [`WebResourceLoad`] and control the response via the returned
268    /// [`InterceptedWebResourceLoad`].
269    pub fn intercept(mut self, response: WebResourceResponse) -> InterceptedWebResourceLoad {
270        if let Err(error) = self.responder.send(WebResourceResponseMsg::Start(response)) {
271            self.error_sender.raise_response_send_error(error);
272        }
273        InterceptedWebResourceLoad {
274            request: self.request.clone(),
275            response_sender: self.responder,
276            finished: false,
277            error_sender: self.error_sender,
278        }
279    }
280}
281
282/// An intercepted web resource load. This struct allows the client to send an alternative response
283/// after calling [`WebResourceLoad::intercept`]. In order to send chunks of body data, the client
284/// must call [`InterceptedWebResourceLoad::send_body_data`]. When the interception is complete, the client
285/// should call [`InterceptedWebResourceLoad::finish`]. If neither `finish()` or `cancel()` are called,
286/// this interception will automatically be finished when dropped.
287pub struct InterceptedWebResourceLoad {
288    pub request: WebResourceRequest,
289    pub(crate) response_sender: IpcResponder<WebResourceResponseMsg>,
290    pub(crate) finished: bool,
291    pub(crate) error_sender: ServoErrorSender,
292}
293
294impl InterceptedWebResourceLoad {
295    /// Send a chunk of response body data. It's possible to make subsequent calls to
296    /// this method when streaming body data.
297    pub fn send_body_data(&mut self, data: Vec<u8>) {
298        if let Err(error) = self
299            .response_sender
300            .send(WebResourceResponseMsg::SendBodyData(data))
301        {
302            self.error_sender.raise_response_send_error(error);
303        }
304    }
305    /// Finish this [`InterceptedWebResourceLoad`] and complete the response.
306    pub fn finish(mut self) {
307        if let Err(error) = self
308            .response_sender
309            .send(WebResourceResponseMsg::FinishLoad)
310        {
311            self.error_sender.raise_response_send_error(error);
312        }
313        self.finished = true;
314    }
315    /// Cancel this [`InterceptedWebResourceLoad`], which will trigger a network error.
316    pub fn cancel(mut self) {
317        if let Err(error) = self
318            .response_sender
319            .send(WebResourceResponseMsg::CancelLoad)
320        {
321            self.error_sender.raise_response_send_error(error);
322        }
323        self.finished = true;
324    }
325}
326
327impl Drop for InterceptedWebResourceLoad {
328    fn drop(&mut self) {
329        if !self.finished &&
330            let Err(error) = self
331                .response_sender
332                .send(WebResourceResponseMsg::FinishLoad)
333        {
334            self.error_sender.raise_response_send_error(error);
335        }
336    }
337}
338
339/// The controls of an interactive form element.
340pub enum EmbedderControl {
341    /// The picker of a `<select>` element.
342    SelectElement(SelectElement),
343    /// The picker of a `<input type=color>` element.
344    ColorPicker(ColorPicker),
345    /// The picker of a `<input type=file>` element.
346    FilePicker(FilePicker),
347    /// Request to present an input method (IME) interface to the user when an
348    /// editable element is focused.
349    InputMethod(InputMethodControl),
350    /// A [simple dialog](https://html.spec.whatwg.org/multipage/#simple-dialogs) initiated by
351    /// script (`alert()`, `confirm()`, or `prompt()`). Since their messages are controlled by web
352    /// content, they should be presented to the user in a way that makes them impossible to
353    /// mistake for browser UI.
354    SimpleDialog(SimpleDialog),
355    /// A context menu. This can be triggered by things like right-clicking on web content.
356    /// The menu that is actually shown the user may be customized, but custom menu entries
357    /// must be handled by the embedder.
358    ContextMenu(ContextMenu),
359}
360
361impl EmbedderControl {
362    /// Return the unique identifier for this embedder control.
363    pub fn id(&self) -> EmbedderControlId {
364        match self {
365            EmbedderControl::SelectElement(select_element) => select_element.id,
366            EmbedderControl::ColorPicker(color_picker) => color_picker.id,
367            EmbedderControl::FilePicker(file_picker) => file_picker.id,
368            EmbedderControl::InputMethod(input_method) => input_method.id,
369            EmbedderControl::SimpleDialog(simple_dialog) => simple_dialog.id(),
370            EmbedderControl::ContextMenu(context_menu) => context_menu.id,
371        }
372    }
373}
374
375/// Represents a context menu opened on web content.
376pub struct ContextMenu {
377    pub(crate) id: EmbedderControlId,
378    pub(crate) position: DeviceIntRect,
379    pub(crate) items: Vec<ContextMenuItem>,
380    pub(crate) element_info: ContextMenuElementInformation,
381    pub(crate) response_sent: bool,
382    pub(crate) constellation_proxy: ConstellationProxy,
383}
384
385impl ContextMenu {
386    /// Return the [`EmbedderControlId`] associated with this element.
387    pub fn id(&self) -> EmbedderControlId {
388        self.id
389    }
390
391    /// Return the area occupied by the element on which this context menu was triggered.
392    ///
393    /// The embedder should use this value to position the prompt that is shown to the user.
394    pub fn position(&self) -> DeviceIntRect {
395        self.position
396    }
397
398    /// A [`ContextMenuElementInformation`] giving details about the element that this [`ContextMenu`]
399    /// was activated on.
400    pub fn element_info(&self) -> &ContextMenuElementInformation {
401        &self.element_info
402    }
403
404    /// Resolve the context menu by activating the given context menu action.
405    pub fn items(&self) -> &[ContextMenuItem] {
406        &self.items
407    }
408
409    /// Resolve the context menu by activating the given context menu action.
410    pub fn select(mut self, action: ContextMenuAction) {
411        self.constellation_proxy
412            .send(EmbedderToConstellationMessage::EmbedderControlResponse(
413                self.id,
414                EmbedderControlResponse::ContextMenu(Some(action)),
415            ));
416        self.response_sent = true;
417    }
418
419    /// Tell Servo that the context menu was dismissed with no selection.
420    pub fn dismiss(mut self) {
421        self.constellation_proxy
422            .send(EmbedderToConstellationMessage::EmbedderControlResponse(
423                self.id,
424                EmbedderControlResponse::ContextMenu(None),
425            ));
426        self.response_sent = true;
427    }
428}
429
430impl Drop for ContextMenu {
431    fn drop(&mut self) {
432        if !self.response_sent {
433            self.constellation_proxy
434                .send(EmbedderToConstellationMessage::EmbedderControlResponse(
435                    self.id,
436                    EmbedderControlResponse::ContextMenu(None),
437                ));
438        }
439    }
440}
441
442/// Represents a dialog triggered by clicking a `<select>` element.
443pub struct SelectElement {
444    pub(crate) id: EmbedderControlId,
445    pub(crate) position: DeviceIntRect,
446    pub(crate) constellation_proxy: ConstellationProxy,
447    pub(crate) select_element_request: SelectElementRequest,
448    pub(crate) response_sent: bool,
449}
450
451impl SelectElement {
452    /// Return the [`EmbedderControlId`] associated with this element.
453    pub fn id(&self) -> EmbedderControlId {
454        self.id
455    }
456
457    /// Return the area occupied by the `<select>` element that triggered the prompt.
458    ///
459    /// The embedder should use this value to position the prompt that is shown to the user.
460    pub fn position(&self) -> DeviceIntRect {
461        self.position
462    }
463
464    /// Consecutive `<option>` elements outside of an `<optgroup>` will be combined
465    /// into a single anonymous group without a label.
466    pub fn options(&self) -> &[SelectElementOptionOrOptgroup] {
467        &self.select_element_request.options
468    }
469
470    /// Set the options that are selected.
471    ///
472    /// `selected_options` is a vector of indices into the array returned by [`Self::options`].
473    ///
474    /// If other options have previously been selected, this set of options
475    /// will replace them.
476    pub fn select(&mut self, selected_options: Vec<usize>) {
477        self.select_element_request.selected_options = selected_options
478    }
479
480    /// Get the currently selected options, represented by indices into the array
481    /// returned by [`Self::options`].
482    pub fn selected_options(&self) -> Vec<usize> {
483        self.select_element_request.selected_options.clone()
484    }
485
486    /// Whether this `<select>` supports selecting multiple options.
487    pub fn allow_select_multiple(&self) -> bool {
488        self.select_element_request.allow_select_multiple
489    }
490
491    /// Resolve the prompt with the options that have been selected by calling [`Self::select`] previously.
492    pub fn submit(mut self) {
493        self.response_sent = true;
494        self.constellation_proxy
495            .send(EmbedderToConstellationMessage::EmbedderControlResponse(
496                self.id,
497                EmbedderControlResponse::SelectElement(self.selected_options()),
498            ));
499    }
500}
501
502impl Drop for SelectElement {
503    fn drop(&mut self) {
504        if !self.response_sent {
505            self.constellation_proxy
506                .send(EmbedderToConstellationMessage::EmbedderControlResponse(
507                    self.id,
508                    EmbedderControlResponse::SelectElement(self.selected_options()),
509                ));
510        }
511    }
512}
513
514/// Represents a dialog triggered by clicking a `<input type=color>` element.
515pub struct ColorPicker {
516    pub(crate) id: EmbedderControlId,
517    pub(crate) current_color: Option<RgbColor>,
518    pub(crate) position: DeviceIntRect,
519    pub(crate) constellation_proxy: ConstellationProxy,
520    pub(crate) response_sent: bool,
521}
522
523impl ColorPicker {
524    /// Return the [`EmbedderControlId`] associated with this element.
525    pub fn id(&self) -> EmbedderControlId {
526        self.id
527    }
528
529    /// Get the area occupied by the `<input>` element that triggered the prompt.
530    ///
531    /// The embedder should use this value to position the prompt that is shown to the user.
532    pub fn position(&self) -> DeviceIntRect {
533        self.position
534    }
535
536    /// Get the currently selected color for this [`ColorPicker`]. This is initially the selected color
537    /// before the picker is opened.
538    pub fn current_color(&self) -> Option<RgbColor> {
539        self.current_color
540    }
541
542    /// Set the selected color for this [`ColorPicker`]. Passing `None` behaves as if the default
543    /// color was selected.
544    pub fn select(&mut self, color: Option<RgbColor>) {
545        self.current_color = color;
546    }
547
548    /// Resolve the prompt with the options that have been selected by calling [`Self::select`] previously.
549    pub fn submit(mut self) {
550        self.response_sent = true;
551        self.constellation_proxy
552            .send(EmbedderToConstellationMessage::EmbedderControlResponse(
553                self.id,
554                EmbedderControlResponse::ColorPicker(self.current_color),
555            ));
556    }
557}
558
559impl Drop for ColorPicker {
560    fn drop(&mut self) {
561        if !self.response_sent {
562            self.constellation_proxy
563                .send(EmbedderToConstellationMessage::EmbedderControlResponse(
564                    self.id,
565                    EmbedderControlResponse::ColorPicker(self.current_color),
566                ));
567        }
568    }
569}
570
571/// Represents a dialog triggered by clicking a `<input type=file>` element.
572pub struct FilePicker {
573    pub(crate) id: EmbedderControlId,
574    pub(crate) file_picker_request: FilePickerRequest,
575    pub(crate) response_sender: Option<Sender<Option<Vec<PathBuf>>>>,
576}
577
578impl FilePicker {
579    /// Return the [`EmbedderControlId`] associated with this element.
580    pub fn id(&self) -> EmbedderControlId {
581        self.id
582    }
583
584    /// Get the file filter patterns for this [`FilePicker`], which specify the types
585    /// of files that are displayed in the dialog.
586    pub fn filter_patterns(&self) -> &[FilterPattern] {
587        &self.file_picker_request.filter_patterns
588    }
589
590    /// Whether or not this file picker allows selecting multiple files at once.
591    pub fn allow_select_multiple(&self) -> bool {
592        self.file_picker_request.allow_select_multiple
593    }
594
595    /// Get the currently selected files in this [`FilePicker`]. This is initially the files that
596    /// were previously selected before the picker is opened.
597    pub fn current_paths(&self) -> &[PathBuf] {
598        &self.file_picker_request.current_paths
599    }
600
601    /// Set the selected files for this [`FilePicker`].
602    pub fn select(&mut self, paths: &[PathBuf]) {
603        self.file_picker_request.current_paths = paths.to_owned();
604    }
605
606    /// Resolve the prompt with the files that have been selected by calling [`Self::select`] previously.
607    pub fn submit(mut self) {
608        if let Some(sender) = self.response_sender.take() {
609            let _ = sender.send(Some(std::mem::take(
610                &mut self.file_picker_request.current_paths,
611            )));
612        }
613    }
614
615    /// Tell Servo that the file picker was dismissed with no selection.
616    pub fn dismiss(mut self) {
617        if let Some(sender) = self.response_sender.take() {
618            let _ = sender.send(None);
619        }
620    }
621}
622
623impl Drop for FilePicker {
624    fn drop(&mut self) {
625        if let Some(sender) = self.response_sender.take() {
626            let _ = sender.send(None);
627        }
628    }
629}
630
631/// Represents a request to enable the system input method interface.
632pub struct InputMethodControl {
633    pub(crate) id: EmbedderControlId,
634    pub(crate) input_method_type: InputMethodType,
635    pub(crate) text: String,
636    pub(crate) insertion_point: Option<u32>,
637    pub(crate) position: DeviceIntRect,
638    pub(crate) multiline: bool,
639    pub(crate) allow_virtual_keyboard: bool,
640}
641
642impl InputMethodControl {
643    /// Return the [`EmbedderControlId`] associated with this element.
644    pub fn id(&self) -> EmbedderControlId {
645        self.id
646    }
647
648    /// Return the type of input method that initated this request.
649    pub fn input_method_type(&self) -> InputMethodType {
650        self.input_method_type
651    }
652
653    /// Return the current string value of the input field.
654    pub fn text(&self) -> String {
655        self.text.clone()
656    }
657
658    /// The current zero-based insertion point / cursor position if it is within the field or `None`
659    /// if it is not.
660    pub fn insertion_point(&self) -> Option<u32> {
661        self.insertion_point
662    }
663
664    /// Get the area occupied by the `<input>` element that triggered the input method.
665    ///
666    /// The embedder should use this value to position the input method interface that is
667    /// shown to the user.
668    pub fn position(&self) -> DeviceIntRect {
669        self.position
670    }
671
672    /// Whether or not this field is a multiline field.
673    pub fn multiline(&self) -> bool {
674        self.multiline
675    }
676
677    /// Whether the virtual keyboard should be shown for this input method event.
678    /// This is currently true for input method events that happen after the user has
679    /// interacted with page contents via an input event.
680    pub fn allow_virtual_keyboard(&self) -> bool {
681        self.allow_virtual_keyboard
682    }
683}
684
685/// [Simple dialogs](https://html.spec.whatwg.org/multipage/#simple-dialogs) are synchronous dialogs
686/// that can be opened by web content. Since their messages are controlled by web content, they
687/// should be presented to the user in a way that makes them impossible to mistake for browser UI.
688pub enum SimpleDialog {
689    Alert(AlertDialog),
690    Confirm(ConfirmDialog),
691    Prompt(PromptDialog),
692}
693
694impl SimpleDialog {
695    #[doc(hidden)]
696    pub fn message(&self) -> &str {
697        match self {
698            SimpleDialog::Alert(alert_dialog) => alert_dialog.message(),
699            SimpleDialog::Confirm(confirm_dialog) => confirm_dialog.message(),
700            SimpleDialog::Prompt(prompt_dialog) => prompt_dialog.message(),
701        }
702    }
703
704    #[doc(hidden)]
705    pub fn confirm(self) {
706        match self {
707            SimpleDialog::Alert(alert_dialog) => alert_dialog.confirm(),
708            SimpleDialog::Confirm(confirm_dialog) => confirm_dialog.confirm(),
709            SimpleDialog::Prompt(prompt_dialog) => prompt_dialog.confirm(),
710        }
711    }
712
713    #[doc(hidden)]
714    pub fn dismiss(self) {
715        match self {
716            SimpleDialog::Alert(alert_dialog) => alert_dialog.confirm(),
717            SimpleDialog::Confirm(confirm_dialog) => confirm_dialog.dismiss(),
718            SimpleDialog::Prompt(prompt_dialog) => prompt_dialog.dismiss(),
719        }
720    }
721}
722
723impl SimpleDialog {
724    fn id(&self) -> EmbedderControlId {
725        match self {
726            SimpleDialog::Alert(alert_dialog) => alert_dialog.id,
727            SimpleDialog::Confirm(confirm_dialog) => confirm_dialog.id,
728            SimpleDialog::Prompt(prompt_dialog) => prompt_dialog.id,
729        }
730    }
731}
732
733impl From<SimpleDialogRequest> for SimpleDialog {
734    fn from(simple_dialog_request: SimpleDialogRequest) -> Self {
735        match simple_dialog_request {
736            SimpleDialogRequest::Alert {
737                id,
738                message,
739                response_sender,
740            } => Self::Alert(AlertDialog {
741                id,
742                message,
743                response_sender,
744                response_sent: false,
745            }),
746            SimpleDialogRequest::Confirm {
747                id,
748                message,
749                response_sender,
750            } => Self::Confirm(ConfirmDialog {
751                id,
752                message,
753                response_sender,
754                response_sent: false,
755            }),
756            SimpleDialogRequest::Prompt {
757                id,
758                message,
759                default,
760                response_sender,
761            } => Self::Prompt(PromptDialog {
762                id,
763                message,
764                current_value: default,
765                response_sender,
766                response_sent: false,
767            }),
768        }
769    }
770}
771
772/// [`alert()`](https://html.spec.whatwg.org/multipage/#dom-alert).
773///
774/// The confirm dialog is expected to be represented by a message and an "Ok" button.
775/// Pressing "Ok" always causes the DOM API to return `undefined`.
776pub struct AlertDialog {
777    id: EmbedderControlId,
778    message: String,
779    response_sender: GenericSender<AlertResponse>,
780    response_sent: bool,
781}
782
783impl Drop for AlertDialog {
784    fn drop(&mut self) {
785        if !self.response_sent {
786            let _ = self.response_sender.send(AlertResponse::Ok);
787        }
788    }
789}
790
791impl AlertDialog {
792    /// Get the message text of this [`AlertDialog`], which is set by web content.
793    pub fn message(&self) -> &str {
794        &self.message
795    }
796
797    /// This should be called when the dialog button is pressed.
798    pub fn confirm(self) {
799        // The result will be send via the `Drop` implementation.
800    }
801}
802
803/// [`confirm()`](https://html.spec.whatwg.org/multipage/#dom-confirm).
804///
805/// The confirm dialog is expected to be represented by a message and "Ok" and "Cancel"
806/// buttons. When "Ok" is selected `true` is sent as a response to the DOM API, while
807/// "Cancel" will send `false`.
808pub struct ConfirmDialog {
809    id: EmbedderControlId,
810    message: String,
811    response_sender: GenericSender<ConfirmResponse>,
812    response_sent: bool,
813}
814
815impl ConfirmDialog {
816    /// Get the message text of this [`ConfirmDialog`], which is set by web content.
817    pub fn message(&self) -> &str {
818        &self.message
819    }
820
821    /// This should be called when the dialog "Cancel" button is pressed.
822    pub fn dismiss(mut self) {
823        let _ = self.response_sender.send(ConfirmResponse::Cancel);
824        self.response_sent = true;
825    }
826
827    /// This should be called when the dialog "Ok" button is pressed.
828    pub fn confirm(mut self) {
829        let _ = self.response_sender.send(ConfirmResponse::Ok);
830        self.response_sent = true;
831    }
832}
833
834impl Drop for ConfirmDialog {
835    fn drop(&mut self) {
836        if !self.response_sent {
837            let _ = self.response_sender.send(ConfirmResponse::Cancel);
838        }
839    }
840}
841
842/// A [`prompt()`](https://html.spec.whatwg.org/multipage/#dom-prompt).
843///
844/// The prompt dialog is expected to be represented by a message, a text entry field, and
845/// an "Ok" and "Cancel" buttons. When "Ok" is selected the current prompt value is sent
846/// as the response to the DOM API. A default value may be sent with the [`PromptDialog`],
847/// which be be retrieved by calling [`Self::current_value`]. Before calling [`Self::confirm`]
848/// or as the prompt field changes, the embedder is expected to call
849/// [`Self::set_current_value`].
850pub struct PromptDialog {
851    id: EmbedderControlId,
852    message: String,
853    current_value: String,
854    response_sender: GenericSender<PromptResponse>,
855    response_sent: bool,
856}
857
858impl Drop for PromptDialog {
859    fn drop(&mut self) {
860        if !self.response_sent {
861            let _ = self.response_sender.send(PromptResponse::Cancel);
862        }
863    }
864}
865
866impl PromptDialog {
867    /// Get the message text of this [`PromptDialog`], which is set by web content.
868    pub fn message(&self) -> &str {
869        &self.message
870    }
871
872    /// Get the current value of the prompt's text entry field.
873    pub fn current_value(&self) -> &str {
874        &self.current_value
875    }
876
877    /// Set the current value of the prompt's text entry field.
878    pub fn set_current_value(&mut self, new_value: &str) {
879        self.current_value = new_value.to_owned()
880    }
881
882    /// This should be called when the dialog "Cancel" button is pressed.
883    pub fn dismiss(mut self) {
884        let _ = self.response_sender.send(PromptResponse::Cancel);
885        self.response_sent = true;
886    }
887
888    /// This should be called when the dialog "Ok" button is pressed, the current prompt value will
889    /// be sent to web content.
890    pub fn confirm(mut self) {
891        let _ = self
892            .response_sender
893            .send(PromptResponse::Ok(self.current_value.clone()));
894        self.response_sent = true;
895    }
896}
897
898/// A request from Servo to embedder to open a new auxiliary [`WebView`], typically
899/// triggered by page content calling DOM APIs like `window.open()`.
900///
901/// Refer to the documentation of [`WebViewDelegate::request_create_new`] for more information.
902pub struct CreateNewWebViewRequest {
903    pub(crate) servo: Servo,
904    pub(crate) responder: IpcResponder<Option<NewWebViewDetails>>,
905}
906
907impl CreateNewWebViewRequest {
908    /// Returns a [`WebViewBuilder`] that can be used to create a new auxiliary [`WebView`].
909    pub fn builder(self, rendering_context: Rc<dyn RenderingContext>) -> WebViewBuilder {
910        WebViewBuilder::new_for_create_request(&self.servo, rendering_context, self.responder)
911    }
912}
913
914/// A trait that the embedder can implement to customize the behaviour of a [`WebView`] or to
915/// listen to events from a [`WebView`]. Multiple [`WebView`]s can share the same delegate
916/// instance. A [`WebView`]'s delegate needs to be set at the time of creation using
917/// [`WebViewBuilder::delegate`].
918pub trait WebViewDelegate {
919    /// Get the [`ScreenGeometry`] for this [`WebView`]. If this is unimplemented or returns `None`
920    /// the screen will have the size of the [`WebView`]'s `RenderingContext` and `WebView` will be
921    /// considered to be positioned at the screen's origin.
922    fn screen_geometry(&self, _webview: WebView) -> Option<ScreenGeometry> {
923        None
924    }
925    /// The URL of the currently loaded page in this [`WebView`] has changed. The new
926    /// URL can accessed via [`WebView::url`].
927    fn notify_url_changed(&self, _webview: WebView, _url: Url) {}
928    /// The page title of the currently loaded page in this [`WebView`] has changed. The new
929    /// title can accessed via [`WebView::page_title`].
930    fn notify_page_title_changed(&self, _webview: WebView, _title: Option<String>) {}
931    /// The status text of the currently loaded page in this [`WebView`] has changed. The new
932    /// status text can accessed via [`WebView::status_text`].
933    fn notify_status_text_changed(&self, _webview: WebView, _status: Option<String>) {}
934    /// This [`WebView`] has either become focused or lost focus. Whether or not the
935    /// [`WebView`] is focused can be accessed via [`WebView::focused`].
936    fn notify_focus_changed(&self, _webview: WebView, _focused: bool) {}
937    /// This [`WebView`] has either started to animate or stopped animating. When a
938    /// [`WebView`] is animating, it is up to the embedding application ensure that
939    /// `Servo::spin_event_loop` is called at regular intervals in order to update the
940    /// painted contents of the [`WebView`].
941    fn notify_animating_changed(&self, _webview: WebView, _animating: bool) {}
942    /// The `LoadStatus` of the currently loading or loaded page in this [`WebView`] has changed. The new
943    /// status can accessed via [`WebView::load_status`].
944    fn notify_load_status_changed(&self, _webview: WebView, _status: LoadStatus) {}
945    /// The [`Cursor`] of the currently loaded page in this [`WebView`] has changed. The new
946    /// cursor can accessed via [`WebView::cursor`].
947    fn notify_cursor_changed(&self, _webview: WebView, _cursor: Cursor) {}
948    /// The favicon of the currently loaded page in this [`WebView`] has changed. The new
949    /// favicon [`Image`](embedder_traits::Image) can accessed via [`WebView::favicon`].
950    fn notify_favicon_changed(&self, _webview: WebView) {}
951    /// Notify the embedder that it needs to present a new frame.
952    fn notify_new_frame_ready(&self, _webview: WebView) {}
953    /// The navigation history of this [`WebView`] has changed. The navigation history is represented
954    /// as a `Vec<Url>` and `_current` denotes the current index in the history. New navigations,
955    /// back navigation, and forward navigation modify this index.
956    fn notify_history_changed(&self, _webview: WebView, _entries: Vec<Url>, _current: usize) {}
957    /// A history traversal operation is complete.
958    fn notify_traversal_complete(&self, _webview: WebView, _traversal_id: TraversalId) {}
959    /// Page content has closed this [`WebView`] via `window.close()`. It's the embedder's
960    /// responsibility to remove the [`WebView`] from the interface when this notification
961    /// occurs.
962    fn notify_closed(&self, _webview: WebView) {}
963
964    /// An input event passed to this [`WebView`] via [`WebView::notify_input_event`] has been handled
965    /// by Servo. This allows post-procesing of input events, such as chaining up unhandled events
966    /// to parent UI elements.
967    fn notify_input_event_handled(
968        &self,
969        _webview: WebView,
970        _event_id: InputEventId,
971        _result: InputEventResult,
972    ) {
973    }
974    /// A pipeline in the webview panicked. First string is the reason, second one is the backtrace.
975    fn notify_crashed(&self, _webview: WebView, _reason: String, _backtrace: Option<String>) {}
976    /// Notifies the embedder about media session events
977    /// (i.e. when there is metadata for the active media session, playback state changes...).
978    fn notify_media_session_event(&self, _webview: WebView, _event: MediaSessionEvent) {}
979    /// A notification that the [`WebView`] has entered or exited fullscreen mode. This is an
980    /// opportunity for the embedder to transition the containing window into or out of fullscreen
981    /// mode and to show or hide extra UI elements. Regardless of how the notification is handled,
982    /// the page will enter or leave fullscreen state internally according to the [Fullscreen
983    /// API](https://fullscreen.spec.whatwg.org/).
984    fn notify_fullscreen_state_changed(&self, _webview: WebView, _is_fullscreen: bool) {}
985
986    /// Whether or not to allow a [`WebView`] to load a URL in its main frame or one of its
987    /// nested `<iframe>`s. [`NavigationRequest`]s are accepted by default.
988    fn request_navigation(&self, _webview: WebView, _navigation_request: NavigationRequest) {}
989    /// Whether or not to allow a [`WebView`]  to unload a `Document` in its main frame or one
990    /// of its nested `<iframe>`s. By default, unloads are allowed.
991    fn request_unload(&self, _webview: WebView, _unload_request: AllowOrDenyRequest) {}
992    /// Move the window to a point.
993    fn request_move_to(&self, _webview: WebView, _point: DeviceIntPoint) {}
994    /// Whether or not to allow a [`WebView`] to (un)register a protocol handler (e.g. `mailto:`).
995    /// Typically an embedder application will show a permissions prompt when this happens
996    /// to confirm a protocol handler is allowed. By default, requests are denied.
997    /// For more information, see the specification:
998    /// <https://html.spec.whatwg.org/multipage/#custom-handlers>
999    fn request_protocol_handler(
1000        &self,
1001        _webview: WebView,
1002        _protocol_handler_registration: ProtocolHandlerRegistration,
1003        _allow_deny_request: AllowOrDenyRequest,
1004    ) {
1005    }
1006    /// Try to resize the window that contains this [`WebView`] to the provided outer
1007    /// size. These resize requests can come from page content. Servo will ensure that the
1008    /// values are greater than zero, but it is up to the embedder to limit the maximum
1009    /// size. For instance, a reasonable limitation might be that the final size is no
1010    /// larger than the screen size.
1011    fn request_resize_to(&self, _webview: WebView, _requested_outer_size: DeviceIntSize) {}
1012    /// This method is called when web content makes a request to open a new
1013    /// `WebView`, such as via the [`window.open`] DOM API. If this request is
1014    /// ignored, no new `WebView` will be opened. Embedders can handle this method by
1015    /// using the provided [`CreateNewWebViewRequest`] to build a new `WebView`.
1016    ///
1017    /// ```ignore
1018    /// fn request_create_new(&self, parent_webview: WebView, request: CreateNewWebViewRequest) {
1019    ///     let webview = request
1020    ///         .builder(self.rendering_context())
1021    ///         .delegate(parent_webview.delegate())
1022    ///         .build();
1023    ///     self.register_webview(webview);
1024    /// }
1025    /// ```
1026    ///
1027    /// **Important:** It is important to keep a live handle to the new `WebView` in the application or
1028    /// it will be immediately destroyed.
1029    ///
1030    /// [`window.open`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/open
1031    fn request_create_new(&self, _parent_webview: WebView, _request: CreateNewWebViewRequest) {}
1032    /// Content in a [`WebView`] is requesting permission to access a feature requiring
1033    /// permission from the user. The embedder should allow or deny the request, either by
1034    /// reading a cached value or querying the user for permission via the user interface.
1035    fn request_permission(&self, _webview: WebView, _request: PermissionRequest) {}
1036
1037    /// Request that the embedder supply credentials to use for HTTP authentication.
1038    fn request_authentication(
1039        &self,
1040        _webview: WebView,
1041        _authentication_request: AuthenticationRequest,
1042    ) {
1043    }
1044
1045    /// Open dialog to select bluetooth device.
1046    fn show_bluetooth_device_dialog(
1047        &self,
1048        _webview: WebView,
1049        _request: BluetoothDeviceSelectionRequest,
1050    ) {
1051    }
1052
1053    /// Request that the embedder show UI elements for form controls that are not integrated
1054    /// into page content, such as dropdowns for `<select>` elements.
1055    fn show_embedder_control(&self, _webview: WebView, _embedder_control: EmbedderControl) {}
1056
1057    /// Request that the embedder hide and ignore a previous [`EmbedderControl`] request, if it hasn’t
1058    /// already responded to it.
1059    ///
1060    /// After this point, any further responses to that request will be ignored.
1061    fn hide_embedder_control(&self, _webview: WebView, _control_id: EmbedderControlId) {}
1062
1063    /// Triggered when this [`WebView`] will load a web (HTTP/HTTPS) resource. The load may be
1064    /// intercepted and alternate contents can be loaded by the client by calling
1065    /// [`WebResourceLoad::intercept`]. If not handled, the load will continue as normal.
1066    ///
1067    /// Note: This delegate method is called for all resource loads associated with a [`WebView`].
1068    /// For loads not associated with a [`WebView`], such as those for service workers, Servo
1069    /// will call [`crate::ServoDelegate::load_web_resource`].
1070    fn load_web_resource(&self, _webview: WebView, _load: WebResourceLoad) {}
1071
1072    /// Request to display a notification.
1073    fn show_notification(&self, _webview: WebView, _notification: Notification) {}
1074
1075    /// A console message was logged by content in this [`WebView`].
1076    /// <https://developer.mozilla.org/en-US/docs/Web/API/Console_API>
1077    fn show_console_message(&self, _webview: WebView, _level: ConsoleLogLevel, _message: String) {}
1078
1079    /// There is a new accessibility tree update from this [`WebView`].
1080    ///
1081    /// Generally the impl should send this update to an AccessKit adapter, but it may need to queue
1082    /// the update for later, if the [graft node] for this [`WebView`] has not yet been created
1083    /// *and* your impl is unable to create it (and send that update to AccessKit) before sending
1084    /// this update to AccessKit. For more details, see [`WebView::set_accessibility_active`].
1085    fn notify_accessibility_tree_update(
1086        &self,
1087        _webview: WebView,
1088        _tree_update: accesskit::TreeUpdate,
1089    ) {
1090    }
1091}
1092
1093pub(crate) struct DefaultWebViewDelegate;
1094impl WebViewDelegate for DefaultWebViewDelegate {}
1095
1096#[cfg(test)]
1097mod test {
1098    use super::*;
1099
1100    #[test]
1101    fn test_allow_deny_request() {
1102        use servo_base::generic_channel;
1103
1104        use crate::responders::ServoErrorChannel;
1105
1106        for default_response in [AllowOrDeny::Allow, AllowOrDeny::Deny] {
1107            // Explicit allow yields allow and nothing else
1108            let errors = ServoErrorChannel::default();
1109            let (sender, receiver) =
1110                generic_channel::channel().expect("Failed to create IPC channel");
1111            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1112            request.allow();
1113            assert_eq!(receiver.try_recv().ok(), Some(AllowOrDeny::Allow));
1114            assert_eq!(receiver.try_recv().ok(), None);
1115            assert!(errors.try_recv().is_none());
1116
1117            // Explicit deny yields deny and nothing else
1118            let errors = ServoErrorChannel::default();
1119            let (sender, receiver) =
1120                generic_channel::channel().expect("Failed to create IPC channel");
1121            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1122            request.deny();
1123            assert_eq!(receiver.try_recv().ok(), Some(AllowOrDeny::Deny));
1124            assert_eq!(receiver.try_recv().ok(), None);
1125            assert!(errors.try_recv().is_none());
1126
1127            // No response yields default response and nothing else
1128            let errors = ServoErrorChannel::default();
1129            let (sender, receiver) =
1130                generic_channel::channel().expect("Failed to create IPC channel");
1131            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1132            drop(request);
1133            assert_eq!(receiver.try_recv().ok(), Some(default_response));
1134            assert_eq!(receiver.try_recv().ok(), None);
1135            assert!(errors.try_recv().is_none());
1136
1137            // Explicit allow when receiver disconnected yields error
1138            let errors = ServoErrorChannel::default();
1139            let (sender, receiver) =
1140                generic_channel::channel().expect("Failed to create IPC channel");
1141            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1142            drop(receiver);
1143            request.allow();
1144            assert!(errors.try_recv().is_some());
1145
1146            // Explicit deny when receiver disconnected yields error
1147            let errors = ServoErrorChannel::default();
1148            let (sender, receiver) =
1149                generic_channel::channel().expect("Failed to create IPC channel");
1150            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1151            drop(receiver);
1152            request.deny();
1153            assert!(errors.try_recv().is_some());
1154
1155            // No response when receiver disconnected yields no error
1156            let errors = ServoErrorChannel::default();
1157            let (sender, receiver) =
1158                generic_channel::channel().expect("Failed to create IPC channel");
1159            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1160            drop(receiver);
1161            drop(request);
1162            assert!(errors.try_recv().is_none());
1163        }
1164    }
1165
1166    #[test]
1167    fn test_authentication_request() {
1168        use crate::responders::ServoErrorChannel;
1169
1170        let url = Url::parse("https://example.com").expect("Guaranteed by argument");
1171
1172        // Explicit response yields that response and nothing else
1173        let errors = ServoErrorChannel::default();
1174        let (sender, mut receiver) = tokio::sync::oneshot::channel();
1175        let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
1176        request.authenticate("diffie".to_owned(), "hunter2".to_owned());
1177        assert_eq!(
1178            receiver.try_recv().ok(),
1179            Some(Some(AuthenticationResponse {
1180                username: "diffie".to_owned(),
1181                password: "hunter2".to_owned(),
1182            }))
1183        );
1184        assert_eq!(receiver.try_recv().ok(), None);
1185        assert!(errors.try_recv().is_none());
1186
1187        // No response yields None response and nothing else
1188        let errors = ServoErrorChannel::default();
1189        let (sender, mut receiver) = tokio::sync::oneshot::channel();
1190        let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
1191        drop(request);
1192        assert_eq!(receiver.try_recv().ok(), Some(None));
1193        assert_eq!(receiver.try_recv().ok(), None);
1194        assert!(errors.try_recv().is_none());
1195
1196        // Explicit response when receiver disconnected yields error
1197        let errors = ServoErrorChannel::default();
1198        let (sender, receiver) = tokio::sync::oneshot::channel();
1199        let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
1200        drop(receiver);
1201        request.authenticate("diffie".to_owned(), "hunter2".to_owned());
1202        assert!(errors.try_recv().is_some());
1203
1204        // No response when receiver disconnected yields no error
1205        let errors = ServoErrorChannel::default();
1206        let (sender, receiver) = tokio::sync::oneshot::channel();
1207        let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
1208        drop(receiver);
1209        drop(request);
1210        assert!(errors.try_recv().is_none());
1211    }
1212
1213    #[test]
1214    fn test_web_resource_load() {
1215        use http::{HeaderMap, Method, StatusCode};
1216
1217        use crate::responders::ServoErrorChannel;
1218
1219        let web_resource_request = || WebResourceRequest {
1220            method: Method::GET,
1221            headers: HeaderMap::default(),
1222            url: Url::parse("https://example.com").expect("Guaranteed by argument"),
1223            destination: content_security_policy::Destination::Document,
1224            referrer_url: None,
1225            is_for_main_frame: false,
1226            is_redirect: false,
1227        };
1228        let web_resource_response = || {
1229            WebResourceResponse::new(
1230                Url::parse("https://diffie.test").expect("Guaranteed by argument"),
1231            )
1232            .status_code(StatusCode::IM_A_TEAPOT)
1233        };
1234
1235        // Explicit intercept with explicit cancel yields Start and Cancel and nothing else
1236        let errors = ServoErrorChannel::default();
1237        let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
1238        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1239        request.intercept(web_resource_response()).cancel();
1240        assert!(matches!(
1241            receiver.try_recv(),
1242            Ok(WebResourceResponseMsg::Start(_))
1243        ));
1244        assert!(matches!(
1245            receiver.try_recv(),
1246            Ok(WebResourceResponseMsg::CancelLoad)
1247        ));
1248        assert!(matches!(receiver.try_recv(), Err(_)));
1249        assert!(errors.try_recv().is_none());
1250
1251        // Explicit intercept with no further action yields Start and FinishLoad and nothing else
1252        let errors = ServoErrorChannel::default();
1253        let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
1254        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1255        drop(request.intercept(web_resource_response()));
1256        assert!(matches!(
1257            receiver.try_recv(),
1258            Ok(WebResourceResponseMsg::Start(_))
1259        ));
1260        assert!(matches!(
1261            receiver.try_recv(),
1262            Ok(WebResourceResponseMsg::FinishLoad)
1263        ));
1264        assert!(matches!(receiver.try_recv(), Err(_)));
1265        assert!(errors.try_recv().is_none());
1266
1267        // No response yields DoNotIntercept and nothing else
1268        let errors = ServoErrorChannel::default();
1269        let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
1270        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1271        drop(request);
1272        assert!(matches!(
1273            receiver.try_recv(),
1274            Ok(WebResourceResponseMsg::DoNotIntercept)
1275        ));
1276        assert!(matches!(receiver.try_recv(), Err(_)));
1277        assert!(errors.try_recv().is_none());
1278
1279        // Explicit intercept with explicit cancel when receiver disconnected yields error
1280        let errors = ServoErrorChannel::default();
1281        let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
1282        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1283        drop(receiver);
1284        request.intercept(web_resource_response()).cancel();
1285        assert!(errors.try_recv().is_some());
1286
1287        // Explicit intercept with no further action when receiver disconnected yields error
1288        let errors = ServoErrorChannel::default();
1289        let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
1290        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1291        drop(receiver);
1292        drop(request.intercept(web_resource_response()));
1293        assert!(errors.try_recv().is_some());
1294
1295        // No response when receiver disconnected yields no error
1296        let errors = ServoErrorChannel::default();
1297        let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
1298        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1299        drop(receiver);
1300        drop(request);
1301        assert!(errors.try_recv().is_none());
1302    }
1303}