servo/
webview_delegate.rs

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