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