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_traits::WebViewTrait;
12use compositing_traits::rendering_context::RenderingContext;
13use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
14use dpi::PhysicalSize;
15use embedder_traits::{
16 ContextMenuAction, ContextMenuItem, Cursor, EmbedderControlId, EmbedderControlRequest, Image,
17 InputEvent, InputEventAndId, InputEventId, JSValue, JavaScriptEvaluationError, LoadStatus,
18 MediaSessionActionType, ScreenGeometry, ScreenshotCaptureError, Scroll, Theme, TraversalId,
19 ViewportDetails, WebViewPoint, WebViewRect,
20};
21use euclid::{Point2D, Scale, Size2D};
22use image::RgbaImage;
23use servo_geometry::DeviceIndependentPixel;
24use servo_url::ServoUrl;
25use style_traits::CSSPixel;
26use url::Url;
27use webrender_api::units::{DeviceIntRect, DevicePixel, DevicePoint, DeviceRect};
28
29use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
30use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate};
31use crate::{
32 ColorPicker, ContextMenu, EmbedderControl, InputMethodControl, SelectElement, Servo,
33 WebRenderDebugOption,
34};
35
36pub(crate) const MINIMUM_WEBVIEW_SIZE: Size2D<i32, DevicePixel> = Size2D::new(1, 1);
37
38#[derive(Clone)]
66pub struct WebView(Rc<RefCell<WebViewInner>>);
67
68impl PartialEq for WebView {
69 fn eq(&self, other: &Self) -> bool {
70 self.inner().id == other.inner().id
71 }
72}
73
74impl Hash for WebView {
75 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
76 self.inner().id.hash(state);
77 }
78}
79
80pub(crate) struct WebViewInner {
81 pub(crate) id: WebViewId,
82 pub(crate) servo: Servo,
83 pub(crate) delegate: Rc<dyn WebViewDelegate>,
84 pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>,
85
86 rect: DeviceRect,
88 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
89 load_status: LoadStatus,
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 back_forward_list: Vec<Url>,
99
100 back_forward_list_index: usize,
102}
103
104impl Drop for WebViewInner {
105 fn drop(&mut self) {
106 self.servo
107 .constellation_proxy()
108 .send(EmbedderToConstellationMessage::CloseWebView(self.id));
109 }
110}
111
112impl WebView {
113 pub(crate) fn new(builder: WebViewBuilder) -> Self {
114 let servo = builder.servo;
115 let size = builder.size.map_or_else(
116 || builder.rendering_context.size2d().to_f32(),
117 |size| Size2D::new(size.width as f32, size.height as f32),
118 );
119
120 let painter_id = servo
121 .compositor_mut()
122 .register_rendering_context(builder.rendering_context);
123
124 let id = WebViewId::new(painter_id);
125 let webview = Self(Rc::new(RefCell::new(WebViewInner {
126 id,
127 servo: servo.clone(),
128 delegate: builder.delegate,
129 clipboard_delegate: Rc::new(DefaultClipboardDelegate),
130 rect: DeviceRect::from_origin_and_size(Point2D::origin(), size),
131 hidpi_scale_factor: builder.hidpi_scale_factor,
132 load_status: LoadStatus::Started,
133 status_text: None,
134 page_title: None,
135 favicon: None,
136 focused: false,
137 animating: false,
138 cursor: Cursor::Pointer,
139 back_forward_list: Default::default(),
140 back_forward_list_index: 0,
141 })));
142
143 let viewport_details = webview.viewport_details();
144 servo.compositor().add_webview(
145 Box::new(ServoRendererWebView {
146 weak_handle: webview.weak_handle(),
147 id,
148 }),
149 viewport_details,
150 );
151
152 servo
153 .webviews_mut()
154 .insert(webview.id(), webview.weak_handle());
155
156 if !builder.auxiliary {
157 let url = builder.url.unwrap_or(
158 Url::parse("about:blank").expect("Should always be able to parse 'about:blank'."),
159 );
160
161 servo
162 .constellation_proxy()
163 .send(EmbedderToConstellationMessage::NewWebView(
164 url.into(),
165 webview.id(),
166 viewport_details,
167 ));
168 }
169
170 webview
171 }
172
173 fn inner(&self) -> Ref<'_, WebViewInner> {
174 self.0.borrow()
175 }
176
177 fn inner_mut(&self) -> RefMut<'_, WebViewInner> {
178 self.0.borrow_mut()
179 }
180
181 pub(crate) fn viewport_details(&self) -> ViewportDetails {
182 let inner = self.inner();
185 let scaled_viewport_size = inner.rect.size() / inner.hidpi_scale_factor;
186 ViewportDetails {
187 size: scaled_viewport_size / Scale::new(1.0),
188 hidpi_scale_factor: Scale::new(inner.hidpi_scale_factor.0),
189 }
190 }
191
192 pub(crate) fn from_weak_handle(inner: &Weak<RefCell<WebViewInner>>) -> Option<Self> {
193 inner.upgrade().map(WebView)
194 }
195
196 pub(crate) fn weak_handle(&self) -> Weak<RefCell<WebViewInner>> {
197 Rc::downgrade(&self.0)
198 }
199
200 pub fn delegate(&self) -> Rc<dyn WebViewDelegate> {
201 self.inner().delegate.clone()
202 }
203
204 pub fn set_delegate(&self, delegate: Rc<dyn WebViewDelegate>) {
205 self.inner_mut().delegate = delegate;
206 }
207
208 pub fn clipboard_delegate(&self) -> Rc<dyn ClipboardDelegate> {
209 self.inner().clipboard_delegate.clone()
210 }
211
212 pub fn set_clipboard_delegate(&self, delegate: Rc<dyn ClipboardDelegate>) {
213 self.inner_mut().clipboard_delegate = delegate;
214 }
215
216 pub fn id(&self) -> WebViewId {
217 self.inner().id
218 }
219
220 pub fn load_status(&self) -> LoadStatus {
221 self.inner().load_status
222 }
223
224 pub(crate) fn set_load_status(self, new_value: LoadStatus) {
225 if self.inner().load_status == new_value {
226 return;
227 }
228 self.inner_mut().load_status = new_value;
229 self.delegate().notify_load_status_changed(self, new_value);
230 }
231
232 pub fn url(&self) -> Option<Url> {
233 let inner = self.inner();
234 inner
235 .back_forward_list
236 .get(inner.back_forward_list_index)
237 .cloned()
238 }
239
240 pub fn status_text(&self) -> Option<String> {
241 self.inner().status_text.clone()
242 }
243
244 pub(crate) fn set_status_text(self, new_value: Option<String>) {
245 if self.inner().status_text == new_value {
246 return;
247 }
248 self.inner_mut().status_text = new_value.clone();
249 self.delegate().notify_status_text_changed(self, new_value);
250 }
251
252 pub fn page_title(&self) -> Option<String> {
253 self.inner().page_title.clone()
254 }
255
256 pub(crate) fn set_page_title(self, new_value: Option<String>) {
257 if self.inner().page_title == new_value {
258 return;
259 }
260 self.inner_mut().page_title = new_value.clone();
261 self.delegate().notify_page_title_changed(self, new_value);
262 }
263
264 pub fn favicon(&self) -> Option<Ref<'_, Image>> {
265 Ref::filter_map(self.inner(), |inner| inner.favicon.as_ref()).ok()
266 }
267
268 pub(crate) fn set_favicon(self, new_value: Image) {
269 self.inner_mut().favicon = Some(new_value);
270 self.delegate().notify_favicon_changed(self);
271 }
272
273 pub fn focused(&self) -> bool {
274 self.inner().focused
275 }
276
277 pub(crate) fn set_focused(self, new_value: bool) {
278 if self.inner().focused == new_value {
279 return;
280 }
281 self.inner_mut().focused = new_value;
282 self.delegate().notify_focus_changed(self, new_value);
283 }
284
285 pub fn cursor(&self) -> Cursor {
286 self.inner().cursor
287 }
288
289 pub(crate) fn set_cursor(self, new_value: Cursor) {
290 if self.inner().cursor == new_value {
291 return;
292 }
293 self.inner_mut().cursor = new_value;
294 self.delegate().notify_cursor_changed(self, new_value);
295 }
296
297 pub fn focus(&self) {
298 self.inner()
299 .servo
300 .constellation_proxy()
301 .send(EmbedderToConstellationMessage::FocusWebView(self.id()));
302 }
303
304 pub fn blur(&self) {
305 self.inner()
306 .servo
307 .constellation_proxy()
308 .send(EmbedderToConstellationMessage::BlurWebView);
309 }
310
311 pub fn animating(self) -> bool {
316 self.inner().animating
317 }
318
319 pub(crate) fn set_animating(self, new_value: bool) {
320 if self.inner().animating == new_value {
321 return;
322 }
323 self.inner_mut().animating = new_value;
324 self.delegate().notify_animating_changed(self, new_value);
325 }
326
327 pub fn rect(&self) -> DeviceRect {
328 self.inner().rect
329 }
330
331 pub fn resize(&self, new_size: PhysicalSize<u32>) {
335 let new_size = PhysicalSize {
336 width: new_size.width.max(MINIMUM_WEBVIEW_SIZE.width as u32),
337 height: new_size.height.max(MINIMUM_WEBVIEW_SIZE.height as u32),
338 };
339
340 self.inner()
341 .servo
342 .compositor()
343 .resize_rendering_context(self.id(), new_size);
344 }
345
346 pub fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
347 self.inner().hidpi_scale_factor
348 }
349
350 pub fn set_hidpi_scale_factor(
351 &self,
352 new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
353 ) {
354 if self.inner().hidpi_scale_factor == new_scale_factor {
355 return;
356 }
357
358 self.inner_mut().hidpi_scale_factor = new_scale_factor;
359 self.inner()
360 .servo
361 .compositor()
362 .set_hidpi_scale_factor(self.id(), new_scale_factor);
363 }
364
365 pub fn show(&self) {
366 self.inner()
367 .servo
368 .compositor()
369 .show_webview(self.id())
370 .expect("BUG: invalid WebView instance");
371 }
372
373 pub fn hide(&self) {
374 self.inner()
375 .servo
376 .compositor()
377 .hide_webview(self.id())
378 .expect("BUG: invalid WebView instance");
379 }
380
381 pub fn notify_theme_change(&self, theme: Theme) {
382 self.inner()
383 .servo
384 .constellation_proxy()
385 .send(EmbedderToConstellationMessage::ThemeChange(
386 self.id(),
387 theme,
388 ))
389 }
390
391 pub fn load(&self, url: Url) {
392 self.inner()
393 .servo
394 .constellation_proxy()
395 .send(EmbedderToConstellationMessage::LoadUrl(
396 self.id(),
397 url.into(),
398 ))
399 }
400
401 pub fn reload(&self) {
402 self.inner()
403 .servo
404 .constellation_proxy()
405 .send(EmbedderToConstellationMessage::Reload(self.id()))
406 }
407
408 pub fn can_go_back(&self) -> bool {
409 self.inner().back_forward_list_index != 0
410 }
411
412 pub fn go_back(&self, amount: usize) -> TraversalId {
413 let traversal_id = TraversalId::new();
414 self.inner().servo.constellation_proxy().send(
415 EmbedderToConstellationMessage::TraverseHistory(
416 self.id(),
417 TraversalDirection::Back(amount),
418 traversal_id.clone(),
419 ),
420 );
421 traversal_id
422 }
423
424 pub fn can_go_forward(&self) -> bool {
425 let inner = self.inner();
426 inner.back_forward_list.len() > inner.back_forward_list_index + 1
427 }
428
429 pub fn go_forward(&self, amount: usize) -> TraversalId {
430 let traversal_id = TraversalId::new();
431 self.inner().servo.constellation_proxy().send(
432 EmbedderToConstellationMessage::TraverseHistory(
433 self.id(),
434 TraversalDirection::Forward(amount),
435 traversal_id.clone(),
436 ),
437 );
438 traversal_id
439 }
440
441 pub fn notify_scroll_event(&self, scroll: Scroll, point: WebViewPoint) {
444 self.inner()
445 .servo
446 .compositor()
447 .notify_scroll_event(self.id(), scroll, point);
448 }
449
450 pub fn notify_input_event(&self, event: InputEvent) -> InputEventId {
451 let event: InputEventAndId = event.into();
452 let event_id = event.id;
453
454 if event.event.point().is_some() {
456 self.inner()
457 .servo
458 .compositor()
459 .notify_input_event(self.id(), event);
460 } else {
461 self.inner().servo.constellation_proxy().send(
462 EmbedderToConstellationMessage::ForwardInputEvent(
463 self.id(),
464 event,
465 None, ),
467 );
468 }
469
470 event_id
471 }
472
473 pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) {
474 self.inner()
475 .servo
476 .constellation_proxy()
477 .send(EmbedderToConstellationMessage::MediaSessionAction(event));
478 }
479
480 pub fn set_page_zoom(&self, new_zoom: f32) {
492 self.inner()
493 .servo
494 .compositor()
495 .set_page_zoom(self.id(), new_zoom);
496 }
497
498 pub fn page_zoom(&self) -> f32 {
500 self.inner().servo.compositor().page_zoom(self.id())
501 }
502
503 pub fn pinch_zoom(&self, pinch_zoom_delta: f32, center: DevicePoint) {
513 self.inner()
514 .servo
515 .compositor()
516 .pinch_zoom(self.id(), pinch_zoom_delta, center);
517 }
518
519 pub fn device_pixels_per_css_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
520 self.inner()
521 .servo
522 .compositor()
523 .device_pixels_per_page_pixel(self.id())
524 }
525
526 pub fn exit_fullscreen(&self) {
527 self.inner()
528 .servo
529 .constellation_proxy()
530 .send(EmbedderToConstellationMessage::ExitFullScreen(self.id()));
531 }
532
533 pub fn set_throttled(&self, throttled: bool) {
534 self.inner().servo.constellation_proxy().send(
535 EmbedderToConstellationMessage::SetWebViewThrottled(self.id(), throttled),
536 );
537 }
538
539 pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) {
540 self.inner()
541 .servo
542 .compositor()
543 .toggle_webrender_debug(debugging);
544 }
545
546 pub fn capture_webrender(&self) {
547 self.inner().servo.compositor().capture_webrender(self.id());
548 }
549
550 pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
551 self.inner().servo.constellation_proxy().send(
552 EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration),
553 );
554 }
555
556 pub fn send_error(&self, message: String) {
557 self.inner()
558 .servo
559 .constellation_proxy()
560 .send(EmbedderToConstellationMessage::SendError(
561 Some(self.id()),
562 message,
563 ));
564 }
565
566 pub fn paint(&self) {
568 self.inner().servo.compositor().render(self.id());
569 }
570
571 pub fn evaluate_javascript<T: ToString>(
574 &self,
575 script: T,
576 callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static,
577 ) {
578 self.inner().servo.javascript_evaluator_mut().evaluate(
579 self.id(),
580 script.to_string(),
581 Box::new(callback),
582 );
583 }
584
585 pub fn take_screenshot(
603 &self,
604 rect: Option<WebViewRect>,
605 callback: impl FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static,
606 ) {
607 self.inner()
608 .servo
609 .compositor()
610 .request_screenshot(self.id(), rect, Box::new(callback));
611 }
612
613 pub(crate) fn set_history(self, new_back_forward_list: Vec<ServoUrl>, new_index: usize) {
614 {
615 let mut inner_mut = self.inner_mut();
616 inner_mut.back_forward_list_index = new_index;
617 inner_mut.back_forward_list = new_back_forward_list
618 .into_iter()
619 .map(ServoUrl::into_url)
620 .collect();
621 }
622
623 let back_forward_list = self.inner().back_forward_list.clone();
624 let back_forward_list_index = self.inner().back_forward_list_index;
625 self.delegate().notify_url_changed(
626 self.clone(),
627 back_forward_list[back_forward_list_index].clone(),
628 );
629 self.delegate().notify_history_changed(
630 self.clone(),
631 back_forward_list,
632 back_forward_list_index,
633 );
634 }
635
636 pub(crate) fn show_embedder_control(
637 self,
638 control_id: EmbedderControlId,
639 position: DeviceIntRect,
640 embedder_control_request: EmbedderControlRequest,
641 ) {
642 let constellation_proxy = self.inner().servo.constellation_proxy().clone();
643 let embedder_control = match embedder_control_request {
644 EmbedderControlRequest::SelectElement(options, selected_option) => {
645 EmbedderControl::SelectElement(SelectElement {
646 id: control_id,
647 options,
648 selected_option,
649 position,
650 constellation_proxy,
651 response_sent: false,
652 })
653 },
654 EmbedderControlRequest::ColorPicker(current_color) => {
655 EmbedderControl::ColorPicker(ColorPicker {
656 id: control_id,
657 current_color: Some(current_color),
658 position,
659 constellation_proxy,
660 response_sent: false,
661 })
662 },
663 EmbedderControlRequest::InputMethod(input_method_request) => {
664 EmbedderControl::InputMethod(InputMethodControl {
665 id: control_id,
666 input_method_type: input_method_request.input_method_type,
667 text: input_method_request.text,
668 insertion_point: input_method_request.insertion_point,
669 position,
670 multiline: input_method_request.multiline,
671 })
672 },
673 EmbedderControlRequest::ContextMenu(mut context_menu_request) => {
674 for item in context_menu_request.items.iter_mut() {
675 match item {
676 ContextMenuItem::Item {
677 action: ContextMenuAction::GoBack,
678 enabled,
679 ..
680 } => *enabled = self.can_go_back(),
681 ContextMenuItem::Item {
682 action: ContextMenuAction::GoForward,
683 enabled,
684 ..
685 } => *enabled = self.can_go_forward(),
686 _ => {},
687 }
688 }
689 EmbedderControl::ContextMenu(ContextMenu {
690 id: control_id,
691 position,
692 items: context_menu_request.items,
693 element_info: context_menu_request.element_info,
694 constellation_proxy,
695 response_sent: false,
696 })
697 },
698 EmbedderControlRequest::FilePicker { .. } => {
699 unreachable!("This message should be routed through the FileManagerThread")
700 },
701 };
702
703 self.delegate()
704 .show_embedder_control(self.clone(), embedder_control);
705 }
706}
707
708struct ServoRendererWebView {
711 id: WebViewId,
712 weak_handle: Weak<RefCell<WebViewInner>>,
713}
714
715impl WebViewTrait for ServoRendererWebView {
716 fn id(&self) -> WebViewId {
717 self.id
718 }
719
720 fn screen_geometry(&self) -> Option<ScreenGeometry> {
721 let webview = WebView::from_weak_handle(&self.weak_handle)?;
722 webview.delegate().screen_geometry(webview)
723 }
724
725 fn set_animating(&self, new_value: bool) {
726 if let Some(webview) = WebView::from_weak_handle(&self.weak_handle) {
727 webview.set_animating(new_value);
728 }
729 }
730}
731
732pub struct WebViewBuilder {
733 servo: Servo,
734 rendering_context: Rc<dyn RenderingContext>,
735 delegate: Rc<dyn WebViewDelegate>,
736 auxiliary: bool,
737 url: Option<Url>,
738 size: Option<PhysicalSize<u32>>,
739 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
740}
741
742impl WebViewBuilder {
743 pub fn new(servo: &Servo, rendering_context: Rc<dyn RenderingContext>) -> Self {
744 Self {
745 servo: servo.clone(),
746 rendering_context,
747 auxiliary: false,
748 url: None,
749 size: None,
750 hidpi_scale_factor: Scale::new(1.0),
751 delegate: Rc::new(DefaultWebViewDelegate),
752 }
753 }
754
755 pub fn new_auxiliary(servo: &Servo, rendering_context: Rc<dyn RenderingContext>) -> Self {
756 let mut builder = Self::new(servo, rendering_context);
757 builder.auxiliary = true;
758 builder
759 }
760
761 pub fn delegate(mut self, delegate: Rc<dyn WebViewDelegate>) -> Self {
762 self.delegate = delegate;
763 self
764 }
765
766 pub fn url(mut self, url: Url) -> Self {
767 self.url = Some(url);
768 self
769 }
770
771 pub fn size(mut self, size: PhysicalSize<u32>) -> Self {
772 self.size = Some(size);
773 self
774 }
775
776 pub fn hidpi_scale_factor(
777 mut self,
778 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
779 ) -> Self {
780 self.hidpi_scale_factor = hidpi_scale_factor;
781 self
782 }
783
784 pub fn build(self) -> WebView {
785 WebView::new(self)
786 }
787}