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