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