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