1use std::cell::{Ref, RefCell, RefMut};
6use std::hash::Hash;
7use std::rc::{Rc, Weak};
8use std::time::Duration;
9
10use base::generic_channel::GenericSender;
11use base::id::WebViewId;
12use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
13use dpi::PhysicalSize;
14use embedder_traits::{
15 ContextMenuAction, ContextMenuItem, Cursor, EmbedderControlId, EmbedderControlRequest, Image,
16 InputEvent, InputEventAndId, InputEventId, JSValue, JavaScriptEvaluationError, LoadStatus,
17 MediaSessionActionType, NewWebViewDetails, ScreenGeometry, ScreenshotCaptureError, Scroll,
18 Theme, TraversalId, ViewportDetails, WebViewPoint, WebViewRect,
19};
20use euclid::{Scale, Size2D};
21use image::RgbaImage;
22use paint_api::WebViewTrait;
23use paint_api::rendering_context::RenderingContext;
24use servo_geometry::DeviceIndependentPixel;
25use servo_url::ServoUrl;
26use style_traits::CSSPixel;
27use url::Url;
28use webrender_api::units::{DeviceIntRect, DevicePixel, DevicePoint, DeviceSize};
29
30use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
31#[cfg(feature = "gamepad")]
32use crate::gamepad_provider::{DefaultGamepadProvider, GamepadProvider};
33use crate::responders::IpcResponder;
34use crate::webview_delegate::{CreateNewWebViewRequest, DefaultWebViewDelegate, WebViewDelegate};
35use crate::{
36 ColorPicker, ContextMenu, EmbedderControl, InputMethodControl, SelectElement, Servo,
37 UserContentManager, WebRenderDebugOption,
38};
39
40pub(crate) const MINIMUM_WEBVIEW_SIZE: Size2D<i32, DevicePixel> = Size2D::new(1, 1);
41
42#[derive(Clone)]
70pub struct WebView(Rc<RefCell<WebViewInner>>);
71
72impl PartialEq for WebView {
73 fn eq(&self, other: &Self) -> bool {
74 self.inner().id == other.inner().id
75 }
76}
77
78impl Hash for WebView {
79 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
80 self.inner().id.hash(state);
81 }
82}
83
84pub(crate) struct WebViewInner {
85 pub(crate) id: WebViewId,
86 pub(crate) servo: Servo,
87 pub(crate) delegate: Rc<dyn WebViewDelegate>,
88 pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>,
89 #[cfg(feature = "gamepad")]
90 pub(crate) gamepad_provider: Rc<dyn GamepadProvider>,
91
92 rendering_context: Rc<dyn RenderingContext>,
93 user_content_manager: Option<Rc<UserContentManager>>,
94 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
95 load_status: LoadStatus,
96 status_text: Option<String>,
97 page_title: Option<String>,
98 favicon: Option<Image>,
99 focused: bool,
100 animating: bool,
101 cursor: Cursor,
102
103 back_forward_list: Vec<Url>,
105
106 back_forward_list_index: usize,
108}
109
110impl Drop for WebViewInner {
111 fn drop(&mut self) {
112 self.servo
113 .constellation_proxy()
114 .send(EmbedderToConstellationMessage::CloseWebView(self.id));
115 self.servo.paint_mut().remove_webview(self.id);
116 }
117}
118
119impl WebView {
120 pub(crate) fn new(mut builder: WebViewBuilder) -> Self {
121 let servo = builder.servo;
122 let painter_id = servo
123 .paint_mut()
124 .register_rendering_context(builder.rendering_context.clone());
125
126 let id = WebViewId::new(painter_id);
127 let webview = Self(Rc::new(RefCell::new(WebViewInner {
128 id,
129 servo: servo.clone(),
130 rendering_context: builder.rendering_context,
131 delegate: builder.delegate,
132 clipboard_delegate: Rc::new(DefaultClipboardDelegate),
133 #[cfg(feature = "gamepad")]
134 gamepad_provider: Rc::new(DefaultGamepadProvider),
135 hidpi_scale_factor: builder.hidpi_scale_factor,
136 load_status: LoadStatus::Started,
137 status_text: None,
138 page_title: None,
139 favicon: None,
140 focused: false,
141 animating: false,
142 cursor: Cursor::Pointer,
143 back_forward_list: Default::default(),
144 back_forward_list_index: 0,
145 user_content_manager: builder.user_content_manager.clone(),
146 })));
147
148 let viewport_details = webview.viewport_details();
149 servo.paint().add_webview(
150 Box::new(ServoRendererWebView {
151 weak_handle: webview.weak_handle(),
152 id,
153 }),
154 viewport_details,
155 );
156
157 servo
158 .webviews_mut()
159 .insert(webview.id(), webview.weak_handle());
160
161 let user_content_manager_id = builder
162 .user_content_manager
163 .as_ref()
164 .map(|user_content_manager| user_content_manager.id());
165
166 let new_webview_details = NewWebViewDetails {
167 webview_id: webview.id(),
168 viewport_details,
169 user_content_manager_id,
170 };
171
172 match builder.create_new_webview_responder.as_mut() {
178 Some(responder) => {
179 let _ = responder.send(Some(new_webview_details));
180 },
181 None => {
182 let url = builder.url.unwrap_or(
183 Url::parse("about:blank")
184 .expect("Should always be able to parse 'about:blank'."),
185 );
186
187 servo
188 .constellation_proxy()
189 .send(EmbedderToConstellationMessage::NewWebView(
190 url.into(),
191 new_webview_details,
192 ));
193 },
194 }
195
196 webview
197 }
198
199 fn inner(&self) -> Ref<'_, WebViewInner> {
200 self.0.borrow()
201 }
202
203 fn inner_mut(&self) -> RefMut<'_, WebViewInner> {
204 self.0.borrow_mut()
205 }
206
207 pub(crate) fn request_create_new(
208 &self,
209 response_sender: GenericSender<Option<NewWebViewDetails>>,
210 ) {
211 let request = CreateNewWebViewRequest {
212 servo: self.inner().servo.clone(),
213 responder: IpcResponder::new(response_sender, None),
214 };
215 self.delegate().request_create_new(self.clone(), request);
216 }
217
218 pub(crate) fn viewport_details(&self) -> ViewportDetails {
219 let inner = self.inner();
222 let scaled_viewport_size =
223 inner.rendering_context.size2d().to_f32() / inner.hidpi_scale_factor;
224 ViewportDetails {
225 size: scaled_viewport_size / Scale::new(1.0),
226 hidpi_scale_factor: Scale::new(inner.hidpi_scale_factor.0),
227 }
228 }
229
230 pub(crate) fn from_weak_handle(inner: &Weak<RefCell<WebViewInner>>) -> Option<Self> {
231 inner.upgrade().map(WebView)
232 }
233
234 pub(crate) fn weak_handle(&self) -> Weak<RefCell<WebViewInner>> {
235 Rc::downgrade(&self.0)
236 }
237
238 pub fn delegate(&self) -> Rc<dyn WebViewDelegate> {
239 self.inner().delegate.clone()
240 }
241
242 pub fn set_delegate(&self, delegate: Rc<dyn WebViewDelegate>) {
243 self.inner_mut().delegate = delegate;
244 }
245
246 pub fn clipboard_delegate(&self) -> Rc<dyn ClipboardDelegate> {
247 self.inner().clipboard_delegate.clone()
248 }
249
250 pub fn set_clipboard_delegate(&self, delegate: Rc<dyn ClipboardDelegate>) {
251 self.inner_mut().clipboard_delegate = delegate;
252 }
253
254 #[cfg(feature = "gamepad")]
255 pub fn gamepad_provider(&self) -> Rc<dyn GamepadProvider> {
256 self.inner().gamepad_provider.clone()
257 }
258
259 #[cfg(feature = "gamepad")]
260 pub fn set_gamepad_provider(&self, provider: Rc<dyn GamepadProvider>) {
261 self.inner_mut().gamepad_provider = provider;
262 }
263
264 pub fn id(&self) -> WebViewId {
265 self.inner().id
266 }
267
268 pub fn load_status(&self) -> LoadStatus {
269 self.inner().load_status
270 }
271
272 pub(crate) fn set_load_status(self, new_value: LoadStatus) {
273 if self.inner().load_status == new_value {
274 return;
275 }
276 self.inner_mut().load_status = new_value;
277 self.delegate().notify_load_status_changed(self, new_value);
278 }
279
280 pub fn url(&self) -> Option<Url> {
281 let inner = self.inner();
282 inner
283 .back_forward_list
284 .get(inner.back_forward_list_index)
285 .cloned()
286 }
287
288 pub fn status_text(&self) -> Option<String> {
289 self.inner().status_text.clone()
290 }
291
292 pub(crate) fn set_status_text(self, new_value: Option<String>) {
293 if self.inner().status_text == new_value {
294 return;
295 }
296 self.inner_mut().status_text = new_value.clone();
297 self.delegate().notify_status_text_changed(self, new_value);
298 }
299
300 pub fn page_title(&self) -> Option<String> {
301 self.inner().page_title.clone()
302 }
303
304 pub(crate) fn set_page_title(self, new_value: Option<String>) {
305 if self.inner().page_title == new_value {
306 return;
307 }
308 self.inner_mut().page_title = new_value.clone();
309 self.delegate().notify_page_title_changed(self, new_value);
310 }
311
312 pub fn favicon(&self) -> Option<Ref<'_, Image>> {
313 Ref::filter_map(self.inner(), |inner| inner.favicon.as_ref()).ok()
314 }
315
316 pub(crate) fn set_favicon(self, new_value: Image) {
317 self.inner_mut().favicon = Some(new_value);
318 self.delegate().notify_favicon_changed(self);
319 }
320
321 pub fn focused(&self) -> bool {
322 self.inner().focused
323 }
324
325 pub(crate) fn set_focused(self, new_value: bool) {
326 if self.inner().focused == new_value {
327 return;
328 }
329 self.inner_mut().focused = new_value;
330 self.delegate().notify_focus_changed(self, new_value);
331 }
332
333 pub fn cursor(&self) -> Cursor {
334 self.inner().cursor
335 }
336
337 pub(crate) fn set_cursor(self, new_value: Cursor) {
338 if self.inner().cursor == new_value {
339 return;
340 }
341 self.inner_mut().cursor = new_value;
342 self.delegate().notify_cursor_changed(self, new_value);
343 }
344
345 pub fn focus(&self) {
346 self.inner()
347 .servo
348 .constellation_proxy()
349 .send(EmbedderToConstellationMessage::FocusWebView(self.id()));
350 }
351
352 pub fn blur(&self) {
353 self.inner()
354 .servo
355 .constellation_proxy()
356 .send(EmbedderToConstellationMessage::BlurWebView);
357 }
358
359 pub fn animating(self) -> bool {
364 self.inner().animating
365 }
366
367 pub(crate) fn set_animating(self, new_value: bool) {
368 if self.inner().animating == new_value {
369 return;
370 }
371 self.inner_mut().animating = new_value;
372 self.delegate().notify_animating_changed(self, new_value);
373 }
374
375 pub fn size(&self) -> DeviceSize {
377 self.inner().rendering_context.size2d().to_f32()
378 }
379
380 pub fn resize(&self, new_size: PhysicalSize<u32>) {
387 let new_size = PhysicalSize {
388 width: new_size.width.max(MINIMUM_WEBVIEW_SIZE.width as u32),
389 height: new_size.height.max(MINIMUM_WEBVIEW_SIZE.height as u32),
390 };
391
392 self.inner()
393 .servo
394 .paint()
395 .resize_rendering_context(self.id(), new_size);
396 }
397
398 pub fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
399 self.inner().hidpi_scale_factor
400 }
401
402 pub fn set_hidpi_scale_factor(
403 &self,
404 new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
405 ) {
406 if self.inner().hidpi_scale_factor == new_scale_factor {
407 return;
408 }
409
410 self.inner_mut().hidpi_scale_factor = new_scale_factor;
411 self.inner()
412 .servo
413 .paint()
414 .set_hidpi_scale_factor(self.id(), new_scale_factor);
415 }
416
417 pub fn show(&self) {
418 self.inner()
419 .servo
420 .paint()
421 .show_webview(self.id())
422 .expect("BUG: invalid WebView instance");
423 }
424
425 pub fn hide(&self) {
426 self.inner()
427 .servo
428 .paint()
429 .hide_webview(self.id())
430 .expect("BUG: invalid WebView instance");
431 }
432
433 pub fn notify_theme_change(&self, theme: Theme) {
434 self.inner()
435 .servo
436 .constellation_proxy()
437 .send(EmbedderToConstellationMessage::ThemeChange(
438 self.id(),
439 theme,
440 ))
441 }
442
443 pub fn load(&self, url: Url) {
444 self.inner()
445 .servo
446 .constellation_proxy()
447 .send(EmbedderToConstellationMessage::LoadUrl(
448 self.id(),
449 url.into(),
450 ))
451 }
452
453 pub fn reload(&self) {
454 self.inner_mut().load_status = LoadStatus::Started;
455 self.inner()
456 .servo
457 .constellation_proxy()
458 .send(EmbedderToConstellationMessage::Reload(self.id()))
459 }
460
461 pub fn can_go_back(&self) -> bool {
462 self.inner().back_forward_list_index != 0
463 }
464
465 pub fn go_back(&self, amount: usize) -> TraversalId {
466 let traversal_id = TraversalId::new();
467 self.inner().servo.constellation_proxy().send(
468 EmbedderToConstellationMessage::TraverseHistory(
469 self.id(),
470 TraversalDirection::Back(amount),
471 traversal_id.clone(),
472 ),
473 );
474 traversal_id
475 }
476
477 pub fn can_go_forward(&self) -> bool {
478 let inner = self.inner();
479 inner.back_forward_list.len() > inner.back_forward_list_index + 1
480 }
481
482 pub fn go_forward(&self, amount: usize) -> TraversalId {
483 let traversal_id = TraversalId::new();
484 self.inner().servo.constellation_proxy().send(
485 EmbedderToConstellationMessage::TraverseHistory(
486 self.id(),
487 TraversalDirection::Forward(amount),
488 traversal_id.clone(),
489 ),
490 );
491 traversal_id
492 }
493
494 pub fn notify_scroll_event(&self, scroll: Scroll, point: WebViewPoint) {
497 self.inner()
498 .servo
499 .paint()
500 .notify_scroll_event(self.id(), scroll, point);
501 }
502
503 pub fn notify_input_event(&self, event: InputEvent) -> InputEventId {
504 let event: InputEventAndId = event.into();
505 let event_id = event.id;
506
507 if event.event.point().is_some() {
509 self.inner()
510 .servo
511 .paint()
512 .notify_input_event(self.id(), event);
513 } else {
514 self.inner().servo.constellation_proxy().send(
515 EmbedderToConstellationMessage::ForwardInputEvent(
516 self.id(),
517 event,
518 None, ),
520 );
521 }
522
523 event_id
524 }
525
526 pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) {
527 self.inner()
528 .servo
529 .constellation_proxy()
530 .send(EmbedderToConstellationMessage::MediaSessionAction(event));
531 }
532
533 pub fn set_page_zoom(&self, new_zoom: f32) {
545 self.inner()
546 .servo
547 .paint()
548 .set_page_zoom(self.id(), new_zoom);
549 }
550
551 pub fn page_zoom(&self) -> f32 {
553 self.inner().servo.paint().page_zoom(self.id())
554 }
555
556 pub fn pinch_zoom(&self, pinch_zoom_delta: f32, center: DevicePoint) {
566 self.inner()
567 .servo
568 .paint()
569 .pinch_zoom(self.id(), pinch_zoom_delta, center);
570 }
571
572 pub fn device_pixels_per_css_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
573 self.inner()
574 .servo
575 .paint()
576 .device_pixels_per_page_pixel(self.id())
577 }
578
579 pub fn exit_fullscreen(&self) {
580 self.inner()
581 .servo
582 .constellation_proxy()
583 .send(EmbedderToConstellationMessage::ExitFullScreen(self.id()));
584 }
585
586 pub fn set_throttled(&self, throttled: bool) {
587 self.inner().servo.constellation_proxy().send(
588 EmbedderToConstellationMessage::SetWebViewThrottled(self.id(), throttled),
589 );
590 }
591
592 pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) {
593 self.inner().servo.paint().toggle_webrender_debug(debugging);
594 }
595
596 pub fn capture_webrender(&self) {
597 self.inner().servo.paint().capture_webrender(self.id());
598 }
599
600 pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
601 self.inner().servo.constellation_proxy().send(
602 EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration),
603 );
604 }
605
606 pub fn send_error(&self, message: String) {
607 self.inner()
608 .servo
609 .constellation_proxy()
610 .send(EmbedderToConstellationMessage::SendError(
611 Some(self.id()),
612 message,
613 ));
614 }
615
616 pub fn paint(&self) {
618 self.inner().servo.paint().render(self.id());
619 }
620
621 pub fn user_content_manager(&self) -> Option<Rc<UserContentManager>> {
623 self.inner().user_content_manager.clone()
624 }
625
626 pub fn evaluate_javascript<T: ToString>(
629 &self,
630 script: T,
631 callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static,
632 ) {
633 self.inner().servo.javascript_evaluator_mut().evaluate(
634 self.id(),
635 script.to_string(),
636 Box::new(callback),
637 );
638 }
639
640 pub fn take_screenshot(
658 &self,
659 rect: Option<WebViewRect>,
660 callback: impl FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static,
661 ) {
662 self.inner()
663 .servo
664 .paint()
665 .request_screenshot(self.id(), rect, Box::new(callback));
666 }
667
668 pub(crate) fn set_history(self, new_back_forward_list: Vec<ServoUrl>, new_index: usize) {
669 {
670 let mut inner_mut = self.inner_mut();
671 inner_mut.back_forward_list_index = new_index;
672 inner_mut.back_forward_list = new_back_forward_list
673 .into_iter()
674 .map(ServoUrl::into_url)
675 .collect();
676 }
677
678 let back_forward_list = self.inner().back_forward_list.clone();
679 let back_forward_list_index = self.inner().back_forward_list_index;
680 self.delegate().notify_url_changed(
681 self.clone(),
682 back_forward_list[back_forward_list_index].clone(),
683 );
684 self.delegate().notify_history_changed(
685 self.clone(),
686 back_forward_list,
687 back_forward_list_index,
688 );
689 }
690
691 pub(crate) fn show_embedder_control(
692 self,
693 control_id: EmbedderControlId,
694 position: DeviceIntRect,
695 embedder_control_request: EmbedderControlRequest,
696 ) {
697 let constellation_proxy = self.inner().servo.constellation_proxy().clone();
698 let embedder_control = match embedder_control_request {
699 EmbedderControlRequest::SelectElement(options, selected_option) => {
700 EmbedderControl::SelectElement(SelectElement {
701 id: control_id,
702 options,
703 selected_option,
704 position,
705 constellation_proxy,
706 response_sent: false,
707 })
708 },
709 EmbedderControlRequest::ColorPicker(current_color) => {
710 EmbedderControl::ColorPicker(ColorPicker {
711 id: control_id,
712 current_color: Some(current_color),
713 position,
714 constellation_proxy,
715 response_sent: false,
716 })
717 },
718 EmbedderControlRequest::InputMethod(input_method_request) => {
719 EmbedderControl::InputMethod(InputMethodControl {
720 id: control_id,
721 input_method_type: input_method_request.input_method_type,
722 text: input_method_request.text,
723 insertion_point: input_method_request.insertion_point,
724 position,
725 multiline: input_method_request.multiline,
726 })
727 },
728 EmbedderControlRequest::ContextMenu(mut context_menu_request) => {
729 for item in context_menu_request.items.iter_mut() {
730 match item {
731 ContextMenuItem::Item {
732 action: ContextMenuAction::GoBack,
733 enabled,
734 ..
735 } => *enabled = self.can_go_back(),
736 ContextMenuItem::Item {
737 action: ContextMenuAction::GoForward,
738 enabled,
739 ..
740 } => *enabled = self.can_go_forward(),
741 _ => {},
742 }
743 }
744 EmbedderControl::ContextMenu(ContextMenu {
745 id: control_id,
746 position,
747 items: context_menu_request.items,
748 element_info: context_menu_request.element_info,
749 constellation_proxy,
750 response_sent: false,
751 })
752 },
753 EmbedderControlRequest::FilePicker { .. } => {
754 unreachable!("This message should be routed through the FileManagerThread")
755 },
756 };
757
758 self.delegate()
759 .show_embedder_control(self.clone(), embedder_control);
760 }
761}
762
763struct ServoRendererWebView {
766 id: WebViewId,
767 weak_handle: Weak<RefCell<WebViewInner>>,
768}
769
770impl WebViewTrait for ServoRendererWebView {
771 fn id(&self) -> WebViewId {
772 self.id
773 }
774
775 fn screen_geometry(&self) -> Option<ScreenGeometry> {
776 let webview = WebView::from_weak_handle(&self.weak_handle)?;
777 webview.delegate().screen_geometry(webview)
778 }
779
780 fn set_animating(&self, new_value: bool) {
781 if let Some(webview) = WebView::from_weak_handle(&self.weak_handle) {
782 webview.set_animating(new_value);
783 }
784 }
785}
786
787pub struct WebViewBuilder {
788 servo: Servo,
789 rendering_context: Rc<dyn RenderingContext>,
790 delegate: Rc<dyn WebViewDelegate>,
791 url: Option<Url>,
792 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
793 create_new_webview_responder: Option<IpcResponder<Option<NewWebViewDetails>>>,
794 user_content_manager: Option<Rc<UserContentManager>>,
795}
796
797impl WebViewBuilder {
798 pub fn new(servo: &Servo, rendering_context: Rc<dyn RenderingContext>) -> Self {
799 Self {
800 servo: servo.clone(),
801 rendering_context,
802 url: None,
803 hidpi_scale_factor: Scale::new(1.0),
804 delegate: Rc::new(DefaultWebViewDelegate),
805 create_new_webview_responder: None,
806 user_content_manager: None,
807 }
808 }
809
810 pub(crate) fn new_for_create_request(
811 servo: &Servo,
812 rendering_context: Rc<dyn RenderingContext>,
813 responder: IpcResponder<Option<NewWebViewDetails>>,
814 ) -> Self {
815 let mut builder = Self::new(servo, rendering_context);
816 builder.create_new_webview_responder = Some(responder);
817 builder
818 }
819
820 pub fn delegate(mut self, delegate: Rc<dyn WebViewDelegate>) -> Self {
821 self.delegate = delegate;
822 self
823 }
824
825 pub fn url(mut self, url: Url) -> Self {
826 self.url = Some(url);
827 self
828 }
829
830 pub fn hidpi_scale_factor(
831 mut self,
832 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
833 ) -> Self {
834 self.hidpi_scale_factor = hidpi_scale_factor;
835 self
836 }
837
838 pub fn user_content_manager(mut self, user_content_manager: Rc<UserContentManager>) -> Self {
842 self.user_content_manager = Some(user_content_manager);
843 self
844 }
845
846 pub fn build(self) -> WebView {
847 WebView::new(self)
848 }
849}