1use std::cell::{Ref, RefCell, RefMut};
6use std::hash::Hash;
7use std::rc::{Rc, Weak};
8use std::time::Duration;
9
10use accesskit::{
11 Node as AccesskitNode, NodeId, Role, Tree, TreeId, TreeUpdate, Uuid as AccesskitUuid,
12};
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_base::generic_channel::GenericSender;
25use servo_base::id::WebViewId;
26use servo_config::pref;
27use servo_constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
28use servo_geometry::DeviceIndependentPixel;
29use servo_url::ServoUrl;
30use style_traits::CSSPixel;
31use url::Url;
32use webrender_api::units::{DeviceIntRect, DevicePixel, DevicePoint, DeviceSize};
33
34use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
35#[cfg(feature = "gamepad")]
36use crate::gamepad_delegate::{DefaultGamepadDelegate, GamepadDelegate};
37use crate::responders::IpcResponder;
38use crate::servo::PendingHandledInputEvent;
39use crate::webview_delegate::{CreateNewWebViewRequest, DefaultWebViewDelegate, WebViewDelegate};
40use crate::{
41 ColorPicker, ContextMenu, EmbedderControl, InputMethodControl, SelectElement, Servo,
42 UserContentManager, WebRenderDebugOption,
43};
44
45pub(crate) const MINIMUM_WEBVIEW_SIZE: Size2D<i32, DevicePixel> = Size2D::new(1, 1);
46
47#[derive(Clone)]
79pub struct WebView(Rc<RefCell<WebViewInner>>);
80
81impl PartialEq for WebView {
82 fn eq(&self, other: &Self) -> bool {
83 self.inner().id == other.inner().id
84 }
85}
86
87impl Hash for WebView {
88 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
89 self.inner().id.hash(state);
90 }
91}
92
93pub(crate) struct WebViewInner {
94 pub(crate) id: WebViewId,
95 pub(crate) servo: Servo,
96 pub(crate) delegate: Rc<dyn WebViewDelegate>,
97 pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>,
98 #[cfg(feature = "gamepad")]
99 pub(crate) gamepad_delegate: Rc<dyn GamepadDelegate>,
100
101 pub(crate) accesskit_tree_id: Option<TreeId>,
106 pub(crate) grafted_accesskit_tree_id: Option<TreeId>,
109
110 rendering_context: Rc<dyn RenderingContext>,
111 user_content_manager: Option<Rc<UserContentManager>>,
112 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
113 load_status: LoadStatus,
114 status_text: Option<String>,
115 page_title: Option<String>,
116 favicon: Option<Image>,
117 focused: bool,
118 animating: bool,
119 cursor: Cursor,
120
121 back_forward_list: Vec<Url>,
123
124 back_forward_list_index: usize,
126}
127
128impl Drop for WebViewInner {
129 fn drop(&mut self) {
130 self.servo
131 .constellation_proxy()
132 .send(EmbedderToConstellationMessage::CloseWebView(self.id));
133 self.servo.paint_mut().remove_webview(self.id);
134 }
135}
136
137impl WebView {
138 pub(crate) fn new(mut builder: WebViewBuilder) -> Self {
139 let servo = builder.servo;
140 let painter_id = servo
141 .paint_mut()
142 .register_rendering_context(builder.rendering_context.clone());
143
144 let id = WebViewId::new(painter_id);
145 let webview = Self(Rc::new(RefCell::new(WebViewInner {
146 id,
147 servo: servo.clone(),
148 rendering_context: builder.rendering_context,
149 delegate: builder.delegate,
150 clipboard_delegate: builder
151 .clipboard_delegate
152 .unwrap_or_else(|| Rc::new(DefaultClipboardDelegate)),
153 #[cfg(feature = "gamepad")]
154 gamepad_delegate: builder
155 .gamepad_delegate
156 .unwrap_or_else(|| Rc::new(DefaultGamepadDelegate)),
157 accesskit_tree_id: None,
158 grafted_accesskit_tree_id: None,
159 hidpi_scale_factor: builder.hidpi_scale_factor,
160 load_status: LoadStatus::Started,
161 status_text: None,
162 page_title: None,
163 favicon: None,
164 focused: false,
165 animating: false,
166 cursor: Cursor::Pointer,
167 back_forward_list: Default::default(),
168 back_forward_list_index: 0,
169 user_content_manager: builder.user_content_manager.clone(),
170 })));
171
172 let viewport_details = webview.viewport_details();
173 servo.paint().add_webview(
174 Box::new(ServoRendererWebView {
175 weak_handle: webview.weak_handle(),
176 id,
177 }),
178 viewport_details,
179 );
180
181 servo
182 .webviews_mut()
183 .insert(webview.id(), webview.weak_handle());
184
185 let user_content_manager_id = builder
186 .user_content_manager
187 .as_ref()
188 .map(|user_content_manager| user_content_manager.id());
189
190 let new_webview_details = NewWebViewDetails {
191 webview_id: webview.id(),
192 viewport_details,
193 user_content_manager_id,
194 };
195
196 match builder.create_new_webview_responder.as_mut() {
202 Some(responder) => {
203 let _ = responder.send(Some(new_webview_details));
204 },
205 None => {
206 let url = builder.url.unwrap_or(
207 Url::parse("about:blank")
208 .expect("Should always be able to parse 'about:blank'."),
209 );
210
211 servo
212 .constellation_proxy()
213 .send(EmbedderToConstellationMessage::NewWebView(
214 url.into(),
215 new_webview_details,
216 ));
217 },
218 }
219
220 webview
221 }
222
223 fn inner(&self) -> Ref<'_, WebViewInner> {
224 self.0.borrow()
225 }
226
227 fn inner_mut(&self) -> RefMut<'_, WebViewInner> {
228 self.0.borrow_mut()
229 }
230
231 pub(crate) fn request_create_new(
232 &self,
233 response_sender: GenericSender<Option<NewWebViewDetails>>,
234 ) {
235 let request = CreateNewWebViewRequest {
236 servo: self.inner().servo.clone(),
237 responder: IpcResponder::new(response_sender, None),
238 };
239 self.delegate().request_create_new(self.clone(), request);
240 }
241
242 pub(crate) fn viewport_details(&self) -> ViewportDetails {
243 let inner = self.inner();
246 let scaled_viewport_size =
247 inner.rendering_context.size2d().to_f32() / inner.hidpi_scale_factor;
248 ViewportDetails {
249 size: scaled_viewport_size / Scale::new(1.0),
250 hidpi_scale_factor: Scale::new(inner.hidpi_scale_factor.0),
251 }
252 }
253
254 pub(crate) fn from_weak_handle(inner: &Weak<RefCell<WebViewInner>>) -> Option<Self> {
255 inner.upgrade().map(WebView)
256 }
257
258 pub(crate) fn weak_handle(&self) -> Weak<RefCell<WebViewInner>> {
259 Rc::downgrade(&self.0)
260 }
261
262 pub fn delegate(&self) -> Rc<dyn WebViewDelegate> {
263 self.inner().delegate.clone()
264 }
265
266 pub fn clipboard_delegate(&self) -> Rc<dyn ClipboardDelegate> {
267 self.inner().clipboard_delegate.clone()
268 }
269
270 #[cfg(feature = "gamepad")]
271 pub fn gamepad_delegate(&self) -> Rc<dyn GamepadDelegate> {
272 self.inner().gamepad_delegate.clone()
273 }
274
275 pub fn id(&self) -> WebViewId {
276 self.inner().id
277 }
278
279 pub fn load_status(&self) -> LoadStatus {
280 self.inner().load_status
281 }
282
283 pub(crate) fn set_load_status(self, new_value: LoadStatus) {
284 if self.inner().load_status == new_value {
285 return;
286 }
287 self.inner_mut().load_status = new_value;
288 self.delegate().notify_load_status_changed(self, new_value);
289 }
290
291 pub fn url(&self) -> Option<Url> {
292 let inner = self.inner();
293 inner
294 .back_forward_list
295 .get(inner.back_forward_list_index)
296 .cloned()
297 }
298
299 pub fn status_text(&self) -> Option<String> {
300 self.inner().status_text.clone()
301 }
302
303 pub(crate) fn set_status_text(self, new_value: Option<String>) {
304 if self.inner().status_text == new_value {
305 return;
306 }
307 self.inner_mut().status_text = new_value.clone();
308 self.delegate().notify_status_text_changed(self, new_value);
309 }
310
311 pub fn page_title(&self) -> Option<String> {
312 self.inner().page_title.clone()
313 }
314
315 pub(crate) fn set_page_title(self, new_value: Option<String>) {
316 if self.inner().page_title == new_value {
317 return;
318 }
319 self.inner_mut().page_title = new_value.clone();
320 self.delegate().notify_page_title_changed(self, new_value);
321 }
322
323 pub fn favicon(&self) -> Option<Ref<'_, Image>> {
324 Ref::filter_map(self.inner(), |inner| inner.favicon.as_ref()).ok()
325 }
326
327 pub(crate) fn set_favicon(self, new_value: Image) {
328 self.inner_mut().favicon = Some(new_value);
329 self.delegate().notify_favicon_changed(self);
330 }
331
332 pub fn focused(&self) -> bool {
333 self.inner().focused
334 }
335
336 pub(crate) fn set_focused(self, new_value: bool) {
337 if self.inner().focused == new_value {
338 return;
339 }
340 self.inner_mut().focused = new_value;
341 self.delegate().notify_focus_changed(self, new_value);
342 }
343
344 pub fn cursor(&self) -> Cursor {
345 self.inner().cursor
346 }
347
348 pub(crate) fn set_cursor(self, new_value: Cursor) {
349 if self.inner().cursor == new_value {
350 return;
351 }
352 self.inner_mut().cursor = new_value;
353 self.delegate().notify_cursor_changed(self, new_value);
354 }
355
356 pub fn focus(&self) {
357 self.inner()
358 .servo
359 .constellation_proxy()
360 .send(EmbedderToConstellationMessage::FocusWebView(self.id()));
361 }
362
363 pub fn blur(&self) {
364 self.inner()
365 .servo
366 .constellation_proxy()
367 .send(EmbedderToConstellationMessage::BlurWebView);
368 }
369
370 pub fn animating(self) -> bool {
375 self.inner().animating
376 }
377
378 pub(crate) fn set_animating(self, new_value: bool) {
379 if self.inner().animating == new_value {
380 return;
381 }
382 self.inner_mut().animating = new_value;
383 self.delegate().notify_animating_changed(self, new_value);
384 }
385
386 pub fn size(&self) -> DeviceSize {
388 self.inner().rendering_context.size2d().to_f32()
389 }
390
391 pub fn resize(&self, new_size: PhysicalSize<u32>) {
398 let new_size = PhysicalSize {
399 width: new_size.width.max(MINIMUM_WEBVIEW_SIZE.width as u32),
400 height: new_size.height.max(MINIMUM_WEBVIEW_SIZE.height as u32),
401 };
402
403 self.inner()
404 .servo
405 .paint()
406 .resize_rendering_context(self.id(), new_size);
407 }
408
409 pub fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
410 self.inner().hidpi_scale_factor
411 }
412
413 pub fn set_hidpi_scale_factor(
414 &self,
415 new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
416 ) {
417 if self.inner().hidpi_scale_factor == new_scale_factor {
418 return;
419 }
420
421 self.inner_mut().hidpi_scale_factor = new_scale_factor;
422 self.inner()
423 .servo
424 .paint()
425 .set_hidpi_scale_factor(self.id(), new_scale_factor);
426 }
427
428 pub fn show(&self) {
429 self.inner()
430 .servo
431 .paint()
432 .show_webview(self.id())
433 .expect("BUG: invalid WebView instance");
434 }
435
436 pub fn hide(&self) {
437 self.inner()
438 .servo
439 .paint()
440 .hide_webview(self.id())
441 .expect("BUG: invalid WebView instance");
442 }
443
444 pub fn notify_theme_change(&self, theme: Theme) {
445 self.inner()
446 .servo
447 .constellation_proxy()
448 .send(EmbedderToConstellationMessage::ThemeChange(
449 self.id(),
450 theme,
451 ))
452 }
453
454 pub fn load(&self, url: Url) {
455 self.inner()
456 .servo
457 .constellation_proxy()
458 .send(EmbedderToConstellationMessage::LoadUrl(
459 self.id(),
460 url.into(),
461 ))
462 }
463
464 pub fn reload(&self) {
465 self.inner_mut().load_status = LoadStatus::Started;
466 self.inner()
467 .servo
468 .constellation_proxy()
469 .send(EmbedderToConstellationMessage::Reload(self.id()))
470 }
471
472 pub fn can_go_back(&self) -> bool {
473 self.inner().back_forward_list_index != 0
474 }
475
476 pub fn go_back(&self, amount: usize) -> TraversalId {
477 let traversal_id = TraversalId::new();
478 self.inner().servo.constellation_proxy().send(
479 EmbedderToConstellationMessage::TraverseHistory(
480 self.id(),
481 TraversalDirection::Back(amount),
482 traversal_id.clone(),
483 ),
484 );
485 traversal_id
486 }
487
488 pub fn can_go_forward(&self) -> bool {
489 let inner = self.inner();
490 inner.back_forward_list.len() > inner.back_forward_list_index + 1
491 }
492
493 pub fn go_forward(&self, amount: usize) -> TraversalId {
494 let traversal_id = TraversalId::new();
495 self.inner().servo.constellation_proxy().send(
496 EmbedderToConstellationMessage::TraverseHistory(
497 self.id(),
498 TraversalDirection::Forward(amount),
499 traversal_id.clone(),
500 ),
501 );
502 traversal_id
503 }
504
505 pub fn notify_scroll_event(&self, scroll: Scroll, point: WebViewPoint) {
508 self.inner()
509 .servo
510 .paint()
511 .notify_scroll_event(self.id(), scroll, point);
512 }
513
514 pub fn notify_input_event(&self, event: InputEvent) -> InputEventId {
515 let event: InputEventAndId = event.into();
516 let event_id = event.id;
517 let webview_id = self.id();
518 let servo = &self.inner().servo;
519 if event.event.point().is_some() {
521 if !servo.paint().notify_input_event(self.id(), event) {
522 servo.add_pending_handled_input_event(PendingHandledInputEvent {
523 event_id,
524 webview_id,
525 });
526 servo.event_loop_waker().wake();
527 }
528 } else {
529 servo
530 .constellation_proxy()
531 .send(EmbedderToConstellationMessage::ForwardInputEvent(
532 webview_id, event, None, ));
534 }
535
536 event_id
537 }
538
539 pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) {
540 self.inner()
541 .servo
542 .constellation_proxy()
543 .send(EmbedderToConstellationMessage::MediaSessionAction(event));
544 }
545
546 pub fn set_page_zoom(&self, new_zoom: f32) {
558 self.inner()
559 .servo
560 .paint()
561 .set_page_zoom(self.id(), new_zoom);
562 }
563
564 pub fn page_zoom(&self) -> f32 {
566 self.inner().servo.paint().page_zoom(self.id())
567 }
568
569 pub fn adjust_pinch_zoom(&self, pinch_zoom_delta: f32, center: DevicePoint) {
579 self.inner()
580 .servo
581 .paint()
582 .adjust_pinch_zoom(self.id(), pinch_zoom_delta, center);
583 }
584
585 pub fn pinch_zoom(&self) -> f32 {
587 self.inner().servo.paint().pinch_zoom(self.id())
588 }
589
590 pub fn device_pixels_per_css_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
591 self.inner()
592 .servo
593 .paint()
594 .device_pixels_per_page_pixel(self.id())
595 }
596
597 pub fn exit_fullscreen(&self) {
598 self.inner()
599 .servo
600 .constellation_proxy()
601 .send(EmbedderToConstellationMessage::ExitFullScreen(self.id()));
602 }
603
604 pub fn set_throttled(&self, throttled: bool) {
605 self.inner().servo.constellation_proxy().send(
606 EmbedderToConstellationMessage::SetWebViewThrottled(self.id(), throttled),
607 );
608 }
609
610 pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) {
611 self.inner().servo.paint().toggle_webrender_debug(debugging);
612 }
613
614 pub fn capture_webrender(&self) {
615 self.inner().servo.paint().capture_webrender(self.id());
616 }
617
618 pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
619 self.inner().servo.constellation_proxy().send(
620 EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration),
621 );
622 }
623
624 pub fn send_error(&self, message: String) {
625 self.inner()
626 .servo
627 .constellation_proxy()
628 .send(EmbedderToConstellationMessage::SendError(
629 Some(self.id()),
630 message,
631 ));
632 }
633
634 pub fn paint(&self) {
636 self.inner().servo.paint().render(self.id());
637 }
638
639 pub fn user_content_manager(&self) -> Option<Rc<UserContentManager>> {
641 self.inner().user_content_manager.clone()
642 }
643
644 pub fn evaluate_javascript<T: ToString>(
647 &self,
648 script: T,
649 callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static,
650 ) {
651 self.inner().servo.javascript_evaluator_mut().evaluate(
652 self.id(),
653 script.to_string(),
654 Box::new(callback),
655 );
656 }
657
658 pub fn take_screenshot(
676 &self,
677 rect: Option<WebViewRect>,
678 callback: impl FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static,
679 ) {
680 self.inner()
681 .servo
682 .paint()
683 .request_screenshot(self.id(), rect, Box::new(callback));
684 }
685
686 pub(crate) fn set_history(self, new_back_forward_list: Vec<ServoUrl>, new_index: usize) {
687 {
688 let mut inner_mut = self.inner_mut();
689 inner_mut.back_forward_list_index = new_index;
690 inner_mut.back_forward_list = new_back_forward_list
691 .into_iter()
692 .map(ServoUrl::into_url)
693 .collect();
694 }
695
696 let back_forward_list = self.inner().back_forward_list.clone();
697 let back_forward_list_index = self.inner().back_forward_list_index;
698 self.delegate().notify_url_changed(
699 self.clone(),
700 back_forward_list[back_forward_list_index].clone(),
701 );
702 self.delegate().notify_history_changed(
703 self.clone(),
704 back_forward_list,
705 back_forward_list_index,
706 );
707 }
708
709 pub(crate) fn show_embedder_control(
710 self,
711 control_id: EmbedderControlId,
712 position: DeviceIntRect,
713 embedder_control_request: EmbedderControlRequest,
714 ) {
715 let constellation_proxy = self.inner().servo.constellation_proxy().clone();
716 let embedder_control = match embedder_control_request {
717 EmbedderControlRequest::SelectElement(options, selected_option) => {
718 EmbedderControl::SelectElement(SelectElement {
719 id: control_id,
720 options,
721 selected_option,
722 position,
723 constellation_proxy,
724 response_sent: false,
725 })
726 },
727 EmbedderControlRequest::ColorPicker(current_color) => {
728 EmbedderControl::ColorPicker(ColorPicker {
729 id: control_id,
730 current_color: Some(current_color),
731 position,
732 constellation_proxy,
733 response_sent: false,
734 })
735 },
736 EmbedderControlRequest::InputMethod(input_method_request) => {
737 EmbedderControl::InputMethod(InputMethodControl {
738 id: control_id,
739 input_method_type: input_method_request.input_method_type,
740 text: input_method_request.text,
741 insertion_point: input_method_request.insertion_point,
742 position,
743 multiline: input_method_request.multiline,
744 allow_virtual_keyboard: input_method_request.allow_virtual_keyboard,
745 })
746 },
747 EmbedderControlRequest::ContextMenu(mut context_menu_request) => {
748 for item in context_menu_request.items.iter_mut() {
749 match item {
750 ContextMenuItem::Item {
751 action: ContextMenuAction::GoBack,
752 enabled,
753 ..
754 } => *enabled = self.can_go_back(),
755 ContextMenuItem::Item {
756 action: ContextMenuAction::GoForward,
757 enabled,
758 ..
759 } => *enabled = self.can_go_forward(),
760 _ => {},
761 }
762 }
763 EmbedderControl::ContextMenu(ContextMenu {
764 id: control_id,
765 position,
766 items: context_menu_request.items,
767 element_info: context_menu_request.element_info,
768 constellation_proxy,
769 response_sent: false,
770 })
771 },
772 EmbedderControlRequest::FilePicker { .. } => {
773 unreachable!("This message should be routed through the FileManagerThread")
774 },
775 };
776
777 self.delegate()
778 .show_embedder_control(self.clone(), embedder_control);
779 }
780
781 pub fn accesskit_tree_id(&self) -> Option<TreeId> {
783 self.inner().accesskit_tree_id
784 }
785
786 pub fn set_accessibility_active(&self, active: bool) -> Option<TreeId> {
801 if !pref!(accessibility_enabled) {
802 return None;
803 }
804
805 if active == self.inner().accesskit_tree_id.is_some() {
806 return self.accesskit_tree_id();
807 }
808
809 if active {
810 let accesskit_tree_id = TreeId(AccesskitUuid::new_v4());
811 self.inner_mut().accesskit_tree_id = Some(accesskit_tree_id);
812 } else {
813 self.inner_mut().accesskit_tree_id = None;
814 }
815
816 self.inner().servo.constellation_proxy().send(
817 EmbedderToConstellationMessage::SetAccessibilityActive(self.id(), active),
818 );
819
820 self.accesskit_tree_id()
821 }
822
823 pub(crate) fn notify_document_accessibility_tree_id(&self, grafted_tree_id: TreeId) {
824 let Some(webview_accesskit_tree_id) = self.inner().accesskit_tree_id else {
825 return;
826 };
827 let old_grafted_tree_id = self
828 .inner_mut()
829 .grafted_accesskit_tree_id
830 .replace(grafted_tree_id);
831 if old_grafted_tree_id == Some(grafted_tree_id) {
833 return;
834 }
835 let root_node_id = NodeId(0);
836 let mut root_node = AccesskitNode::new(Role::ScrollView);
837 let graft_node_id = NodeId(1);
838 let mut graft_node = AccesskitNode::new(Role::GenericContainer);
839 graft_node.set_tree_id(grafted_tree_id);
840 root_node.set_children(vec![graft_node_id]);
841 self.delegate().notify_accessibility_tree_update(
842 self.clone(),
843 TreeUpdate {
844 nodes: vec![(root_node_id, root_node), (graft_node_id, graft_node)],
845 tree: Some(Tree {
846 root: root_node_id,
847 toolkit_name: None,
848 toolkit_version: None,
849 }),
850 tree_id: webview_accesskit_tree_id,
851 focus: root_node_id,
852 },
853 );
854 }
855}
856
857struct ServoRendererWebView {
860 id: WebViewId,
861 weak_handle: Weak<RefCell<WebViewInner>>,
862}
863
864impl WebViewTrait for ServoRendererWebView {
865 fn id(&self) -> WebViewId {
866 self.id
867 }
868
869 fn screen_geometry(&self) -> Option<ScreenGeometry> {
870 let webview = WebView::from_weak_handle(&self.weak_handle)?;
871 webview.delegate().screen_geometry(webview)
872 }
873
874 fn set_animating(&self, new_value: bool) {
875 if let Some(webview) = WebView::from_weak_handle(&self.weak_handle) {
876 webview.set_animating(new_value);
877 }
878 }
879}
880
881pub struct WebViewBuilder {
883 servo: Servo,
884 rendering_context: Rc<dyn RenderingContext>,
885 delegate: Rc<dyn WebViewDelegate>,
886 url: Option<Url>,
887 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
888 create_new_webview_responder: Option<IpcResponder<Option<NewWebViewDetails>>>,
889 user_content_manager: Option<Rc<UserContentManager>>,
890 clipboard_delegate: Option<Rc<dyn ClipboardDelegate>>,
891 #[cfg(feature = "gamepad")]
892 gamepad_delegate: Option<Rc<dyn GamepadDelegate>>,
893}
894
895impl WebViewBuilder {
896 pub fn new(servo: &Servo, rendering_context: Rc<dyn RenderingContext>) -> Self {
897 Self {
898 servo: servo.clone(),
899 rendering_context,
900 url: None,
901 hidpi_scale_factor: Scale::new(1.0),
902 delegate: Rc::new(DefaultWebViewDelegate),
903 create_new_webview_responder: None,
904 user_content_manager: None,
905 clipboard_delegate: None,
906 #[cfg(feature = "gamepad")]
907 gamepad_delegate: None,
908 }
909 }
910
911 pub(crate) fn new_for_create_request(
912 servo: &Servo,
913 rendering_context: Rc<dyn RenderingContext>,
914 responder: IpcResponder<Option<NewWebViewDetails>>,
915 ) -> Self {
916 let mut builder = Self::new(servo, rendering_context);
917 builder.create_new_webview_responder = Some(responder);
918 builder
919 }
920
921 pub fn delegate(mut self, delegate: Rc<dyn WebViewDelegate>) -> Self {
922 self.delegate = delegate;
923 self
924 }
925
926 pub fn url(mut self, url: Url) -> Self {
927 self.url = Some(url);
928 self
929 }
930
931 pub fn hidpi_scale_factor(
932 mut self,
933 hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
934 ) -> Self {
935 self.hidpi_scale_factor = hidpi_scale_factor;
936 self
937 }
938
939 pub fn user_content_manager(mut self, user_content_manager: Rc<UserContentManager>) -> Self {
943 self.user_content_manager = Some(user_content_manager);
944 self
945 }
946
947 pub fn clipboard_delegate(mut self, clipboard_delegate: Rc<dyn ClipboardDelegate>) -> Self {
950 self.clipboard_delegate = Some(clipboard_delegate);
951 self
952 }
953
954 #[cfg(feature = "gamepad")]
957 pub fn gamepad_delegate(mut self, gamepad_delegate: Rc<dyn GamepadDelegate>) -> Self {
958 self.gamepad_delegate = Some(gamepad_delegate);
959 self
960 }
961
962 pub fn build(self) -> WebView {
963 WebView::new(self)
964 }
965}