servo/
webview_delegate.rs

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