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