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