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