script/dom/
document_embedder_controls.rs1use std::cell::Cell;
6
7use base::{Epoch, IpcSend};
8use embedder_traits::{
9 EmbedderControlId, EmbedderControlRequest, EmbedderControlResponse, EmbedderMsg,
10};
11use euclid::{Point2D, Rect, Size2D};
12use ipc_channel::router::ROUTER;
13use net_traits::CoreResourceMsg;
14use net_traits::filemanager_thread::FileManagerThreadMsg;
15use rustc_hash::FxHashMap;
16use script_bindings::root::{Dom, DomRoot};
17use script_bindings::script_runtime::CanGc;
18use webrender_api::units::DeviceIntRect;
19
20use crate::dom::bindings::cell::DomRefCell;
21use crate::dom::bindings::inheritance::Castable as _;
22use crate::dom::bindings::trace::NoTrace;
23use crate::dom::node::Node;
24use crate::dom::types::{Element, HTMLElement, HTMLInputElement, HTMLSelectElement, Window};
25use crate::messaging::MainThreadScriptMsg;
26
27#[derive(JSTraceable, MallocSizeOf)]
28pub(crate) enum ControlElement {
29 Select(DomRoot<HTMLSelectElement>),
30 ColorInput(DomRoot<HTMLInputElement>),
31 FileInput(DomRoot<HTMLInputElement>),
32 Ime(DomRoot<HTMLElement>),
33}
34
35impl ControlElement {
36 fn element(&self) -> &Element {
37 match self {
38 ControlElement::Select(element) => element.upcast::<Element>(),
39 ControlElement::ColorInput(element) => element.upcast::<Element>(),
40 ControlElement::FileInput(element) => element.upcast::<Element>(),
41 ControlElement::Ime(element) => element.upcast::<Element>(),
42 }
43 }
44}
45
46#[derive(JSTraceable, MallocSizeOf)]
47#[cfg_attr(crown, allow(crown::unrooted_must_root))]
48pub(crate) struct DocumentEmbedderControls {
49 window: Dom<Window>,
51 #[no_trace]
54 user_interface_element_index: Cell<Epoch>,
55 visible_elements: DomRefCell<FxHashMap<NoTrace<Epoch>, ControlElement>>,
57}
58
59impl DocumentEmbedderControls {
60 pub fn new(window: &Window) -> Self {
61 Self {
62 window: Dom::from_ref(window),
63 user_interface_element_index: Default::default(),
64 visible_elements: Default::default(),
65 }
66 }
67
68 pub(crate) fn next_control_id(&self) -> EmbedderControlId {
71 let index = self.user_interface_element_index.get();
72 self.user_interface_element_index.set(index.next());
73 EmbedderControlId {
74 webview_id: self.window.webview_id(),
75 pipeline_id: self.window.pipeline_id(),
76 index,
77 }
78 }
79
80 pub(crate) fn show_embedder_control(
81 &self,
82 element: ControlElement,
83 request: EmbedderControlRequest,
84 ) -> EmbedderControlId {
85 let id = self.next_control_id();
86 let rect = element
87 .element()
88 .upcast::<Node>()
89 .border_box()
90 .unwrap_or_default();
91 let rect = Rect::new(
92 Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
93 Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
94 );
95 let rect = DeviceIntRect::from_untyped(&rect.to_box2d());
97 self.visible_elements
98 .borrow_mut()
99 .insert(id.index.into(), element);
100
101 match request {
102 EmbedderControlRequest::SelectElement(..) |
103 EmbedderControlRequest::ColorPicker(..) |
104 EmbedderControlRequest::InputMethod(..) => self
105 .window
106 .send_to_embedder(EmbedderMsg::ShowEmbedderControl(id, rect, request)),
107 EmbedderControlRequest::FilePicker(file_picker_request) => {
108 let (sender, receiver) = profile_traits::ipc::channel(
109 self.window.as_global_scope().time_profiler_chan().clone(),
110 )
111 .expect("Error initializing channel");
112 let main_thread_sender = self.window.main_thread_script_chan().clone();
113 ROUTER.add_typed_route(
114 receiver.to_ipc_receiver(),
115 Box::new(move |result| {
116 let Ok(embedder_control_response) = result else {
117 return;
118 };
119 if let Err(error) = main_thread_sender.send(
120 MainThreadScriptMsg::ForwardEmbedderControlResponseFromFileManager(
121 id,
122 embedder_control_response,
123 ),
124 ) {
125 warn!("Could not send FileManager response to main thread: {error}")
126 }
127 }),
128 );
129 self.window
130 .as_global_scope()
131 .resource_threads()
132 .sender()
133 .send(CoreResourceMsg::ToFileManager(
134 FileManagerThreadMsg::SelectFiles(id, file_picker_request, sender),
135 ))
136 .unwrap();
137 },
138 }
139
140 id
141 }
142
143 pub(crate) fn hide_embedder_control(&self, element: &Element) {
144 self.visible_elements
145 .borrow_mut()
146 .retain(|index, control_element| {
147 if control_element.element() != element {
148 return true;
149 }
150 let id = EmbedderControlId {
151 webview_id: self.window.webview_id(),
152 pipeline_id: self.window.pipeline_id(),
153 index: index.0,
154 };
155 self.window
156 .send_to_embedder(EmbedderMsg::HideEmbedderControl(id));
157 false
158 });
159 }
160
161 pub(crate) fn handle_embedder_control_response(
162 &self,
163 id: EmbedderControlId,
164 response: EmbedderControlResponse,
165 can_gc: CanGc,
166 ) {
167 assert_eq!(self.window.pipeline_id(), id.pipeline_id);
168 assert_eq!(self.window.webview_id(), id.webview_id);
169
170 let Some(element) = self.visible_elements.borrow_mut().remove(&id.index.into()) else {
171 return;
172 };
173
174 match (element, response) {
175 (
176 ControlElement::Select(select_element),
177 EmbedderControlResponse::SelectElement(response),
178 ) => {
179 select_element.handle_menu_response(response, can_gc);
180 },
181 (
182 ControlElement::ColorInput(input_element),
183 EmbedderControlResponse::ColorPicker(response),
184 ) => {
185 input_element.handle_color_picker_response(response, can_gc);
186 },
187 (
188 ControlElement::FileInput(input_element),
189 EmbedderControlResponse::FilePicker(response),
190 ) => {
191 input_element.handle_file_picker_response(response, can_gc);
192 },
193 (_, _) => unreachable!(
194 "The response to a form control should always match it's originating type."
195 ),
196 }
197 }
198}