servo/
webview.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::cell::{Ref, RefCell, RefMut};
6use std::hash::Hash;
7use std::rc::{Rc, Weak};
8use std::time::Duration;
9
10use base::generic_channel::GenericSender;
11use base::id::WebViewId;
12use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
13use dpi::PhysicalSize;
14use embedder_traits::{
15    ContextMenuAction, ContextMenuItem, Cursor, EmbedderControlId, EmbedderControlRequest, Image,
16    InputEvent, InputEventAndId, InputEventId, JSValue, JavaScriptEvaluationError, LoadStatus,
17    MediaSessionActionType, NewWebViewDetails, ScreenGeometry, ScreenshotCaptureError, Scroll,
18    Theme, TraversalId, ViewportDetails, WebViewPoint, WebViewRect,
19};
20use euclid::{Scale, Size2D};
21use image::RgbaImage;
22use paint_api::WebViewTrait;
23use paint_api::rendering_context::RenderingContext;
24use servo_geometry::DeviceIndependentPixel;
25use servo_url::ServoUrl;
26use style_traits::CSSPixel;
27use url::Url;
28use webrender_api::units::{DeviceIntRect, DevicePixel, DevicePoint, DeviceSize};
29
30use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
31use crate::responders::IpcResponder;
32use crate::webview_delegate::{CreateNewWebViewRequest, DefaultWebViewDelegate, WebViewDelegate};
33use crate::{
34    ColorPicker, ContextMenu, EmbedderControl, InputMethodControl, SelectElement, Servo,
35    UserContentManager, WebRenderDebugOption,
36};
37
38pub(crate) const MINIMUM_WEBVIEW_SIZE: Size2D<i32, DevicePixel> = Size2D::new(1, 1);
39
40/// A handle to a Servo webview. If you clone this handle, it does not create a new webview,
41/// but instead creates a new handle to the webview. Once the last handle is dropped, Servo
42/// considers that the webview has closed and will clean up all associated resources related
43/// to this webview.
44///
45/// ## Rendering Model
46///
47/// Every [`WebView`] has a [`RenderingContext`](crate::RenderingContext). The embedder manages when
48/// the contents of the [`WebView`] paint to the [`RenderingContext`](crate::RenderingContext). When
49/// a [`WebView`] needs to be painted, for instance, because its contents have changed, Servo will
50/// call [`WebViewDelegate::notify_new_frame_ready`] in order to signal that it is time to repaint
51/// the [`WebView`] using [`WebView::paint`].
52///
53/// An example of how this flow might work is:
54///
55/// 1. [`WebViewDelegate::notify_new_frame_ready`] is called. The applications triggers a request
56///    to repaint the window that contains this [`WebView`].
57/// 2. During window repainting, the application calls [`WebView::paint`] and the contents of the
58///    [`RenderingContext`][crate::RenderingContext] are updated.
59/// 3. If the [`RenderingContext`][crate::RenderingContext] is double-buffered, the
60///    application then calls [`crate::RenderingContext::present()`] in order to swap the back buffer
61///    to the front, finally displaying the updated [`WebView`] contents.
62///
63/// In cases where the [`WebView`] contents have not been updated, but a repaint is necessary, for
64/// instance when repainting a window due to damage, an application may simply perform the final two
65/// steps and Servo will repaint even without first calling the
66/// [`WebViewDelegate::notify_new_frame_ready`] method.
67#[derive(Clone)]
68pub struct WebView(Rc<RefCell<WebViewInner>>);
69
70impl PartialEq for WebView {
71    fn eq(&self, other: &Self) -> bool {
72        self.inner().id == other.inner().id
73    }
74}
75
76impl Hash for WebView {
77    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
78        self.inner().id.hash(state);
79    }
80}
81
82pub(crate) struct WebViewInner {
83    pub(crate) id: WebViewId,
84    pub(crate) servo: Servo,
85    pub(crate) delegate: Rc<dyn WebViewDelegate>,
86    pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>,
87
88    rendering_context: Rc<dyn RenderingContext>,
89    user_content_manager: Option<Rc<UserContentManager>>,
90    hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
91    load_status: LoadStatus,
92    status_text: Option<String>,
93    page_title: Option<String>,
94    favicon: Option<Image>,
95    focused: bool,
96    animating: bool,
97    cursor: Cursor,
98
99    /// The back / forward list of this WebView.
100    back_forward_list: Vec<Url>,
101
102    /// The current index in the back / forward list.
103    back_forward_list_index: usize,
104}
105
106impl Drop for WebViewInner {
107    fn drop(&mut self) {
108        self.servo
109            .constellation_proxy()
110            .send(EmbedderToConstellationMessage::CloseWebView(self.id));
111        self.servo.paint_mut().remove_webview(self.id);
112    }
113}
114
115impl WebView {
116    pub(crate) fn new(mut builder: WebViewBuilder) -> Self {
117        let servo = builder.servo;
118        let painter_id = servo
119            .paint_mut()
120            .register_rendering_context(builder.rendering_context.clone());
121
122        let id = WebViewId::new(painter_id);
123        let webview = Self(Rc::new(RefCell::new(WebViewInner {
124            id,
125            servo: servo.clone(),
126            rendering_context: builder.rendering_context,
127            delegate: builder.delegate,
128            clipboard_delegate: Rc::new(DefaultClipboardDelegate),
129            hidpi_scale_factor: builder.hidpi_scale_factor,
130            load_status: LoadStatus::Started,
131            status_text: None,
132            page_title: None,
133            favicon: None,
134            focused: false,
135            animating: false,
136            cursor: Cursor::Pointer,
137            back_forward_list: Default::default(),
138            back_forward_list_index: 0,
139            user_content_manager: builder.user_content_manager.clone(),
140        })));
141
142        let viewport_details = webview.viewport_details();
143        servo.paint().add_webview(
144            Box::new(ServoRendererWebView {
145                weak_handle: webview.weak_handle(),
146                id,
147            }),
148            viewport_details,
149        );
150
151        servo
152            .webviews_mut()
153            .insert(webview.id(), webview.weak_handle());
154
155        let user_content_manager_id = builder
156            .user_content_manager
157            .as_ref()
158            .map(|user_content_manager| user_content_manager.id());
159
160        let new_webview_details = NewWebViewDetails {
161            webview_id: webview.id(),
162            viewport_details,
163            user_content_manager_id,
164        };
165
166        // There are two possibilities here. Either the WebView is a new toplevel
167        // WebView in which case `Self::create_new_webview_responder` is `None` or this
168        // is the response to a `WebViewDelegate::request_create_new` method in which
169        // case script expects that we just return the information directly back to
170        // the `ScriptThread`.
171        match builder.create_new_webview_responder.as_mut() {
172            Some(responder) => {
173                let _ = responder.send(Some(new_webview_details));
174            },
175            None => {
176                let url = builder.url.unwrap_or(
177                    Url::parse("about:blank")
178                        .expect("Should always be able to parse 'about:blank'."),
179                );
180
181                servo
182                    .constellation_proxy()
183                    .send(EmbedderToConstellationMessage::NewWebView(
184                        url.into(),
185                        new_webview_details,
186                    ));
187            },
188        }
189
190        webview
191    }
192
193    fn inner(&self) -> Ref<'_, WebViewInner> {
194        self.0.borrow()
195    }
196
197    fn inner_mut(&self) -> RefMut<'_, WebViewInner> {
198        self.0.borrow_mut()
199    }
200
201    pub(crate) fn request_create_new(
202        &self,
203        response_sender: GenericSender<Option<NewWebViewDetails>>,
204    ) {
205        let request = CreateNewWebViewRequest {
206            servo: self.inner().servo.clone(),
207            responder: IpcResponder::new(response_sender, None),
208        };
209        self.delegate().request_create_new(self.clone(), request);
210    }
211
212    pub(crate) fn viewport_details(&self) -> ViewportDetails {
213        // The division by 1 represents the page's default zoom of 100%,
214        // and gives us the appropriate CSSPixel type for the viewport.
215        let inner = self.inner();
216        let scaled_viewport_size =
217            inner.rendering_context.size2d().to_f32() / inner.hidpi_scale_factor;
218        ViewportDetails {
219            size: scaled_viewport_size / Scale::new(1.0),
220            hidpi_scale_factor: Scale::new(inner.hidpi_scale_factor.0),
221        }
222    }
223
224    pub(crate) fn from_weak_handle(inner: &Weak<RefCell<WebViewInner>>) -> Option<Self> {
225        inner.upgrade().map(WebView)
226    }
227
228    pub(crate) fn weak_handle(&self) -> Weak<RefCell<WebViewInner>> {
229        Rc::downgrade(&self.0)
230    }
231
232    pub fn delegate(&self) -> Rc<dyn WebViewDelegate> {
233        self.inner().delegate.clone()
234    }
235
236    pub fn set_delegate(&self, delegate: Rc<dyn WebViewDelegate>) {
237        self.inner_mut().delegate = delegate;
238    }
239
240    pub fn clipboard_delegate(&self) -> Rc<dyn ClipboardDelegate> {
241        self.inner().clipboard_delegate.clone()
242    }
243
244    pub fn set_clipboard_delegate(&self, delegate: Rc<dyn ClipboardDelegate>) {
245        self.inner_mut().clipboard_delegate = delegate;
246    }
247
248    pub fn id(&self) -> WebViewId {
249        self.inner().id
250    }
251
252    pub fn load_status(&self) -> LoadStatus {
253        self.inner().load_status
254    }
255
256    pub(crate) fn set_load_status(self, new_value: LoadStatus) {
257        if self.inner().load_status == new_value {
258            return;
259        }
260        self.inner_mut().load_status = new_value;
261        self.delegate().notify_load_status_changed(self, new_value);
262    }
263
264    pub fn url(&self) -> Option<Url> {
265        let inner = self.inner();
266        inner
267            .back_forward_list
268            .get(inner.back_forward_list_index)
269            .cloned()
270    }
271
272    pub fn status_text(&self) -> Option<String> {
273        self.inner().status_text.clone()
274    }
275
276    pub(crate) fn set_status_text(self, new_value: Option<String>) {
277        if self.inner().status_text == new_value {
278            return;
279        }
280        self.inner_mut().status_text = new_value.clone();
281        self.delegate().notify_status_text_changed(self, new_value);
282    }
283
284    pub fn page_title(&self) -> Option<String> {
285        self.inner().page_title.clone()
286    }
287
288    pub(crate) fn set_page_title(self, new_value: Option<String>) {
289        if self.inner().page_title == new_value {
290            return;
291        }
292        self.inner_mut().page_title = new_value.clone();
293        self.delegate().notify_page_title_changed(self, new_value);
294    }
295
296    pub fn favicon(&self) -> Option<Ref<'_, Image>> {
297        Ref::filter_map(self.inner(), |inner| inner.favicon.as_ref()).ok()
298    }
299
300    pub(crate) fn set_favicon(self, new_value: Image) {
301        self.inner_mut().favicon = Some(new_value);
302        self.delegate().notify_favicon_changed(self);
303    }
304
305    pub fn focused(&self) -> bool {
306        self.inner().focused
307    }
308
309    pub(crate) fn set_focused(self, new_value: bool) {
310        if self.inner().focused == new_value {
311            return;
312        }
313        self.inner_mut().focused = new_value;
314        self.delegate().notify_focus_changed(self, new_value);
315    }
316
317    pub fn cursor(&self) -> Cursor {
318        self.inner().cursor
319    }
320
321    pub(crate) fn set_cursor(self, new_value: Cursor) {
322        if self.inner().cursor == new_value {
323            return;
324        }
325        self.inner_mut().cursor = new_value;
326        self.delegate().notify_cursor_changed(self, new_value);
327    }
328
329    pub fn focus(&self) {
330        self.inner()
331            .servo
332            .constellation_proxy()
333            .send(EmbedderToConstellationMessage::FocusWebView(self.id()));
334    }
335
336    pub fn blur(&self) {
337        self.inner()
338            .servo
339            .constellation_proxy()
340            .send(EmbedderToConstellationMessage::BlurWebView);
341    }
342
343    /// Whether or not this [`WebView`] has animating content, such as a CSS animation or
344    /// transition or is running `requestAnimationFrame` callbacks. This indicates that the
345    /// embedding application should be spinning the Servo event loop on regular intervals
346    /// in order to trigger animation updates.
347    pub fn animating(self) -> bool {
348        self.inner().animating
349    }
350
351    pub(crate) fn set_animating(self, new_value: bool) {
352        if self.inner().animating == new_value {
353            return;
354        }
355        self.inner_mut().animating = new_value;
356        self.delegate().notify_animating_changed(self, new_value);
357    }
358
359    /// The size of this [`WebView`]'s [`RenderingContext`].
360    pub fn size(&self) -> DeviceSize {
361        self.inner().rendering_context.size2d().to_f32()
362    }
363
364    /// Request that the given [`WebView`]'s [`RenderingContext`] be resized. Note that the
365    /// minimum size for a WebView is 1 pixel by 1 pixel so any requested size will be
366    /// clamped by that value.
367    ///
368    /// This will also resize any other [`WebView`] using the same [`RenderingContext`]. A
369    /// [`WebView`] is always as big as its [`RenderingContext`].
370    pub fn resize(&self, new_size: PhysicalSize<u32>) {
371        let new_size = PhysicalSize {
372            width: new_size.width.max(MINIMUM_WEBVIEW_SIZE.width as u32),
373            height: new_size.height.max(MINIMUM_WEBVIEW_SIZE.height as u32),
374        };
375
376        self.inner()
377            .servo
378            .paint()
379            .resize_rendering_context(self.id(), new_size);
380    }
381
382    pub fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
383        self.inner().hidpi_scale_factor
384    }
385
386    pub fn set_hidpi_scale_factor(
387        &self,
388        new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
389    ) {
390        if self.inner().hidpi_scale_factor == new_scale_factor {
391            return;
392        }
393
394        self.inner_mut().hidpi_scale_factor = new_scale_factor;
395        self.inner()
396            .servo
397            .paint()
398            .set_hidpi_scale_factor(self.id(), new_scale_factor);
399    }
400
401    pub fn show(&self) {
402        self.inner()
403            .servo
404            .paint()
405            .show_webview(self.id())
406            .expect("BUG: invalid WebView instance");
407    }
408
409    pub fn hide(&self) {
410        self.inner()
411            .servo
412            .paint()
413            .hide_webview(self.id())
414            .expect("BUG: invalid WebView instance");
415    }
416
417    pub fn notify_theme_change(&self, theme: Theme) {
418        self.inner()
419            .servo
420            .constellation_proxy()
421            .send(EmbedderToConstellationMessage::ThemeChange(
422                self.id(),
423                theme,
424            ))
425    }
426
427    pub fn load(&self, url: Url) {
428        self.inner()
429            .servo
430            .constellation_proxy()
431            .send(EmbedderToConstellationMessage::LoadUrl(
432                self.id(),
433                url.into(),
434            ))
435    }
436
437    pub fn reload(&self) {
438        self.inner_mut().load_status = LoadStatus::Started;
439        self.inner()
440            .servo
441            .constellation_proxy()
442            .send(EmbedderToConstellationMessage::Reload(self.id()))
443    }
444
445    pub fn can_go_back(&self) -> bool {
446        self.inner().back_forward_list_index != 0
447    }
448
449    pub fn go_back(&self, amount: usize) -> TraversalId {
450        let traversal_id = TraversalId::new();
451        self.inner().servo.constellation_proxy().send(
452            EmbedderToConstellationMessage::TraverseHistory(
453                self.id(),
454                TraversalDirection::Back(amount),
455                traversal_id.clone(),
456            ),
457        );
458        traversal_id
459    }
460
461    pub fn can_go_forward(&self) -> bool {
462        let inner = self.inner();
463        inner.back_forward_list.len() > inner.back_forward_list_index + 1
464    }
465
466    pub fn go_forward(&self, amount: usize) -> TraversalId {
467        let traversal_id = TraversalId::new();
468        self.inner().servo.constellation_proxy().send(
469            EmbedderToConstellationMessage::TraverseHistory(
470                self.id(),
471                TraversalDirection::Forward(amount),
472                traversal_id.clone(),
473            ),
474        );
475        traversal_id
476    }
477
478    /// Ask the [`WebView`] to scroll web content. Note that positive scroll offsets reveal more
479    /// content on the bottom and right of the page.
480    pub fn notify_scroll_event(&self, scroll: Scroll, point: WebViewPoint) {
481        self.inner()
482            .servo
483            .paint()
484            .notify_scroll_event(self.id(), scroll, point);
485    }
486
487    pub fn notify_input_event(&self, event: InputEvent) -> InputEventId {
488        let event: InputEventAndId = event.into();
489        let event_id = event.id;
490
491        // Events with a `point` first go to `Paint` for hit testing.
492        if event.event.point().is_some() {
493            self.inner()
494                .servo
495                .paint()
496                .notify_input_event(self.id(), event);
497        } else {
498            self.inner().servo.constellation_proxy().send(
499                EmbedderToConstellationMessage::ForwardInputEvent(
500                    self.id(),
501                    event,
502                    None, /* hit_test */
503                ),
504            );
505        }
506
507        event_id
508    }
509
510    pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) {
511        self.inner()
512            .servo
513            .constellation_proxy()
514            .send(EmbedderToConstellationMessage::MediaSessionAction(event));
515    }
516
517    /// Set the page zoom of the [`WebView`]. This sets the final page zoom value of the
518    /// [`WebView`]. Unlike [`WebView::pinch_zoom`] *it is not* multiplied by the current
519    /// page zoom value, but overrides it.
520    ///
521    /// [`WebView`]s have two types of zoom, pinch zoom and page zoom. This adjusts page
522    /// zoom, which will adjust the `devicePixelRatio` of the page and cause it to modify
523    /// its layout.
524    ///
525    /// These values will be clamped internally. The values used for clamping can be
526    /// adjusted by page content when `<meta viewport>` parsing is enabled via
527    /// `Prefs::viewport_meta_enabled`.
528    pub fn set_page_zoom(&self, new_zoom: f32) {
529        self.inner()
530            .servo
531            .paint()
532            .set_page_zoom(self.id(), new_zoom);
533    }
534
535    /// Get the page zoom of the [`WebView`].
536    pub fn page_zoom(&self) -> f32 {
537        self.inner().servo.paint().page_zoom(self.id())
538    }
539
540    /// Adjust the pinch zoom on this [`WebView`] multiplying the current pinch zoom
541    /// level with the provided `pinch_zoom_delta`.
542    ///
543    /// [`WebView`]s have two types of zoom, pinch zoom and page zoom. This adjusts pinch
544    /// zoom, which is a type of zoom which does not modify layout, and instead simply
545    /// magnifies the view in the viewport.
546    ///
547    /// The final pinch zoom values will be clamped to reasonable defaults (currently to
548    /// the inclusive range [1.0, 10.0]).
549    pub fn pinch_zoom(&self, pinch_zoom_delta: f32, center: DevicePoint) {
550        self.inner()
551            .servo
552            .paint()
553            .pinch_zoom(self.id(), pinch_zoom_delta, center);
554    }
555
556    pub fn device_pixels_per_css_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
557        self.inner()
558            .servo
559            .paint()
560            .device_pixels_per_page_pixel(self.id())
561    }
562
563    pub fn exit_fullscreen(&self) {
564        self.inner()
565            .servo
566            .constellation_proxy()
567            .send(EmbedderToConstellationMessage::ExitFullScreen(self.id()));
568    }
569
570    pub fn set_throttled(&self, throttled: bool) {
571        self.inner().servo.constellation_proxy().send(
572            EmbedderToConstellationMessage::SetWebViewThrottled(self.id(), throttled),
573        );
574    }
575
576    pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) {
577        self.inner().servo.paint().toggle_webrender_debug(debugging);
578    }
579
580    pub fn capture_webrender(&self) {
581        self.inner().servo.paint().capture_webrender(self.id());
582    }
583
584    pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
585        self.inner().servo.constellation_proxy().send(
586            EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration),
587        );
588    }
589
590    pub fn send_error(&self, message: String) {
591        self.inner()
592            .servo
593            .constellation_proxy()
594            .send(EmbedderToConstellationMessage::SendError(
595                Some(self.id()),
596                message,
597            ));
598    }
599
600    /// Paint the contents of this [`WebView`] into its `RenderingContext`.
601    pub fn paint(&self) {
602        self.inner().servo.paint().render(self.id());
603    }
604
605    /// Get the [`UserContentManager`] associated with this [`WebView`].
606    pub fn user_content_manager(&self) -> Option<Rc<UserContentManager>> {
607        self.inner().user_content_manager.clone()
608    }
609
610    /// Evaluate the specified string of JavaScript code. Once execution is complete or an error
611    /// occurs, Servo will call `callback`.
612    pub fn evaluate_javascript<T: ToString>(
613        &self,
614        script: T,
615        callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static,
616    ) {
617        self.inner().servo.javascript_evaluator_mut().evaluate(
618            self.id(),
619            script.to_string(),
620            Box::new(callback),
621        );
622    }
623
624    /// Asynchronously take a screenshot of the [`WebView`] contents, given a `rect` or the whole
625    /// viewport, if no `rect` is given.
626    ///
627    /// This method will wait until the [`WebView`] is ready before the screenshot is taken.
628    /// This includes waiting for:
629    ///
630    ///  - all frames to fire their `load` event.
631    ///  - all render blocking elements, such as stylesheets included via the `<link>`
632    ///    element, to stop blocking the rendering.
633    ///  - all images to be loaded and displayed.
634    ///  - all web fonts are loaded.
635    ///  - the `reftest-wait` and `test-wait` classes have been removed from the root element.
636    ///  - the rendering is up-to-date
637    ///
638    /// Once all these conditions are met and the rendering does not have any pending frames
639    /// to render, the provided `callback` will be called with the results of the screenshot
640    /// operation.
641    pub fn take_screenshot(
642        &self,
643        rect: Option<WebViewRect>,
644        callback: impl FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static,
645    ) {
646        self.inner()
647            .servo
648            .paint()
649            .request_screenshot(self.id(), rect, Box::new(callback));
650    }
651
652    pub(crate) fn set_history(self, new_back_forward_list: Vec<ServoUrl>, new_index: usize) {
653        {
654            let mut inner_mut = self.inner_mut();
655            inner_mut.back_forward_list_index = new_index;
656            inner_mut.back_forward_list = new_back_forward_list
657                .into_iter()
658                .map(ServoUrl::into_url)
659                .collect();
660        }
661
662        let back_forward_list = self.inner().back_forward_list.clone();
663        let back_forward_list_index = self.inner().back_forward_list_index;
664        self.delegate().notify_url_changed(
665            self.clone(),
666            back_forward_list[back_forward_list_index].clone(),
667        );
668        self.delegate().notify_history_changed(
669            self.clone(),
670            back_forward_list,
671            back_forward_list_index,
672        );
673    }
674
675    pub(crate) fn show_embedder_control(
676        self,
677        control_id: EmbedderControlId,
678        position: DeviceIntRect,
679        embedder_control_request: EmbedderControlRequest,
680    ) {
681        let constellation_proxy = self.inner().servo.constellation_proxy().clone();
682        let embedder_control = match embedder_control_request {
683            EmbedderControlRequest::SelectElement(options, selected_option) => {
684                EmbedderControl::SelectElement(SelectElement {
685                    id: control_id,
686                    options,
687                    selected_option,
688                    position,
689                    constellation_proxy,
690                    response_sent: false,
691                })
692            },
693            EmbedderControlRequest::ColorPicker(current_color) => {
694                EmbedderControl::ColorPicker(ColorPicker {
695                    id: control_id,
696                    current_color: Some(current_color),
697                    position,
698                    constellation_proxy,
699                    response_sent: false,
700                })
701            },
702            EmbedderControlRequest::InputMethod(input_method_request) => {
703                EmbedderControl::InputMethod(InputMethodControl {
704                    id: control_id,
705                    input_method_type: input_method_request.input_method_type,
706                    text: input_method_request.text,
707                    insertion_point: input_method_request.insertion_point,
708                    position,
709                    multiline: input_method_request.multiline,
710                })
711            },
712            EmbedderControlRequest::ContextMenu(mut context_menu_request) => {
713                for item in context_menu_request.items.iter_mut() {
714                    match item {
715                        ContextMenuItem::Item {
716                            action: ContextMenuAction::GoBack,
717                            enabled,
718                            ..
719                        } => *enabled = self.can_go_back(),
720                        ContextMenuItem::Item {
721                            action: ContextMenuAction::GoForward,
722                            enabled,
723                            ..
724                        } => *enabled = self.can_go_forward(),
725                        _ => {},
726                    }
727                }
728                EmbedderControl::ContextMenu(ContextMenu {
729                    id: control_id,
730                    position,
731                    items: context_menu_request.items,
732                    element_info: context_menu_request.element_info,
733                    constellation_proxy,
734                    response_sent: false,
735                })
736            },
737            EmbedderControlRequest::FilePicker { .. } => {
738                unreachable!("This message should be routed through the FileManagerThread")
739            },
740        };
741
742        self.delegate()
743            .show_embedder_control(self.clone(), embedder_control);
744    }
745}
746
747/// A structure used to expose a view of the [`WebView`] to the Servo
748/// renderer, without having the Servo renderer depend on the embedding layer.
749struct ServoRendererWebView {
750    id: WebViewId,
751    weak_handle: Weak<RefCell<WebViewInner>>,
752}
753
754impl WebViewTrait for ServoRendererWebView {
755    fn id(&self) -> WebViewId {
756        self.id
757    }
758
759    fn screen_geometry(&self) -> Option<ScreenGeometry> {
760        let webview = WebView::from_weak_handle(&self.weak_handle)?;
761        webview.delegate().screen_geometry(webview)
762    }
763
764    fn set_animating(&self, new_value: bool) {
765        if let Some(webview) = WebView::from_weak_handle(&self.weak_handle) {
766            webview.set_animating(new_value);
767        }
768    }
769}
770
771pub struct WebViewBuilder {
772    servo: Servo,
773    rendering_context: Rc<dyn RenderingContext>,
774    delegate: Rc<dyn WebViewDelegate>,
775    url: Option<Url>,
776    hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
777    create_new_webview_responder: Option<IpcResponder<Option<NewWebViewDetails>>>,
778    user_content_manager: Option<Rc<UserContentManager>>,
779}
780
781impl WebViewBuilder {
782    pub fn new(servo: &Servo, rendering_context: Rc<dyn RenderingContext>) -> Self {
783        Self {
784            servo: servo.clone(),
785            rendering_context,
786            url: None,
787            hidpi_scale_factor: Scale::new(1.0),
788            delegate: Rc::new(DefaultWebViewDelegate),
789            create_new_webview_responder: None,
790            user_content_manager: None,
791        }
792    }
793
794    pub(crate) fn new_for_create_request(
795        servo: &Servo,
796        rendering_context: Rc<dyn RenderingContext>,
797        responder: IpcResponder<Option<NewWebViewDetails>>,
798    ) -> Self {
799        let mut builder = Self::new(servo, rendering_context);
800        builder.create_new_webview_responder = Some(responder);
801        builder
802    }
803
804    pub fn delegate(mut self, delegate: Rc<dyn WebViewDelegate>) -> Self {
805        self.delegate = delegate;
806        self
807    }
808
809    pub fn url(mut self, url: Url) -> Self {
810        self.url = Some(url);
811        self
812    }
813
814    pub fn hidpi_scale_factor(
815        mut self,
816        hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
817    ) -> Self {
818        self.hidpi_scale_factor = hidpi_scale_factor;
819        self
820    }
821
822    /// Set the [`UserContentManager`] for the `WebView` being created. The same
823    /// `UserContentManager` can be shared among multiple `WebView`s. Any updates
824    /// to the `UserContentManager` will take effect only after the document is reloaded>
825    pub fn user_content_manager(mut self, user_content_manager: Rc<UserContentManager>) -> Self {
826        self.user_content_manager = Some(user_content_manager);
827        self
828    }
829
830    pub fn build(self) -> WebView {
831        WebView::new(self)
832    }
833}