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