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