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