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