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