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