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
914pub trait WebViewDelegate {
915    /// Get the [`ScreenGeometry`] for this [`WebView`]. If this is unimplemented or returns `None`
916    /// the screen will have the size of the [`WebView`]'s `RenderingContext` and `WebView` will be
917    /// considered to be positioned at the screen's origin.
918    fn screen_geometry(&self, _webview: WebView) -> Option<ScreenGeometry> {
919        None
920    }
921    /// The URL of the currently loaded page in this [`WebView`] has changed. The new
922    /// URL can accessed via [`WebView::url`].
923    fn notify_url_changed(&self, _webview: WebView, _url: Url) {}
924    /// The page title of the currently loaded page in this [`WebView`] has changed. The new
925    /// title can accessed via [`WebView::page_title`].
926    fn notify_page_title_changed(&self, _webview: WebView, _title: Option<String>) {}
927    /// The status text of the currently loaded page in this [`WebView`] has changed. The new
928    /// status text can accessed via [`WebView::status_text`].
929    fn notify_status_text_changed(&self, _webview: WebView, _status: Option<String>) {}
930    /// This [`WebView`] has either become focused or lost focus. Whether or not the
931    /// [`WebView`] is focused can be accessed via [`WebView::focused`].
932    fn notify_focus_changed(&self, _webview: WebView, _focused: bool) {}
933    /// This [`WebView`] has either started to animate or stopped animating. When a
934    /// [`WebView`] is animating, it is up to the embedding application ensure that
935    /// `Servo::spin_event_loop` is called at regular intervals in order to update the
936    /// painted contents of the [`WebView`].
937    fn notify_animating_changed(&self, _webview: WebView, _animating: bool) {}
938    /// The `LoadStatus` of the currently loading or loaded page in this [`WebView`] has changed. The new
939    /// status can accessed via [`WebView::load_status`].
940    fn notify_load_status_changed(&self, _webview: WebView, _status: LoadStatus) {}
941    /// The [`Cursor`] of the currently loaded page in this [`WebView`] has changed. The new
942    /// cursor can accessed via [`WebView::cursor`].
943    fn notify_cursor_changed(&self, _webview: WebView, _cursor: Cursor) {}
944    /// The favicon of the currently loaded page in this [`WebView`] has changed. The new
945    /// favicon [`Image`](embedder_traits::Image) can accessed via [`WebView::favicon`].
946    fn notify_favicon_changed(&self, _webview: WebView) {}
947    /// Notify the embedder that it needs to present a new frame.
948    fn notify_new_frame_ready(&self, _webview: WebView) {}
949    /// The navigation history of this [`WebView`] has changed. The navigation history is represented
950    /// as a `Vec<Url>` and `_current` denotes the current index in the history. New navigations,
951    /// back navigation, and forward navigation modify this index.
952    fn notify_history_changed(&self, _webview: WebView, _entries: Vec<Url>, _current: usize) {}
953    /// A history traversal operation is complete.
954    fn notify_traversal_complete(&self, _webview: WebView, _traversal_id: TraversalId) {}
955    /// Page content has closed this [`WebView`] via `window.close()`. It's the embedder's
956    /// responsibility to remove the [`WebView`] from the interface when this notification
957    /// occurs.
958    fn notify_closed(&self, _webview: WebView) {}
959
960    /// An input event passed to this [`WebView`] via [`WebView::notify_input_event`] has been handled
961    /// by Servo. This allows post-procesing of input events, such as chaining up unhandled events
962    /// to parent UI elements.
963    fn notify_input_event_handled(
964        &self,
965        _webview: WebView,
966        _event_id: InputEventId,
967        _result: InputEventResult,
968    ) {
969    }
970    /// A pipeline in the webview panicked. First string is the reason, second one is the backtrace.
971    fn notify_crashed(&self, _webview: WebView, _reason: String, _backtrace: Option<String>) {}
972    /// Notifies the embedder about media session events
973    /// (i.e. when there is metadata for the active media session, playback state changes...).
974    fn notify_media_session_event(&self, _webview: WebView, _event: MediaSessionEvent) {}
975    /// A notification that the [`WebView`] has entered or exited fullscreen mode. This is an
976    /// opportunity for the embedder to transition the containing window into or out of fullscreen
977    /// mode and to show or hide extra UI elements. Regardless of how the notification is handled,
978    /// the page will enter or leave fullscreen state internally according to the [Fullscreen
979    /// API](https://fullscreen.spec.whatwg.org/).
980    fn notify_fullscreen_state_changed(&self, _webview: WebView, _is_fullscreen: bool) {}
981
982    /// Whether or not to allow a [`WebView`] to load a URL in its main frame or one of its
983    /// nested `<iframe>`s. [`NavigationRequest`]s are accepted by default.
984    fn request_navigation(&self, _webview: WebView, _navigation_request: NavigationRequest) {}
985    /// Whether or not to allow a [`WebView`]  to unload a `Document` in its main frame or one
986    /// of its nested `<iframe>`s. By default, unloads are allowed.
987    fn request_unload(&self, _webview: WebView, _unload_request: AllowOrDenyRequest) {}
988    /// Move the window to a point.
989    fn request_move_to(&self, _webview: WebView, _point: DeviceIntPoint) {}
990    /// Whether or not to allow a [`WebView`] to (un)register a protocol handler (e.g. `mailto:`).
991    /// Typically an embedder application will show a permissions prompt when this happens
992    /// to confirm a protocol handler is allowed. By default, requests are denied.
993    /// For more information, see the specification:
994    /// <https://html.spec.whatwg.org/multipage/#custom-handlers>
995    fn request_protocol_handler(
996        &self,
997        _webview: WebView,
998        _protocol_handler_registration: ProtocolHandlerRegistration,
999        _allow_deny_request: AllowOrDenyRequest,
1000    ) {
1001    }
1002    /// Try to resize the window that contains this [`WebView`] to the provided outer
1003    /// size. These resize requests can come from page content. Servo will ensure that the
1004    /// values are greater than zero, but it is up to the embedder to limit the maximum
1005    /// size. For instance, a reasonable limitation might be that the final size is no
1006    /// larger than the screen size.
1007    fn request_resize_to(&self, _webview: WebView, _requested_outer_size: DeviceIntSize) {}
1008    /// This method is called when web content makes a request to open a new
1009    /// `WebView`, such as via the [`window.open`] DOM API. If this request is
1010    /// ignored, no new `WebView` will be opened. Embedders can handle this method by
1011    /// using the provided [`CreateNewWebViewRequest`] to build a new `WebView`.
1012    ///
1013    /// ```ignore
1014    /// fn request_create_new(&self, parent_webview: WebView, request: CreateNewWebViewRequest) {
1015    ///     let webview = request
1016    ///         .builder(self.rendering_context())
1017    ///         .delegate(parent_webview.delegate())
1018    ///         .build();
1019    ///     self.register_webview(webview);
1020    /// }
1021    /// ```
1022    ///
1023    /// **Important:** It is important to keep a live handle to the new `WebView` in the application or
1024    /// it will be immediately destroyed.
1025    ///
1026    /// [`window.open`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/open
1027    fn request_create_new(&self, _parent_webview: WebView, _request: CreateNewWebViewRequest) {}
1028    /// Content in a [`WebView`] is requesting permission to access a feature requiring
1029    /// permission from the user. The embedder should allow or deny the request, either by
1030    /// reading a cached value or querying the user for permission via the user interface.
1031    fn request_permission(&self, _webview: WebView, _request: PermissionRequest) {}
1032
1033    /// Request that the embedder supply credentials to use for HTTP authentication.
1034    fn request_authentication(
1035        &self,
1036        _webview: WebView,
1037        _authentication_request: AuthenticationRequest,
1038    ) {
1039    }
1040
1041    /// Open dialog to select bluetooth device.
1042    fn show_bluetooth_device_dialog(
1043        &self,
1044        _webview: WebView,
1045        _request: BluetoothDeviceSelectionRequest,
1046    ) {
1047    }
1048
1049    /// Request that the embedder show UI elements for form controls that are not integrated
1050    /// into page content, such as dropdowns for `<select>` elements.
1051    fn show_embedder_control(&self, _webview: WebView, _embedder_control: EmbedderControl) {}
1052
1053    /// Request that the embedder hide and ignore a previous [`EmbedderControl`] request, if it hasn’t
1054    /// already responded to it.
1055    ///
1056    /// After this point, any further responses to that request will be ignored.
1057    fn hide_embedder_control(&self, _webview: WebView, _control_id: EmbedderControlId) {}
1058
1059    /// Triggered when this [`WebView`] will load a web (HTTP/HTTPS) resource. The load may be
1060    /// intercepted and alternate contents can be loaded by the client by calling
1061    /// [`WebResourceLoad::intercept`]. If not handled, the load will continue as normal.
1062    ///
1063    /// Note: This delegate method is called for all resource loads associated with a [`WebView`].
1064    /// For loads not associated with a [`WebView`], such as those for service workers, Servo
1065    /// will call [`crate::ServoDelegate::load_web_resource`].
1066    fn load_web_resource(&self, _webview: WebView, _load: WebResourceLoad) {}
1067
1068    /// Request to display a notification.
1069    fn show_notification(&self, _webview: WebView, _notification: Notification) {}
1070
1071    /// A console message was logged by content in this [`WebView`].
1072    /// <https://developer.mozilla.org/en-US/docs/Web/API/Console_API>
1073    fn show_console_message(&self, _webview: WebView, _level: ConsoleLogLevel, _message: String) {}
1074
1075    /// There is a new accessibility tree update from this [`WebView`].
1076    ///
1077    /// Generally the impl should send this update to an AccessKit adapter, but it may need to queue
1078    /// the update for later, if the [graft node] for this [`WebView`] has not yet been created
1079    /// *and* your impl is unable to create it (and send that update to AccessKit) before sending
1080    /// this update to AccessKit. For more details, see [`WebView::set_accessibility_active`].
1081    fn notify_accessibility_tree_update(
1082        &self,
1083        _webview: WebView,
1084        _tree_update: accesskit::TreeUpdate,
1085    ) {
1086    }
1087}
1088
1089pub(crate) struct DefaultWebViewDelegate;
1090impl WebViewDelegate for DefaultWebViewDelegate {}
1091
1092#[cfg(test)]
1093mod test {
1094    use super::*;
1095
1096    #[test]
1097    fn test_allow_deny_request() {
1098        use servo_base::generic_channel;
1099
1100        use crate::responders::ServoErrorChannel;
1101
1102        for default_response in [AllowOrDeny::Allow, AllowOrDeny::Deny] {
1103            // Explicit allow yields allow and nothing else
1104            let errors = ServoErrorChannel::default();
1105            let (sender, receiver) =
1106                generic_channel::channel().expect("Failed to create IPC channel");
1107            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1108            request.allow();
1109            assert_eq!(receiver.try_recv().ok(), Some(AllowOrDeny::Allow));
1110            assert_eq!(receiver.try_recv().ok(), None);
1111            assert!(errors.try_recv().is_none());
1112
1113            // Explicit deny yields deny and nothing else
1114            let errors = ServoErrorChannel::default();
1115            let (sender, receiver) =
1116                generic_channel::channel().expect("Failed to create IPC channel");
1117            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1118            request.deny();
1119            assert_eq!(receiver.try_recv().ok(), Some(AllowOrDeny::Deny));
1120            assert_eq!(receiver.try_recv().ok(), None);
1121            assert!(errors.try_recv().is_none());
1122
1123            // No response yields default response and nothing else
1124            let errors = ServoErrorChannel::default();
1125            let (sender, receiver) =
1126                generic_channel::channel().expect("Failed to create IPC channel");
1127            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1128            drop(request);
1129            assert_eq!(receiver.try_recv().ok(), Some(default_response));
1130            assert_eq!(receiver.try_recv().ok(), None);
1131            assert!(errors.try_recv().is_none());
1132
1133            // Explicit allow when receiver disconnected yields error
1134            let errors = ServoErrorChannel::default();
1135            let (sender, receiver) =
1136                generic_channel::channel().expect("Failed to create IPC channel");
1137            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1138            drop(receiver);
1139            request.allow();
1140            assert!(errors.try_recv().is_some());
1141
1142            // Explicit deny when receiver disconnected yields error
1143            let errors = ServoErrorChannel::default();
1144            let (sender, receiver) =
1145                generic_channel::channel().expect("Failed to create IPC channel");
1146            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1147            drop(receiver);
1148            request.deny();
1149            assert!(errors.try_recv().is_some());
1150
1151            // No response when receiver disconnected yields no error
1152            let errors = ServoErrorChannel::default();
1153            let (sender, receiver) =
1154                generic_channel::channel().expect("Failed to create IPC channel");
1155            let request = AllowOrDenyRequest::new(sender, default_response, errors.sender());
1156            drop(receiver);
1157            drop(request);
1158            assert!(errors.try_recv().is_none());
1159        }
1160    }
1161
1162    #[test]
1163    fn test_authentication_request() {
1164        use crate::responders::ServoErrorChannel;
1165
1166        let url = Url::parse("https://example.com").expect("Guaranteed by argument");
1167
1168        // Explicit response yields that response and nothing else
1169        let errors = ServoErrorChannel::default();
1170        let (sender, mut receiver) = tokio::sync::oneshot::channel();
1171        let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
1172        request.authenticate("diffie".to_owned(), "hunter2".to_owned());
1173        assert_eq!(
1174            receiver.try_recv().ok(),
1175            Some(Some(AuthenticationResponse {
1176                username: "diffie".to_owned(),
1177                password: "hunter2".to_owned(),
1178            }))
1179        );
1180        assert_eq!(receiver.try_recv().ok(), None);
1181        assert!(errors.try_recv().is_none());
1182
1183        // No response yields None response and nothing else
1184        let errors = ServoErrorChannel::default();
1185        let (sender, mut receiver) = tokio::sync::oneshot::channel();
1186        let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
1187        drop(request);
1188        assert_eq!(receiver.try_recv().ok(), Some(None));
1189        assert_eq!(receiver.try_recv().ok(), None);
1190        assert!(errors.try_recv().is_none());
1191
1192        // Explicit response when receiver disconnected yields error
1193        let errors = ServoErrorChannel::default();
1194        let (sender, receiver) = tokio::sync::oneshot::channel();
1195        let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
1196        drop(receiver);
1197        request.authenticate("diffie".to_owned(), "hunter2".to_owned());
1198        assert!(errors.try_recv().is_some());
1199
1200        // No response when receiver disconnected yields no error
1201        let errors = ServoErrorChannel::default();
1202        let (sender, receiver) = tokio::sync::oneshot::channel();
1203        let request = AuthenticationRequest::new(url.clone(), false, sender, errors.sender());
1204        drop(receiver);
1205        drop(request);
1206        assert!(errors.try_recv().is_none());
1207    }
1208
1209    #[test]
1210    fn test_web_resource_load() {
1211        use http::{HeaderMap, Method, StatusCode};
1212
1213        use crate::responders::ServoErrorChannel;
1214
1215        let web_resource_request = || WebResourceRequest {
1216            method: Method::GET,
1217            headers: HeaderMap::default(),
1218            url: Url::parse("https://example.com").expect("Guaranteed by argument"),
1219            destination: content_security_policy::Destination::Document,
1220            referrer_url: None,
1221            is_for_main_frame: false,
1222            is_redirect: false,
1223        };
1224        let web_resource_response = || {
1225            WebResourceResponse::new(
1226                Url::parse("https://diffie.test").expect("Guaranteed by argument"),
1227            )
1228            .status_code(StatusCode::IM_A_TEAPOT)
1229        };
1230
1231        // Explicit intercept with explicit cancel yields Start and Cancel and nothing else
1232        let errors = ServoErrorChannel::default();
1233        let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
1234        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1235        request.intercept(web_resource_response()).cancel();
1236        assert!(matches!(
1237            receiver.try_recv(),
1238            Ok(WebResourceResponseMsg::Start(_))
1239        ));
1240        assert!(matches!(
1241            receiver.try_recv(),
1242            Ok(WebResourceResponseMsg::CancelLoad)
1243        ));
1244        assert!(matches!(receiver.try_recv(), Err(_)));
1245        assert!(errors.try_recv().is_none());
1246
1247        // Explicit intercept with no further action yields Start and FinishLoad and nothing else
1248        let errors = ServoErrorChannel::default();
1249        let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
1250        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1251        drop(request.intercept(web_resource_response()));
1252        assert!(matches!(
1253            receiver.try_recv(),
1254            Ok(WebResourceResponseMsg::Start(_))
1255        ));
1256        assert!(matches!(
1257            receiver.try_recv(),
1258            Ok(WebResourceResponseMsg::FinishLoad)
1259        ));
1260        assert!(matches!(receiver.try_recv(), Err(_)));
1261        assert!(errors.try_recv().is_none());
1262
1263        // No response yields DoNotIntercept and nothing else
1264        let errors = ServoErrorChannel::default();
1265        let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
1266        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1267        drop(request);
1268        assert!(matches!(
1269            receiver.try_recv(),
1270            Ok(WebResourceResponseMsg::DoNotIntercept)
1271        ));
1272        assert!(matches!(receiver.try_recv(), Err(_)));
1273        assert!(errors.try_recv().is_none());
1274
1275        // Explicit intercept with explicit cancel when receiver disconnected yields error
1276        let errors = ServoErrorChannel::default();
1277        let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
1278        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1279        drop(receiver);
1280        request.intercept(web_resource_response()).cancel();
1281        assert!(errors.try_recv().is_some());
1282
1283        // Explicit intercept with no further action when receiver disconnected yields error
1284        let errors = ServoErrorChannel::default();
1285        let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
1286        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1287        drop(receiver);
1288        drop(request.intercept(web_resource_response()));
1289        assert!(errors.try_recv().is_some());
1290
1291        // No response when receiver disconnected yields no error
1292        let errors = ServoErrorChannel::default();
1293        let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
1294        let request = WebResourceLoad::new(web_resource_request(), sender, errors.sender());
1295        drop(receiver);
1296        drop(request);
1297        assert!(errors.try_recv().is_none());
1298    }
1299}