Skip to main content

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 accesskit::{
11    Node as AccesskitNode, NodeId, Role, Tree, TreeId, TreeUpdate, Uuid as AccesskitUuid,
12};
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, UrlRequest, ViewportDetails, WebViewPoint, WebViewRect,
19};
20use euclid::{Scale, Size2D};
21use image::RgbaImage;
22use log::debug;
23use paint_api::WebViewTrait;
24use paint_api::rendering_context::RenderingContext;
25use servo_base::Epoch;
26use servo_base::generic_channel::GenericSender;
27use servo_base::id::WebViewId;
28use servo_config::pref;
29use servo_constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
30use servo_geometry::DeviceIndependentPixel;
31use servo_url::ServoUrl;
32use style_traits::CSSPixel;
33use url::Url;
34use webrender_api::units::{DeviceIntRect, DevicePixel, DevicePoint, DeviceSize};
35
36use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
37#[cfg(feature = "gamepad")]
38use crate::gamepad_delegate::{DefaultGamepadDelegate, GamepadDelegate};
39use crate::responders::IpcResponder;
40use crate::servo::PendingHandledInputEvent;
41use crate::webview_delegate::{CreateNewWebViewRequest, DefaultWebViewDelegate, WebViewDelegate};
42use crate::{
43    ColorPicker, ContextMenu, EmbedderControl, InputMethodControl, SelectElement, Servo,
44    UserContentManager, WebRenderDebugOption,
45};
46
47pub(crate) const MINIMUM_WEBVIEW_SIZE: Size2D<i32, DevicePixel> = Size2D::new(1, 1);
48
49/// A handle to a Servo webview. If you clone this handle, it does not create a new webview,
50/// but instead creates a new handle to the webview. Once the last handle is dropped, Servo
51/// considers that the webview has closed and will clean up all associated resources related
52/// to this webview.
53///
54/// ## Creating a WebView
55///
56/// To create a [`WebView`], use [`WebViewBuilder`].
57///
58/// ## Rendering Model
59///
60/// Every [`WebView`] has a [`RenderingContext`]. The embedder manages when
61/// the contents of the [`WebView`] paint to the [`RenderingContext`]. When
62/// a [`WebView`] needs to be painted, for instance, because its contents have changed, Servo will
63/// call [`WebViewDelegate::notify_new_frame_ready`] in order to signal that it is time to repaint
64/// the [`WebView`] using [`WebView::paint`].
65///
66/// An example of how this flow might work is:
67///
68/// 1. [`WebViewDelegate::notify_new_frame_ready`] is called. The applications triggers a request
69///    to repaint the window that contains this [`WebView`].
70/// 2. During window repainting, the application calls [`WebView::paint`] and the contents of the
71///    [`RenderingContext`] are updated.
72/// 3. If the [`RenderingContext`] is double-buffered, the
73///    application then calls [`crate::RenderingContext::present()`] in order to swap the back buffer
74///    to the front, finally displaying the updated [`WebView`] contents.
75///
76/// In cases where the [`WebView`] contents have not been updated, but a repaint is necessary, for
77/// instance when repainting a window due to damage, an application may simply perform the final two
78/// steps and Servo will repaint even without first calling the
79/// [`WebViewDelegate::notify_new_frame_ready`] method.
80#[derive(Clone)]
81pub struct WebView(Rc<RefCell<WebViewInner>>);
82
83impl PartialEq for WebView {
84    fn eq(&self, other: &Self) -> bool {
85        self.inner().id == other.inner().id
86    }
87}
88
89impl Hash for WebView {
90    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
91        self.inner().id.hash(state);
92    }
93}
94
95pub(crate) struct WebViewInner {
96    pub(crate) id: WebViewId,
97    pub(crate) servo: Servo,
98    pub(crate) delegate: Rc<dyn WebViewDelegate>,
99    pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>,
100    #[cfg(feature = "gamepad")]
101    pub(crate) gamepad_delegate: Rc<dyn GamepadDelegate>,
102
103    /// AccessKit subtree id for this [`WebView`], if accessibility is active.
104    ///
105    /// Set by [`WebView::set_accessibility_active()`], and forwarded to the constellation via
106    /// [`EmbedderToConstellationMessage::SetAccessibilityActive`].
107    pub(crate) accesskit_tree_id: Option<TreeId>,
108    /// [`TreeId`] of the web contents of this [`WebView`]’s active top-level pipeline,
109    /// which is grafted into the tree for this [`WebView`].
110    pub(crate) grafted_accesskit_tree_id: Option<TreeId>,
111    /// A counter for changes to the grafted accesskit tree for this webview.
112    /// See [`Self::grafted_accesskit_tree_id`].
113    grafted_accesskit_tree_epoch: Option<Epoch>,
114
115    rendering_context: Rc<dyn RenderingContext>,
116    user_content_manager: Option<Rc<UserContentManager>>,
117    hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
118    load_status: LoadStatus,
119    status_text: Option<String>,
120    page_title: Option<String>,
121    favicon: Option<Image>,
122    focused: bool,
123    animating: bool,
124    cursor: Cursor,
125
126    /// The back / forward list of this WebView.
127    back_forward_list: Vec<Url>,
128
129    /// The current index in the back / forward list.
130    back_forward_list_index: usize,
131}
132
133impl Drop for WebViewInner {
134    fn drop(&mut self) {
135        self.servo
136            .constellation_proxy()
137            .send(EmbedderToConstellationMessage::CloseWebView(self.id));
138        self.servo.paint_mut().remove_webview(self.id);
139    }
140}
141
142impl WebView {
143    pub(crate) fn new(mut builder: WebViewBuilder) -> Self {
144        let servo = builder.servo;
145        let painter_id = servo
146            .paint_mut()
147            .register_rendering_context(builder.rendering_context.clone());
148
149        let id = WebViewId::new(painter_id);
150        let webview = Self(Rc::new(RefCell::new(WebViewInner {
151            id,
152            servo: servo.clone(),
153            rendering_context: builder.rendering_context,
154            delegate: builder.delegate,
155            clipboard_delegate: builder
156                .clipboard_delegate
157                .unwrap_or_else(|| Rc::new(DefaultClipboardDelegate)),
158            #[cfg(feature = "gamepad")]
159            gamepad_delegate: builder
160                .gamepad_delegate
161                .unwrap_or_else(|| Rc::new(DefaultGamepadDelegate)),
162            accesskit_tree_id: None,
163            grafted_accesskit_tree_id: None,
164            grafted_accesskit_tree_epoch: None,
165            hidpi_scale_factor: builder.hidpi_scale_factor,
166            load_status: LoadStatus::Started,
167            status_text: None,
168            page_title: None,
169            favicon: None,
170            focused: false,
171            animating: false,
172            cursor: Cursor::Pointer,
173            back_forward_list: Default::default(),
174            back_forward_list_index: 0,
175            user_content_manager: builder.user_content_manager.clone(),
176        })));
177
178        let viewport_details = webview.viewport_details();
179        servo.paint().add_webview(
180            Box::new(ServoRendererWebView {
181                weak_handle: webview.weak_handle(),
182                id,
183            }),
184            viewport_details,
185        );
186
187        servo
188            .webviews_mut()
189            .insert(webview.id(), webview.weak_handle());
190
191        let user_content_manager_id = builder
192            .user_content_manager
193            .as_ref()
194            .map(|user_content_manager| user_content_manager.id());
195
196        let new_webview_details = NewWebViewDetails {
197            webview_id: webview.id(),
198            viewport_details,
199            user_content_manager_id,
200        };
201
202        // There are two possibilities here. Either the WebView is a new toplevel
203        // WebView in which case `Self::create_new_webview_responder` is `None` or this
204        // is the response to a `WebViewDelegate::request_create_new` method in which
205        // case script expects that we just return the information directly back to
206        // the `ScriptThread`.
207        match builder.create_new_webview_responder.as_mut() {
208            Some(responder) => {
209                let _ = responder.send(Some(new_webview_details));
210            },
211            None => {
212                let url = builder.url.unwrap_or(
213                    Url::parse("about:blank")
214                        .expect("Should always be able to parse 'about:blank'."),
215                );
216
217                servo
218                    .constellation_proxy()
219                    .send(EmbedderToConstellationMessage::NewWebView(
220                        url.into(),
221                        new_webview_details,
222                    ));
223            },
224        }
225
226        webview
227    }
228
229    fn inner(&self) -> Ref<'_, WebViewInner> {
230        self.0.borrow()
231    }
232
233    fn inner_mut(&self) -> RefMut<'_, WebViewInner> {
234        self.0.borrow_mut()
235    }
236
237    pub(crate) fn request_create_new(
238        &self,
239        response_sender: GenericSender<Option<NewWebViewDetails>>,
240    ) {
241        let request = CreateNewWebViewRequest {
242            servo: self.inner().servo.clone(),
243            responder: IpcResponder::new(response_sender, None),
244        };
245        self.delegate().request_create_new(self.clone(), request);
246    }
247
248    pub(crate) fn viewport_details(&self) -> ViewportDetails {
249        // The division by 1 represents the page's default zoom of 100%,
250        // and gives us the appropriate CSSPixel type for the viewport.
251        let inner = self.inner();
252        let viewport_size = inner.rendering_context.size2d().to_f32();
253        let scaled_viewport_size = viewport_size / inner.hidpi_scale_factor;
254        let device_size = self
255            .delegate()
256            .screen_geometry(self.clone())
257            .map(|geometry| geometry.size.to_f32())
258            .unwrap_or_else(|| viewport_size);
259        ViewportDetails {
260            size: scaled_viewport_size / Scale::new(1.0),
261            hidpi_scale_factor: Scale::new(inner.hidpi_scale_factor.0),
262            device_size,
263        }
264    }
265
266    pub(crate) fn from_weak_handle(inner: &Weak<RefCell<WebViewInner>>) -> Option<Self> {
267        inner.upgrade().map(WebView)
268    }
269
270    pub(crate) fn weak_handle(&self) -> Weak<RefCell<WebViewInner>> {
271        Rc::downgrade(&self.0)
272    }
273
274    /// Get the [`WebViewDelegate`] associated with this [`WebView`].
275    pub fn delegate(&self) -> Rc<dyn WebViewDelegate> {
276        self.inner().delegate.clone()
277    }
278
279    /// Get the [`ClipboardDelegate`] associated with this [`WebView`].
280    pub fn clipboard_delegate(&self) -> Rc<dyn ClipboardDelegate> {
281        self.inner().clipboard_delegate.clone()
282    }
283
284    /// Get the [`GamepadDelegate`] associated with this [`WebView`].
285    #[cfg(feature = "gamepad")]
286    pub fn gamepad_delegate(&self) -> Rc<dyn GamepadDelegate> {
287        self.inner().gamepad_delegate.clone()
288    }
289
290    /// Get the unique identifier for this [`WebView`].
291    pub fn id(&self) -> WebViewId {
292        self.inner().id
293    }
294
295    /// Get the [`RenderingContext`] associated with this [`WebView`].
296    pub fn rendering_context(&self) -> Rc<dyn RenderingContext> {
297        self.inner().rendering_context.clone()
298    }
299
300    /// Get the load status for the page that is currently loading or loaded in this [`WebView`].
301    ///
302    /// The embedder can use [`WebViewDelegate::notify_load_status_changed`] to subscribe
303    /// to changes in the load status.
304    pub fn load_status(&self) -> LoadStatus {
305        self.inner().load_status
306    }
307
308    pub(crate) fn set_load_status(self, new_value: LoadStatus) {
309        if self.inner().load_status == new_value {
310            return;
311        }
312        self.inner_mut().load_status = new_value;
313        self.delegate().notify_load_status_changed(self, new_value);
314    }
315
316    /// Get the URL of the currently active page in this [`WebView`]'s navigation history.
317    /// Returns `None` if no page is currently loaded.
318    pub fn url(&self) -> Option<Url> {
319        let inner = self.inner();
320        inner
321            .back_forward_list
322            .get(inner.back_forward_list_index)
323            .cloned()
324    }
325
326    /// Get the current status text for this [`WebView`]. Returns `None` if there is no status text.
327    ///
328    /// The status text changes as the user interacts with the page, for example, by hovering over
329    /// a link. The embedder can use [`WebViewDelegate::notify_status_text_changed`] to subscribe
330    /// to changes in the status text.
331    pub fn status_text(&self) -> Option<String> {
332        self.inner().status_text.clone()
333    }
334
335    pub(crate) fn set_status_text(self, new_value: Option<String>) {
336        if self.inner().status_text == new_value {
337            return;
338        }
339        self.inner_mut().status_text = new_value.clone();
340        self.delegate().notify_status_text_changed(self, new_value);
341    }
342
343    /// Get the title of the currently active page in this [`WebView`]. Returns `None` if the
344    /// page has no title.
345    ///
346    /// The embedder can use [`WebViewDelegate::notify_page_title_changed`] to subscribe
347    /// to changes in the [`WebView`]'s page title.
348    pub fn page_title(&self) -> Option<String> {
349        self.inner().page_title.clone()
350    }
351
352    pub(crate) fn set_page_title(self, new_value: Option<String>) {
353        if self.inner().page_title == new_value {
354            return;
355        }
356        self.inner_mut().page_title = new_value.clone();
357        self.delegate().notify_page_title_changed(self, new_value);
358    }
359
360    /// Get a read-only reference to the image data for the favicon of the currently
361    /// active page in this [`WebView`]. Returns `None` if no favicon is available
362    /// for the currently active page.
363    ///
364    /// The embedder can use [`WebViewDelegate::notify_favicon_changed`] to subscribe
365    /// to changes in the [`WebView`]'s favicon.
366    pub fn favicon(&self) -> Option<Ref<'_, Image>> {
367        Ref::filter_map(self.inner(), |inner| inner.favicon.as_ref()).ok()
368    }
369
370    pub(crate) fn set_favicon(self, new_value: Image) {
371        self.inner_mut().favicon = Some(new_value);
372        self.delegate().notify_favicon_changed(self);
373    }
374
375    /// Whether or not this [`WebView`] currently has the keyboard focus.
376    ///
377    /// The embedder can use [`WebViewDelegate::notify_focus_changed`] to subscribe
378    /// to changes in the  [`WebView`]'s focus state.
379    pub fn focused(&self) -> bool {
380        self.inner().focused
381    }
382
383    pub(crate) fn set_focused(self, new_value: bool) {
384        if self.inner().focused == new_value {
385            return;
386        }
387        self.inner_mut().focused = new_value;
388        self.delegate().notify_focus_changed(self, new_value);
389    }
390
391    /// Get the current [`Cursor`] for this [`WebView`].
392    ///
393    /// The cursor can change as the user interacts with page content. The embedder
394    /// can use [`WebViewDelegate::notify_cursor_changed`] to subscribe to changes in
395    /// the  [`WebView`]'s cursor.
396    pub fn cursor(&self) -> Cursor {
397        self.inner().cursor
398    }
399
400    pub(crate) fn set_cursor(self, new_value: Cursor) {
401        if self.inner().cursor == new_value {
402            return;
403        }
404        self.inner_mut().cursor = new_value;
405        self.delegate().notify_cursor_changed(self, new_value);
406    }
407
408    /// Notify Servo that this [`WebView`] has gained keyboard focus.
409    pub fn focus(&self) {
410        self.inner()
411            .servo
412            .constellation_proxy()
413            .send(EmbedderToConstellationMessage::FocusWebView(self.id()));
414    }
415
416    /// Notify Servo that this [`WebView`] has lost keyboard focus.
417    pub fn blur(&self) {
418        self.inner()
419            .servo
420            .constellation_proxy()
421            .send(EmbedderToConstellationMessage::BlurWebView);
422    }
423
424    /// Whether or not this [`WebView`] has animating content, such as a CSS animation or
425    /// transition or is running `requestAnimationFrame` callbacks. This indicates that the
426    /// embedding application should be spinning the Servo event loop on regular intervals
427    /// in order to trigger animation updates.
428    pub fn animating(&self) -> bool {
429        self.inner().animating
430    }
431
432    pub(crate) fn set_animating(self, new_value: bool) {
433        if self.inner().animating == new_value {
434            return;
435        }
436        self.inner_mut().animating = new_value;
437        self.delegate().notify_animating_changed(self, new_value);
438    }
439
440    /// The size of this [`WebView`]'s [`RenderingContext`].
441    pub fn size(&self) -> DeviceSize {
442        self.inner().rendering_context.size2d().to_f32()
443    }
444
445    /// Request that the given [`WebView`]'s [`RenderingContext`] be resized. Note that the
446    /// minimum size for a WebView is 1 pixel by 1 pixel so any requested size will be
447    /// clamped by that value.
448    ///
449    /// This will also resize any other [`WebView`] using the same [`RenderingContext`]. A
450    /// [`WebView`] is always as big as its [`RenderingContext`].
451    pub fn resize(&self, new_size: PhysicalSize<u32>) {
452        let new_size = PhysicalSize {
453            width: new_size.width.max(MINIMUM_WEBVIEW_SIZE.width as u32),
454            height: new_size.height.max(MINIMUM_WEBVIEW_SIZE.height as u32),
455        };
456
457        self.inner()
458            .servo
459            .paint()
460            .resize_rendering_context(self.id(), new_size);
461    }
462
463    /// Get the HiDPI scale factor for this [`WebView`].
464    pub fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
465        self.inner().hidpi_scale_factor
466    }
467
468    /// Set the HiDPI scale factor for this [`WebView`].
469    ///
470    /// This scale factor determines how device-independent pixels map to physical device pixels
471    /// and therefore depends on which device this [`WebView`] is being displayed.
472    pub fn set_hidpi_scale_factor(
473        &self,
474        new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
475    ) {
476        if self.inner().hidpi_scale_factor == new_scale_factor {
477            return;
478        }
479
480        self.inner_mut().hidpi_scale_factor = new_scale_factor;
481        self.inner()
482            .servo
483            .paint()
484            .set_hidpi_scale_factor(self.id(), new_scale_factor);
485    }
486
487    /// Make this [`WebView`] visible within its [`RenderingContext`].
488    pub fn show(&self) {
489        self.inner()
490            .servo
491            .paint()
492            .show_webview(self.id())
493            .expect("BUG: invalid WebView instance");
494    }
495
496    /// Hide this [`WebView`] within its [`RenderingContext`].
497    pub fn hide(&self) {
498        self.inner()
499            .servo
500            .paint()
501            .hide_webview(self.id())
502            .expect("BUG: invalid WebView instance");
503    }
504
505    /// Notify this [`WebView`] of a change to the system theme (e.g. light or dark mode).
506    pub fn notify_theme_change(&self, theme: Theme) {
507        self.inner()
508            .servo
509            .constellation_proxy()
510            .send(EmbedderToConstellationMessage::ThemeChange(
511                self.id(),
512                theme,
513            ))
514    }
515
516    /// Load the given URL into this [`WebView`] using the default request headers.
517    ///
518    /// This pushes a new entry onto the navigation history, so the user can navigate
519    /// back to the previous page.
520    pub fn load(&self, url: Url) {
521        self.inner()
522            .servo
523            .constellation_proxy()
524            .send(EmbedderToConstellationMessage::LoadUrl(
525                self.id(),
526                UrlRequest::new(url),
527            ))
528    }
529
530    /// Load a [`UrlRequest`] with custom headers into this [`WebView`].
531    ///
532    /// This pushes a new entry onto the navigation history, so the user can navigate
533    /// back to the previous page.
534    pub fn load_request(&self, url_request: UrlRequest) {
535        self.inner()
536            .servo
537            .constellation_proxy()
538            .send(EmbedderToConstellationMessage::LoadUrl(
539                self.id(),
540                url_request,
541            ))
542    }
543
544    /// Reload the currently loaded page in this [`WebView`].
545    pub fn reload(&self) {
546        self.inner_mut().load_status = LoadStatus::Started;
547        self.inner()
548            .servo
549            .constellation_proxy()
550            .send(EmbedderToConstellationMessage::Reload(self.id()))
551    }
552
553    /// Whether or not this [`WebView`] can go backward in its navigation history.
554    ///
555    /// This is `false` if the currently active page is the oldest entry in the
556    /// [`WebView`]'s navigation history.
557    pub fn can_go_back(&self) -> bool {
558        self.inner().back_forward_list_index != 0
559    }
560
561    /// Go backward in this [`WebView`]'s navigation history by the given number of steps.
562    ///
563    /// Returns a [`TraversalId`] that can be used with the
564    /// [`WebViewDelegate::notify_traversal_complete`] callback to determine when the
565    /// traversal is complete.
566    pub fn go_back(&self, amount: usize) -> TraversalId {
567        let traversal_id = TraversalId::new();
568        self.inner().servo.constellation_proxy().send(
569            EmbedderToConstellationMessage::TraverseHistory(
570                self.id(),
571                TraversalDirection::Back(amount),
572                traversal_id.clone(),
573            ),
574        );
575        traversal_id
576    }
577
578    /// Whether or not this [`WebView`] can go forward in its navigation history.
579    ///
580    /// This is `false` if the currently active page is the most recent entry in
581    /// the [`WebView`]'s navigation history.
582    pub fn can_go_forward(&self) -> bool {
583        let inner = self.inner();
584        inner.back_forward_list.len() > inner.back_forward_list_index + 1
585    }
586
587    /// Go forward in this [`WebView`]'s navigation history by the given number of steps.
588    ///
589    /// Returns a [`TraversalId`] that can be used with the
590    /// [`WebViewDelegate::notify_traversal_complete`] callback to determine when the
591    /// traversal is complete.
592    pub fn go_forward(&self, amount: usize) -> TraversalId {
593        let traversal_id = TraversalId::new();
594        self.inner().servo.constellation_proxy().send(
595            EmbedderToConstellationMessage::TraverseHistory(
596                self.id(),
597                TraversalDirection::Forward(amount),
598                traversal_id.clone(),
599            ),
600        );
601        traversal_id
602    }
603
604    /// Ask the [`WebView`] to scroll the scrollable area under `point` to the
605    /// given `scroll` destination.
606    pub fn notify_scroll_event(&self, scroll: Scroll, point: WebViewPoint) {
607        self.inner()
608            .servo
609            .paint()
610            .notify_scroll_event(self.id(), scroll, point);
611    }
612
613    /// Notify this [`WebView`] about an [`InputEvent`] such as a mouse click, touch
614    /// event, or key press.
615    ///
616    /// Returns an [`InputEventId`] that can be used with the
617    /// [`WebViewDelegate::notify_input_event_handled`] callback to determine the result of
618    /// processing of the event by the page content.
619    pub fn notify_input_event(&self, event: InputEvent) -> InputEventId {
620        let event: InputEventAndId = event.into();
621        let event_id = event.id;
622        let webview_id = self.id();
623        let servo = &self.inner().servo;
624        // Events with a `point` first go to `Paint` for hit testing.
625        if event.event.point().is_some() {
626            if !servo.paint().notify_input_event(self.id(), event) {
627                servo.add_pending_handled_input_event(PendingHandledInputEvent {
628                    event_id,
629                    webview_id,
630                });
631                servo.event_loop_waker().wake();
632            }
633        } else {
634            servo
635                .constellation_proxy()
636                .send(EmbedderToConstellationMessage::ForwardInputEvent(
637                    webview_id, event, None, /* hit_test */
638                ));
639        }
640
641        event_id
642    }
643
644    /// Notify this [`WebView`] about a media session event (e.g. play, pause, next track).
645    pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) {
646        self.inner()
647            .servo
648            .constellation_proxy()
649            .send(EmbedderToConstellationMessage::MediaSessionAction(event));
650    }
651
652    /// Set the page zoom of the [`WebView`]. This sets the final page zoom value of the
653    /// [`WebView`]. Unlike [`WebView::pinch_zoom`] *it is not* multiplied by the current
654    /// page zoom value, but overrides it.
655    ///
656    /// [`WebView`]s have two types of zoom, pinch zoom and page zoom. This adjusts page
657    /// zoom, which will adjust the `devicePixelRatio` of the page and cause it to modify
658    /// its layout.
659    ///
660    /// These values will be clamped internally to the inclusive range [0.1, 10.0]).
661    pub fn set_page_zoom(&self, new_zoom: f32) {
662        self.inner()
663            .servo
664            .paint()
665            .set_page_zoom(self.id(), new_zoom);
666    }
667
668    /// Get the page zoom of the [`WebView`].
669    pub fn page_zoom(&self) -> f32 {
670        self.inner().servo.paint().page_zoom(self.id())
671    }
672
673    /// Adjust the pinch zoom on this [`WebView`] multiplying the current pinch zoom
674    /// level with the provided `pinch_zoom_delta`.
675    ///
676    /// [`WebView`]s have two types of zoom, pinch zoom and page zoom. This adjusts pinch
677    /// zoom, which is a type of zoom which does not modify layout, and instead simply
678    /// magnifies the view in the viewport.
679    ///
680    /// The final pinch zoom values will be clamped to defaults (the inclusive range [1.0, 10.0]).
681    /// The values used for clamping can be adjusted by page content when `<meta viewport>`
682    /// parsing is enabled via `Prefs::viewport_meta_enabled`, exclusively on mobile devices.
683    pub fn adjust_pinch_zoom(&self, pinch_zoom_delta: f32, center: DevicePoint) {
684        self.inner()
685            .servo
686            .paint()
687            .adjust_pinch_zoom(self.id(), pinch_zoom_delta, center);
688    }
689
690    /// Get the pinch zoom of the [`WebView`].
691    pub fn pinch_zoom(&self) -> f32 {
692        self.inner().servo.paint().pinch_zoom(self.id())
693    }
694
695    /// Get the ratio of physical device pixels to CSS pixels for this [`WebView`].
696    ///
697    /// The returned scale factor takes into account page zoom, pinch zoom and the
698    /// HiDPI scaling factor.
699    pub fn device_pixels_per_css_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
700        self.inner()
701            .servo
702            .paint()
703            .device_pixels_per_page_pixel(self.id())
704    }
705
706    /// Tell the currently active page in this [`WebView`] to exit fullscreen mode.
707    pub fn exit_fullscreen(&self) {
708        self.inner()
709            .servo
710            .constellation_proxy()
711            .send(EmbedderToConstellationMessage::ExitFullScreen(self.id()));
712    }
713
714    /// Set whether resource usage of this [`WebView`] should be throttled or not.
715    ///
716    /// A throttled [`WebView`] attempts to use less system resources by stopping
717    /// animations and running timers at a heavily limited rate.
718    pub fn set_throttled(&self, throttled: bool) {
719        self.inner().servo.constellation_proxy().send(
720            EmbedderToConstellationMessage::SetWebViewThrottled(self.id(), throttled),
721        );
722    }
723
724    /// Toggle the given [`WebRenderDebugOption`] from its current state.
725    ///
726    /// Note that this method toggles the debugging options globally i.e., it affects
727    /// all [`WebView`]s managed by Servo and not just the [`WebView`] on which
728    /// this method is invoked.
729    pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) {
730        self.inner().servo.paint().toggle_webrender_debug(debugging);
731    }
732
733    /// Capture the current WebRender state for this [`WebView`] for debugging.
734    ///
735    /// Note that the captured state includes information about all [`WebView`]s
736    /// that share this [`WebView`]'s [`RenderingContext`].
737    pub fn capture_webrender(&self) {
738        self.inner().servo.paint().capture_webrender(self.id());
739    }
740
741    /// Enable the sampling profiler for debugging performance issues.
742    ///
743    /// The `rate` determines how often samples are taken and `max_duration` is
744    /// the maximum period for which sampling is enabled.
745    ///
746    /// Note that the profiler is enabled globally i.e., for all [`WebView`]s managed
747    /// by Servo rather than just the [`WebView`] on which this method is invoked.
748    pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
749        self.inner().servo.constellation_proxy().send(
750            EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration),
751        );
752    }
753
754    /// Paint the contents of this [`WebView`] into its [`RenderingContext`].
755    pub fn paint(&self) {
756        self.inner().servo.paint().render(self.id());
757    }
758
759    /// Get the [`UserContentManager`] associated with this [`WebView`].
760    pub fn user_content_manager(&self) -> Option<Rc<UserContentManager>> {
761        self.inner().user_content_manager.clone()
762    }
763
764    /// Evaluate the specified string of JavaScript code. Once execution is complete or an error
765    /// occurs, Servo will call `callback`.
766    pub fn evaluate_javascript<T: ToString>(
767        &self,
768        script: T,
769        callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static,
770    ) {
771        self.inner().servo.javascript_evaluator_mut().evaluate(
772            self.id(),
773            script.to_string(),
774            Box::new(callback),
775        );
776    }
777
778    /// Asynchronously take a screenshot of the [`WebView`] contents, given a `rect` or the whole
779    /// viewport, if no `rect` is given.
780    ///
781    /// This method will wait until the [`WebView`] is ready before the screenshot is taken.
782    /// This includes waiting for:
783    ///
784    ///  - all frames to fire their `load` event.
785    ///  - all render blocking elements, such as stylesheets included via the `<link>`
786    ///    element, to stop blocking the rendering.
787    ///  - all images to be loaded and displayed.
788    ///  - all web fonts are loaded.
789    ///  - the `reftest-wait` and `test-wait` classes have been removed from the root element.
790    ///  - the rendering is up-to-date
791    ///
792    /// Once all these conditions are met and the rendering does not have any pending frames
793    /// to render, the provided `callback` will be called with the results of the screenshot
794    /// operation.
795    pub fn take_screenshot(
796        &self,
797        rect: Option<WebViewRect>,
798        callback: impl FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static,
799    ) {
800        self.inner()
801            .servo
802            .paint()
803            .request_screenshot(self.id(), rect, Box::new(callback));
804    }
805
806    pub(crate) fn set_history(self, new_back_forward_list: Vec<ServoUrl>, new_index: usize) {
807        {
808            let mut inner_mut = self.inner_mut();
809            inner_mut.back_forward_list_index = new_index;
810            inner_mut.back_forward_list = new_back_forward_list
811                .into_iter()
812                .map(ServoUrl::into_url)
813                .collect();
814        }
815
816        let back_forward_list = self.inner().back_forward_list.clone();
817        let back_forward_list_index = self.inner().back_forward_list_index;
818        self.delegate().notify_url_changed(
819            self.clone(),
820            back_forward_list[back_forward_list_index].clone(),
821        );
822        self.delegate().notify_history_changed(
823            self.clone(),
824            back_forward_list,
825            back_forward_list_index,
826        );
827    }
828
829    pub(crate) fn show_embedder_control(
830        self,
831        control_id: EmbedderControlId,
832        position: DeviceIntRect,
833        embedder_control_request: EmbedderControlRequest,
834    ) {
835        let constellation_proxy = self.inner().servo.constellation_proxy().clone();
836        let embedder_control = match embedder_control_request {
837            EmbedderControlRequest::SelectElement(request) => {
838                EmbedderControl::SelectElement(SelectElement {
839                    id: control_id,
840                    select_element_request: request,
841                    position,
842                    constellation_proxy,
843                    response_sent: false,
844                })
845            },
846            EmbedderControlRequest::ColorPicker(current_color) => {
847                EmbedderControl::ColorPicker(ColorPicker {
848                    id: control_id,
849                    current_color: Some(current_color),
850                    position,
851                    constellation_proxy,
852                    response_sent: false,
853                })
854            },
855            EmbedderControlRequest::InputMethod(input_method_request) => {
856                EmbedderControl::InputMethod(InputMethodControl {
857                    id: control_id,
858                    input_method_type: input_method_request.input_method_type,
859                    text: input_method_request.text,
860                    insertion_point: input_method_request.insertion_point,
861                    position,
862                    multiline: input_method_request.multiline,
863                    allow_virtual_keyboard: input_method_request.allow_virtual_keyboard,
864                })
865            },
866            EmbedderControlRequest::ContextMenu(mut context_menu_request) => {
867                for item in context_menu_request.items.iter_mut() {
868                    match item {
869                        ContextMenuItem::Item {
870                            action: ContextMenuAction::GoBack,
871                            enabled,
872                            ..
873                        } => *enabled = self.can_go_back(),
874                        ContextMenuItem::Item {
875                            action: ContextMenuAction::GoForward,
876                            enabled,
877                            ..
878                        } => *enabled = self.can_go_forward(),
879                        _ => {},
880                    }
881                }
882                EmbedderControl::ContextMenu(ContextMenu {
883                    id: control_id,
884                    position,
885                    items: context_menu_request.items,
886                    element_info: context_menu_request.element_info,
887                    constellation_proxy,
888                    response_sent: false,
889                })
890            },
891            EmbedderControlRequest::FilePicker { .. } => {
892                unreachable!("This message should be routed through the FileManagerThread")
893            },
894        };
895
896        self.delegate()
897            .show_embedder_control(self.clone(), embedder_control);
898    }
899
900    /// AccessKit subtree id for this [`WebView`], if accessibility is active.
901    pub fn accesskit_tree_id(&self) -> Option<TreeId> {
902        self.inner().accesskit_tree_id
903    }
904
905    /// Activate or deactivate accessibility features for this [`WebView`], returning the
906    /// AccessKit subtree id if accessibility is now active.
907    ///
908    /// After accessibility is activated, you must [graft] (with [`set_tree_id()`]) the returned
909    /// [`TreeId`] into your application’s main AccessKit tree as soon as possible, *before*
910    /// sending any tree updates from the webview to your AccessKit adapter. Otherwise you may
911    /// violate AccessKit’s subtree invariants and **panic**.
912    ///
913    /// If your impl for [`WebViewDelegate::notify_accessibility_tree_update()`] can’t create the
914    /// graft node (and send *that* update to AccessKit) before sending any updates from this
915    /// webview to AccessKit, then it must queue those updates until it can guarantee that.
916    ///
917    /// [graft]: https://docs.rs/accesskit/0.24.0/accesskit/struct.Node.html#method.tree_id
918    /// [`set_tree_id()`]: https://docs.rs/accesskit/0.24.0/accesskit/struct.Node.html#method.set_tree_id
919    pub fn set_accessibility_active(&self, active: bool) -> Option<TreeId> {
920        if !pref!(accessibility_enabled) {
921            return None;
922        }
923
924        if active == self.inner().accesskit_tree_id.is_some() {
925            return self.accesskit_tree_id();
926        }
927
928        if active {
929            let accesskit_tree_id = TreeId(AccesskitUuid::new_v4());
930            self.inner_mut().accesskit_tree_id = Some(accesskit_tree_id);
931        } else {
932            self.inner_mut().accesskit_tree_id = None;
933            self.inner_mut().grafted_accesskit_tree_id = None;
934            self.inner_mut().grafted_accesskit_tree_epoch = None;
935        }
936
937        self.inner().servo.constellation_proxy().send(
938            EmbedderToConstellationMessage::SetAccessibilityActive(self.id(), active),
939        );
940
941        self.accesskit_tree_id()
942    }
943
944    pub(crate) fn notify_document_accessibility_tree_id(&self, grafted_tree_id: TreeId) {
945        let Some(webview_accesskit_tree_id) = self.inner().accesskit_tree_id else {
946            return;
947        };
948        let old_grafted_tree_id = self
949            .inner_mut()
950            .grafted_accesskit_tree_id
951            .replace(grafted_tree_id);
952        // TODO(#4344): try to avoid duplicate notifications in the first place?
953        // (see ConstellationWebView::new for more details)
954        if old_grafted_tree_id == Some(grafted_tree_id) {
955            return;
956        }
957        let root_node_id = NodeId(0);
958        let mut root_node = AccesskitNode::new(Role::ScrollView);
959        let graft_node_id = NodeId(1);
960        let mut graft_node = AccesskitNode::new(Role::GenericContainer);
961        graft_node.set_tree_id(grafted_tree_id);
962        root_node.set_children(vec![graft_node_id]);
963        self.delegate().notify_accessibility_tree_update(
964            self.clone(),
965            TreeUpdate {
966                nodes: vec![(root_node_id, root_node), (graft_node_id, graft_node)],
967                tree: Some(Tree {
968                    root: root_node_id,
969                    toolkit_name: None,
970                    toolkit_version: None,
971                }),
972                tree_id: webview_accesskit_tree_id,
973                focus: root_node_id,
974            },
975        );
976    }
977
978    pub(crate) fn process_accessibility_tree_update(&self, tree_update: TreeUpdate, epoch: Epoch) {
979        if self
980            .inner()
981            .grafted_accesskit_tree_epoch
982            .is_some_and(|current| epoch < current)
983        {
984            // We expect this to happen occasionally when the constellation navigates, because
985            // deactivating accessibility happens asynchronously, so the script thread of the
986            // previously active document may continue sending updates for a short period of time.
987            debug!("Ignoring stale tree update for {:?}", tree_update.tree_id);
988            return;
989        }
990        if self
991            .inner()
992            .grafted_accesskit_tree_epoch
993            .is_none_or(|current| epoch > current)
994        {
995            self.notify_document_accessibility_tree_id(tree_update.tree_id);
996            self.inner_mut().grafted_accesskit_tree_epoch = Some(epoch);
997        }
998        self.delegate()
999            .notify_accessibility_tree_update(self.clone(), tree_update);
1000    }
1001}
1002
1003/// A structure used to expose a view of the [`WebView`] to the Servo
1004/// renderer, without having the Servo renderer depend on the embedding layer.
1005struct ServoRendererWebView {
1006    id: WebViewId,
1007    weak_handle: Weak<RefCell<WebViewInner>>,
1008}
1009
1010impl WebViewTrait for ServoRendererWebView {
1011    fn id(&self) -> WebViewId {
1012        self.id
1013    }
1014
1015    fn screen_geometry(&self) -> Option<ScreenGeometry> {
1016        let webview = WebView::from_weak_handle(&self.weak_handle)?;
1017        webview.delegate().screen_geometry(webview)
1018    }
1019
1020    fn set_animating(&self, new_value: bool) {
1021        if let Some(webview) = WebView::from_weak_handle(&self.weak_handle) {
1022            webview.set_animating(new_value);
1023        }
1024    }
1025}
1026
1027/// Builder for creating a [`WebView`].
1028pub struct WebViewBuilder {
1029    servo: Servo,
1030    rendering_context: Rc<dyn RenderingContext>,
1031    delegate: Rc<dyn WebViewDelegate>,
1032    url: Option<Url>,
1033    hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
1034    create_new_webview_responder: Option<IpcResponder<Option<NewWebViewDetails>>>,
1035    user_content_manager: Option<Rc<UserContentManager>>,
1036    clipboard_delegate: Option<Rc<dyn ClipboardDelegate>>,
1037    #[cfg(feature = "gamepad")]
1038    gamepad_delegate: Option<Rc<dyn GamepadDelegate>>,
1039}
1040
1041impl WebViewBuilder {
1042    /// Create a [`WebViewBuilder`] that can be used to configure and create a [`WebView`].
1043    ///
1044    /// The new [`WebView`] will be managed by the given `servo` instance and will
1045    /// use `rendering_context` to paint its contents.
1046    pub fn new(servo: &Servo, rendering_context: Rc<dyn RenderingContext>) -> Self {
1047        Self {
1048            servo: servo.clone(),
1049            rendering_context,
1050            url: None,
1051            hidpi_scale_factor: Scale::new(1.0),
1052            delegate: Rc::new(DefaultWebViewDelegate),
1053            create_new_webview_responder: None,
1054            user_content_manager: None,
1055            clipboard_delegate: None,
1056            #[cfg(feature = "gamepad")]
1057            gamepad_delegate: None,
1058        }
1059    }
1060
1061    pub(crate) fn new_for_create_request(
1062        servo: &Servo,
1063        rendering_context: Rc<dyn RenderingContext>,
1064        responder: IpcResponder<Option<NewWebViewDetails>>,
1065    ) -> Self {
1066        let mut builder = Self::new(servo, rendering_context);
1067        builder.create_new_webview_responder = Some(responder);
1068        builder
1069    }
1070
1071    /// Set the [`WebViewDelegate`] that will receive notifications about the events
1072    /// in the [`WebView`] being created.
1073    pub fn delegate(mut self, delegate: Rc<dyn WebViewDelegate>) -> Self {
1074        self.delegate = delegate;
1075        self
1076    }
1077
1078    /// Set the initial URL to load in the [`WebView`] being created.
1079    pub fn url(mut self, url: Url) -> Self {
1080        self.url = Some(url);
1081        self
1082    }
1083
1084    /// Set the initial HiDPI scale factor for the [`WebView`] being created.
1085    pub fn hidpi_scale_factor(
1086        mut self,
1087        hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
1088    ) -> Self {
1089        self.hidpi_scale_factor = hidpi_scale_factor;
1090        self
1091    }
1092
1093    /// Set the [`UserContentManager`] for the `WebView` being created. The same
1094    /// `UserContentManager` can be shared among multiple `WebView`s. Any updates
1095    /// to the `UserContentManager` will take effect only after the document is reloaded.
1096    pub fn user_content_manager(mut self, user_content_manager: Rc<UserContentManager>) -> Self {
1097        self.user_content_manager = Some(user_content_manager);
1098        self
1099    }
1100
1101    /// Set the [`ClipboardDelegate`] for the `WebView` being created. The same
1102    /// [`ClipboardDelegate`] can be shared among multiple `WebView`s.
1103    pub fn clipboard_delegate(mut self, clipboard_delegate: Rc<dyn ClipboardDelegate>) -> Self {
1104        self.clipboard_delegate = Some(clipboard_delegate);
1105        self
1106    }
1107
1108    /// Set the [`GamepadDelegate`] for the `WebView` being created. The same
1109    /// [`GamepadDelegate`] can be shared among multiple `WebView`s.
1110    #[cfg(feature = "gamepad")]
1111    pub fn gamepad_delegate(mut self, gamepad_delegate: Rc<dyn GamepadDelegate>) -> Self {
1112        self.gamepad_delegate = Some(gamepad_delegate);
1113        self
1114    }
1115
1116    /// Create the [`WebView`] using the configuration specified in this [`WebViewBuilder`].
1117    pub fn build(self) -> WebView {
1118        WebView::new(self)
1119    }
1120}