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