1use std::cell::{Ref, RefCell, RefMut};
6use std::hash::Hash;
7use std::rc::{Rc, Weak};
8use std::time::Duration;
9
10use base::id::{RenderingGroupId, WebViewId};
11use compositing::IOCompositor;
12use compositing_traits::WebViewTrait;
13use compositing_traits::viewport_description::{MAX_PAGE_ZOOM, MIN_PAGE_ZOOM};
14use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
15use dpi::PhysicalSize;
16use embedder_traits::{
17 Cursor, Image, InputEvent, InputEventAndId, InputEventId, JSValue, JavaScriptEvaluationError,
18 LoadStatus, MediaSessionActionType, ScreenGeometry, ScreenshotCaptureError, Theme, TraversalId,
19 ViewportDetails,
20};
21use euclid::{Point2D, Scale, Size2D};
22use image::RgbaImage;
23use servo_geometry::DeviceIndependentPixel;
24use style_traits::CSSPixel;
25use url::Url;
26use webrender_api::ScrollLocation;
27use webrender_api::units::{DeviceIntPoint, DevicePixel, DeviceRect};
28
29use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
30use crate::javascript_evaluator::JavaScriptEvaluator;
31use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate};
32use crate::{ConstellationProxy, Servo, WebRenderDebugOption};
33
34pub(crate) const MINIMUM_WEBVIEW_SIZE: Size2D<i32, DevicePixel> = Size2D::new(1, 1);
35
36#[derive(Clone)]
64pub struct WebView(Rc<RefCell<WebViewInner>>);
65
66impl PartialEq for WebView {
67 fn eq(&self, other: &Self) -> bool {
68 self.inner().id == other.inner().id
69 }
70}
71
72impl Hash for WebView {
73 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
74 self.inner().id.hash(state);
75 }
76}
77
78pub(crate) struct WebViewInner {
79 pub(crate) id: WebViewId,
81 pub(crate) constellation_proxy: ConstellationProxy,
82 pub(crate) compositor: Rc<RefCell<IOCompositor>>,
83 pub(crate) delegate: Rc<dyn WebViewDelegate>,
84 pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>,
85 javascript_evaluator: Rc<RefCell<JavaScriptEvaluator>>,
86 rect: DeviceRect,
88 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
89 page_zoom: f32,
90 load_status: LoadStatus,
91 url: Option<Url>,
92 status_text: Option<String>,
93 page_title: Option<String>,
94 favicon: Option<Image>,
95 focused: bool,
96 animating: bool,
97 cursor: Cursor,
98
99 rendering_group_id: RenderingGroupId,
100}
101
102impl Drop for WebViewInner {
103 fn drop(&mut self) {
104 self.constellation_proxy
105 .send(EmbedderToConstellationMessage::CloseWebView(self.id));
106 }
107}
108
109impl WebView {
110 pub(crate) fn new(builder: WebViewBuilder) -> Self {
111 let id = WebViewId::new();
112 let servo = builder.servo;
113 let size = builder.size.map_or_else(
114 || {
115 builder
116 .servo
117 .compositor
118 .borrow()
119 .rendering_context_size()
120 .to_f32()
121 },
122 |size| Size2D::new(size.width as f32, size.height as f32),
123 );
124
125 let webview = Self(Rc::new(RefCell::new(WebViewInner {
126 id,
127 constellation_proxy: servo.constellation_proxy.clone(),
128 compositor: servo.compositor.clone(),
129 delegate: builder.delegate,
130 clipboard_delegate: Rc::new(DefaultClipboardDelegate),
131 javascript_evaluator: servo.javascript_evaluator.clone(),
132 rect: DeviceRect::from_origin_and_size(Point2D::origin(), size),
133 hidpi_scale_factor: builder.hidpi_scale_factor,
134 page_zoom: 1.0,
135 load_status: LoadStatus::Started,
136 url: None,
137 status_text: None,
138 page_title: None,
139 favicon: None,
140 focused: false,
141 animating: false,
142 cursor: Cursor::Pointer,
143 rendering_group_id: builder.group_id.unwrap_or_default(),
144 })));
145
146 let viewport_details = webview.viewport_details();
147 let wv = Box::new(ServoRendererWebView {
148 weak_handle: webview.weak_handle(),
149 id,
150 });
151 servo
152 .compositor
153 .borrow_mut()
154 .add_webview(wv, viewport_details);
155
156 servo
157 .webviews
158 .borrow_mut()
159 .insert(webview.id(), webview.weak_handle());
160
161 if !builder.auxiliary {
162 let url = builder.url.unwrap_or(
163 Url::parse("about:blank").expect("Should always be able to parse 'about:blank'."),
164 );
165
166 builder
167 .servo
168 .constellation_proxy
169 .send(EmbedderToConstellationMessage::NewWebView(
170 url.into(),
171 webview.id(),
172 viewport_details,
173 ));
174 }
175
176 webview
177 }
178
179 fn inner(&self) -> Ref<'_, WebViewInner> {
180 self.0.borrow()
181 }
182
183 fn inner_mut(&self) -> RefMut<'_, WebViewInner> {
184 self.0.borrow_mut()
185 }
186
187 pub(crate) fn viewport_details(&self) -> ViewportDetails {
188 let inner = self.inner();
191 let scaled_viewport_size = inner.rect.size() / inner.hidpi_scale_factor;
192 ViewportDetails {
193 size: scaled_viewport_size / Scale::new(1.0),
194 hidpi_scale_factor: Scale::new(inner.hidpi_scale_factor.0),
195 }
196 }
197
198 pub(crate) fn from_weak_handle(inner: &Weak<RefCell<WebViewInner>>) -> Option<Self> {
199 inner.upgrade().map(WebView)
200 }
201
202 pub(crate) fn weak_handle(&self) -> Weak<RefCell<WebViewInner>> {
203 Rc::downgrade(&self.0)
204 }
205
206 pub fn delegate(&self) -> Rc<dyn WebViewDelegate> {
207 self.inner().delegate.clone()
208 }
209
210 pub fn set_delegate(&self, delegate: Rc<dyn WebViewDelegate>) {
211 self.inner_mut().delegate = delegate;
212 }
213
214 pub fn clipboard_delegate(&self) -> Rc<dyn ClipboardDelegate> {
215 self.inner().clipboard_delegate.clone()
216 }
217
218 pub fn set_clipboard_delegate(&self, delegate: Rc<dyn ClipboardDelegate>) {
219 self.inner_mut().clipboard_delegate = delegate;
220 }
221
222 pub fn id(&self) -> WebViewId {
223 self.inner().id
224 }
225
226 pub fn rendering_group_id(&self) -> RenderingGroupId {
227 self.inner().rendering_group_id
228 }
229
230 pub fn load_status(&self) -> LoadStatus {
231 self.inner().load_status
232 }
233
234 pub(crate) fn set_load_status(self, new_value: LoadStatus) {
235 if self.inner().load_status == new_value {
236 return;
237 }
238 self.inner_mut().load_status = new_value;
239 self.delegate().notify_load_status_changed(self, new_value);
240 }
241
242 pub fn url(&self) -> Option<Url> {
243 self.inner().url.clone()
244 }
245
246 pub(crate) fn set_url(self, new_value: Url) {
247 if self
248 .inner()
249 .url
250 .as_ref()
251 .is_some_and(|url| url == &new_value)
252 {
253 return;
254 }
255 self.inner_mut().url = Some(new_value.clone());
256 self.delegate().notify_url_changed(self, new_value);
257 }
258
259 pub fn status_text(&self) -> Option<String> {
260 self.inner().status_text.clone()
261 }
262
263 pub(crate) fn set_status_text(self, new_value: Option<String>) {
264 if self.inner().status_text == new_value {
265 return;
266 }
267 self.inner_mut().status_text = new_value.clone();
268 self.delegate().notify_status_text_changed(self, new_value);
269 }
270
271 pub fn page_title(&self) -> Option<String> {
272 self.inner().page_title.clone()
273 }
274
275 pub(crate) fn set_page_title(self, new_value: Option<String>) {
276 if self.inner().page_title == new_value {
277 return;
278 }
279 self.inner_mut().page_title = new_value.clone();
280 self.delegate().notify_page_title_changed(self, new_value);
281 }
282
283 pub fn favicon(&self) -> Option<Ref<'_, Image>> {
284 Ref::filter_map(self.inner(), |inner| inner.favicon.as_ref()).ok()
285 }
286
287 pub(crate) fn set_favicon(self, new_value: Image) {
288 self.inner_mut().favicon = Some(new_value);
289 self.delegate().notify_favicon_changed(self);
290 }
291
292 pub fn focused(&self) -> bool {
293 self.inner().focused
294 }
295
296 pub(crate) fn set_focused(self, new_value: bool) {
297 if self.inner().focused == new_value {
298 return;
299 }
300 self.inner_mut().focused = new_value;
301 self.delegate().notify_focus_changed(self, new_value);
302 }
303
304 pub fn cursor(&self) -> Cursor {
305 self.inner().cursor
306 }
307
308 pub(crate) fn set_cursor(self, new_value: Cursor) {
309 if self.inner().cursor == new_value {
310 return;
311 }
312 self.inner_mut().cursor = new_value;
313 self.delegate().notify_cursor_changed(self, new_value);
314 }
315
316 pub fn focus(&self) {
317 self.inner()
318 .constellation_proxy
319 .send(EmbedderToConstellationMessage::FocusWebView(self.id()));
320 }
321
322 pub fn blur(&self) {
323 self.inner()
324 .constellation_proxy
325 .send(EmbedderToConstellationMessage::BlurWebView);
326 }
327
328 pub fn animating(self) -> bool {
333 self.inner().animating
334 }
335
336 pub(crate) fn set_animating(self, new_value: bool) {
337 if self.inner().animating == new_value {
338 return;
339 }
340 self.inner_mut().animating = new_value;
341 self.delegate().notify_animating_changed(self, new_value);
342 }
343
344 pub fn rect(&self) -> DeviceRect {
345 self.inner().rect
346 }
347
348 pub fn move_resize(&self, rect: DeviceRect) {
349 if self.inner().rect == rect {
350 return;
351 }
352
353 let rect =
354 DeviceRect::from_origin_and_size(rect.min, rect.size().max(Size2D::new(1.0, 1.0)));
355
356 self.inner_mut().rect = rect;
357 self.inner()
358 .compositor
359 .borrow_mut()
360 .move_resize_webview(self.id(), rect);
361 }
362
363 pub fn resize(&self, new_size: PhysicalSize<u32>) {
367 let new_size = PhysicalSize {
368 width: new_size.width.max(MINIMUM_WEBVIEW_SIZE.width as u32),
369 height: new_size.height.max(MINIMUM_WEBVIEW_SIZE.height as u32),
370 };
371
372 self.inner()
373 .compositor
374 .borrow_mut()
375 .resize_rendering_context(new_size);
376 }
377
378 pub fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
379 self.inner().hidpi_scale_factor
380 }
381
382 pub fn set_hidpi_scale_factor(
383 &self,
384 new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
385 ) {
386 if self.inner().hidpi_scale_factor == new_scale_factor {
387 return;
388 }
389
390 self.inner_mut().hidpi_scale_factor = new_scale_factor;
391 self.inner()
392 .compositor
393 .borrow_mut()
394 .set_hidpi_scale_factor(self.id(), new_scale_factor);
395 }
396
397 pub fn show(&self, hide_others: bool) {
398 self.inner()
399 .compositor
400 .borrow_mut()
401 .show_webview(self.id(), hide_others)
402 .expect("BUG: invalid WebView instance");
403 }
404
405 pub fn hide(&self) {
406 self.inner()
407 .compositor
408 .borrow_mut()
409 .hide_webview(self.id())
410 .expect("BUG: invalid WebView instance");
411 }
412
413 pub fn raise_to_top(&self, hide_others: bool) {
414 self.inner()
415 .compositor
416 .borrow_mut()
417 .raise_webview_to_top(self.id(), hide_others)
418 .expect("BUG: invalid WebView instance");
419 }
420
421 pub fn focus_and_raise_to_top(&self, hide_others: bool) {
422 self.focus();
423 self.raise_to_top(hide_others);
424 }
425
426 pub fn notify_theme_change(&self, theme: Theme) {
427 self.inner()
428 .constellation_proxy
429 .send(EmbedderToConstellationMessage::ThemeChange(
430 self.id(),
431 theme,
432 ))
433 }
434
435 pub fn load(&self, url: Url) {
436 self.inner()
437 .constellation_proxy
438 .send(EmbedderToConstellationMessage::LoadUrl(
439 self.id(),
440 url.into(),
441 ))
442 }
443
444 pub fn reload(&self) {
445 self.inner()
446 .constellation_proxy
447 .send(EmbedderToConstellationMessage::Reload(self.id()))
448 }
449
450 pub fn go_back(&self, amount: usize) -> TraversalId {
451 let traversal_id = TraversalId::new();
452 self.inner()
453 .constellation_proxy
454 .send(EmbedderToConstellationMessage::TraverseHistory(
455 self.id(),
456 TraversalDirection::Back(amount),
457 traversal_id.clone(),
458 ));
459 traversal_id
460 }
461
462 pub fn go_forward(&self, amount: usize) -> TraversalId {
463 let traversal_id = TraversalId::new();
464 self.inner()
465 .constellation_proxy
466 .send(EmbedderToConstellationMessage::TraverseHistory(
467 self.id(),
468 TraversalDirection::Forward(amount),
469 traversal_id.clone(),
470 ));
471 traversal_id
472 }
473
474 pub fn notify_scroll_event(&self, location: ScrollLocation, point: DeviceIntPoint) {
477 self.inner()
478 .compositor
479 .borrow_mut()
480 .notify_scroll_event(self.id(), location, point);
481 }
482
483 pub fn notify_input_event(&self, event: InputEvent) -> InputEventId {
484 let event: InputEventAndId = event.into();
485 let event_id = event.id;
486
487 if event.event.point().is_some() {
489 self.inner()
490 .compositor
491 .borrow_mut()
492 .notify_input_event(self.id(), event);
493 } else {
494 self.inner().constellation_proxy.send(
495 EmbedderToConstellationMessage::ForwardInputEvent(
496 self.id(),
497 event,
498 None, ),
500 );
501 }
502
503 event_id
504 }
505
506 pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) {
507 self.inner()
508 .constellation_proxy
509 .send(EmbedderToConstellationMessage::MediaSessionAction(event));
510 }
511
512 pub fn notify_vsync(&self) {
513 self.inner().compositor.borrow_mut().on_vsync(self.id());
514 }
515
516 pub fn set_page_zoom(&self, new_zoom: f32) {
522 let new_zoom = new_zoom.clamp(MIN_PAGE_ZOOM.get(), MAX_PAGE_ZOOM.get());
523 if new_zoom == self.inner().page_zoom {
524 return;
525 }
526
527 self.inner_mut().page_zoom = new_zoom;
528 self.inner()
529 .compositor
530 .borrow_mut()
531 .on_zoom_window_event(self.id(), new_zoom);
532 }
533
534 pub fn page_zoom(&self) -> f32 {
536 self.inner().page_zoom
537 }
538
539 pub fn set_pinch_zoom(&self, new_pinch_zoom: f32) {
540 self.inner()
541 .compositor
542 .borrow_mut()
543 .set_pinch_zoom(self.id(), new_pinch_zoom);
544 }
545
546 pub fn device_pixels_per_css_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
547 self.inner()
548 .compositor
549 .borrow()
550 .device_pixels_per_page_pixel(self.id())
551 }
552
553 pub fn exit_fullscreen(&self) {
554 self.inner()
555 .constellation_proxy
556 .send(EmbedderToConstellationMessage::ExitFullScreen(self.id()));
557 }
558
559 pub fn set_throttled(&self, throttled: bool) {
560 self.inner()
561 .constellation_proxy
562 .send(EmbedderToConstellationMessage::SetWebViewThrottled(
563 self.id(),
564 throttled,
565 ));
566 }
567
568 pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) {
569 self.inner()
570 .compositor
571 .borrow_mut()
572 .toggle_webrender_debug(debugging);
573 }
574
575 pub fn capture_webrender(&self) {
576 self.inner().compositor.borrow_mut().capture_webrender();
577 }
578
579 pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
580 self.inner()
581 .constellation_proxy
582 .send(EmbedderToConstellationMessage::ToggleProfiler(
583 rate,
584 max_duration,
585 ));
586 }
587
588 pub fn send_error(&self, message: String) {
589 self.inner()
590 .constellation_proxy
591 .send(EmbedderToConstellationMessage::SendError(
592 Some(self.id()),
593 message,
594 ));
595 }
596
597 pub fn paint(&self) {
599 self.inner().compositor.borrow_mut().render();
600 }
601
602 pub fn evaluate_javascript<T: ToString>(
605 &self,
606 script: T,
607 callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static,
608 ) {
609 self.inner().javascript_evaluator.borrow_mut().evaluate(
610 self.id(),
611 script.to_string(),
612 Box::new(callback),
613 );
614 }
615
616 pub fn take_screenshot(
634 &self,
635 rect: Option<DeviceRect>,
636 callback: impl FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static,
637 ) {
638 self.inner()
639 .compositor
640 .borrow()
641 .request_screenshot(self.id(), rect, Box::new(callback));
642 }
643}
644
645struct ServoRendererWebView {
648 id: WebViewId,
649 weak_handle: Weak<RefCell<WebViewInner>>,
650}
651
652impl WebViewTrait for ServoRendererWebView {
653 fn id(&self) -> WebViewId {
654 self.id
655 }
656
657 fn screen_geometry(&self) -> Option<ScreenGeometry> {
658 let webview = WebView::from_weak_handle(&self.weak_handle)?;
659 webview.delegate().screen_geometry(webview)
660 }
661
662 fn set_animating(&self, new_value: bool) {
663 if let Some(webview) = WebView::from_weak_handle(&self.weak_handle) {
664 webview.set_animating(new_value);
665 }
666 }
667
668 fn rendering_group_id(&self) -> Option<RenderingGroupId> {
669 WebView::from_weak_handle(&self.weak_handle).map(|webview| webview.rendering_group_id())
670 }
671}
672
673pub struct WebViewBuilder<'servo> {
674 servo: &'servo Servo,
675 delegate: Rc<dyn WebViewDelegate>,
676 auxiliary: bool,
677 url: Option<Url>,
678 size: Option<PhysicalSize<u32>>,
679 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
680 group_id: Option<RenderingGroupId>,
681}
682
683impl<'servo> WebViewBuilder<'servo> {
684 pub fn new(servo: &'servo Servo) -> Self {
685 Self {
686 servo,
687 auxiliary: false,
688 url: None,
689 size: None,
690 hidpi_scale_factor: Scale::new(1.0),
691 delegate: Rc::new(DefaultWebViewDelegate),
692 group_id: None,
693 }
694 }
695
696 pub fn new_auxiliary(servo: &'servo Servo) -> Self {
697 let mut builder = Self::new(servo);
698 builder.auxiliary = true;
699 builder
700 }
701
702 pub fn delegate(mut self, delegate: Rc<dyn WebViewDelegate>) -> Self {
703 self.delegate = delegate;
704 self
705 }
706
707 pub fn url(mut self, url: Url) -> Self {
708 self.url = Some(url);
709 self
710 }
711
712 pub fn size(mut self, size: PhysicalSize<u32>) -> Self {
713 self.size = Some(size);
714 self
715 }
716
717 pub fn hidpi_scale_factor(
718 mut self,
719 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
720 ) -> Self {
721 self.hidpi_scale_factor = hidpi_scale_factor;
722 self
723 }
724
725 pub fn build(self) -> WebView {
726 WebView::new(self)
727 }
728}