script/dom/html/input_element/
text_input_widget.rs1use std::cell::Ref;
5
6use html5ever::{local_name, ns};
7use js::context::JSContext;
8use markup5ever::QualName;
9use script_bindings::codegen::GenericBindings::CharacterDataBinding::CharacterDataMethods;
10use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
11use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
12use script_bindings::inheritance::Castable;
13use script_bindings::root::{Dom, DomRoot};
14use style::selector_parser::PseudoElement;
15
16use crate::dom::bindings::cell::DomRefCell;
17use crate::dom::characterdata::CharacterData;
18use crate::dom::document::Document;
19use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator};
20use crate::dom::node::{Node, NodeTraits};
21use crate::dom::textcontrol::TextControlElement;
22
23const PASSWORD_REPLACEMENT_CHAR: char = '●';
24
25#[derive(Default, JSTraceable, MallocSizeOf, PartialEq)]
26#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
27pub(crate) struct TextInputWidget {
28 shadow_tree: DomRefCell<Option<TextInputWidgetShadowTree>>,
29}
30
31impl TextInputWidget {
32 fn get_or_create_shadow_tree(
35 &self,
36 cx: &mut JSContext,
37 text_control_element: &impl TextControlElement,
38 ) -> Ref<'_, TextInputWidgetShadowTree> {
39 {
40 if let Ok(shadow_tree) = Ref::filter_map(self.shadow_tree.borrow(), |shadow_tree| {
41 shadow_tree.as_ref()
42 }) {
43 return shadow_tree;
44 }
45 }
46
47 let element = text_control_element.upcast::<Element>();
48 let shadow_root = element
49 .shadow_root()
50 .unwrap_or_else(|| element.attach_ua_shadow_root(cx, true));
51 let shadow_root = shadow_root.upcast();
52 *self.shadow_tree.borrow_mut() = Some(TextInputWidgetShadowTree::new(cx, shadow_root));
53 self.get_or_create_shadow_tree(cx, text_control_element)
54 }
55
56 pub(crate) fn update_shadow_tree(&self, cx: &mut JSContext, element: &impl TextControlElement) {
57 self.get_or_create_shadow_tree(cx, element).update(element)
58 }
59
60 pub(crate) fn update_placeholder_contents(
61 &self,
62 cx: &mut JSContext,
63 element: &impl TextControlElement,
64 ) {
65 self.get_or_create_shadow_tree(cx, element)
66 .update_placeholder(cx, element);
67 }
68}
69
70#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
71#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
72pub(crate) struct TextInputWidgetShadowTree {
92 inner_container: Dom<Element>,
93 text_container: Dom<Element>,
94 placeholder_container: DomRefCell<Option<Dom<Element>>>,
95}
96
97impl TextInputWidgetShadowTree {
98 pub(crate) fn new(cx: &mut JSContext, shadow_root: &Node) -> Self {
99 let document = shadow_root.owner_document();
100 let inner_container = Element::create(
101 cx,
102 QualName::new(None, ns!(html), local_name!("div")),
103 None,
104 &document,
105 ElementCreator::ScriptCreated,
106 CustomElementCreationMode::Asynchronous,
107 None,
108 );
109
110 Node::replace_all(cx, Some(inner_container.upcast()), shadow_root.upcast());
111 inner_container
112 .upcast::<Node>()
113 .set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
114
115 let text_container = create_ua_widget_div_with_text_node(
116 cx,
117 &document,
118 inner_container.upcast(),
119 PseudoElement::ServoTextControlInnerEditor,
120 false,
121 );
122
123 Self {
124 inner_container: inner_container.as_traced(),
125 text_container: text_container.as_traced(),
126 placeholder_container: DomRefCell::new(None),
127 }
128 }
129
130 fn init_placeholder_container_if_necessary(
133 &self,
134 cx: &mut JSContext,
135 element: &impl TextControlElement,
136 ) -> Option<DomRoot<Element>> {
137 if let Some(placeholder_container) = &*self.placeholder_container.borrow() {
138 return Some(placeholder_container.root_element());
139 }
140 let placeholder = element.placeholder_text();
143 if placeholder.is_empty() {
144 return None;
145 }
146
147 let placeholder_container = create_ua_widget_div_with_text_node(
148 cx,
149 &element.owner_document(),
150 self.inner_container.upcast::<Node>(),
151 PseudoElement::Placeholder,
152 true,
153 );
154 *self.placeholder_container.borrow_mut() = Some(placeholder_container.as_traced());
155 Some(placeholder_container)
156 }
157
158 fn placeholder_character_data(
159 &self,
160 cx: &mut JSContext,
161 element: &impl TextControlElement,
162 ) -> Option<DomRoot<CharacterData>> {
163 self.init_placeholder_container_if_necessary(cx, element)
164 .and_then(|placeholder_container| {
165 let first_child = placeholder_container.upcast::<Node>().GetFirstChild()?;
166 Some(DomRoot::from_ref(first_child.downcast::<CharacterData>()?))
167 })
168 }
169
170 pub(crate) fn update_placeholder(&self, cx: &mut JSContext, element: &impl TextControlElement) {
171 if let Some(character_data) = self.placeholder_character_data(cx, element) {
172 let placeholder_value = element.placeholder_text();
173 if character_data.Data() != *placeholder_value {
174 character_data.SetData(placeholder_value.clone());
175 }
176 }
177 }
178
179 fn value_character_data(&self) -> Option<DomRoot<CharacterData>> {
180 Some(DomRoot::from_ref(
181 self.text_container
182 .upcast::<Node>()
183 .GetFirstChild()?
184 .downcast::<CharacterData>()?,
185 ))
186 }
187
188 pub(crate) fn update(&self, element: &impl TextControlElement) {
191 let value = element.value_text();
200 let value_text = match (value.is_empty(), element.is_password_field()) {
201 (false, true) => value
203 .str()
204 .chars()
205 .map(|_| PASSWORD_REPLACEMENT_CHAR)
206 .collect::<String>()
207 .into(),
208 (false, _) => value,
209 (true, _) => "\u{200B}".into(),
210 };
211
212 if let Some(character_data) = self.value_character_data() {
213 if character_data.Data() != value_text {
214 character_data.SetData(value_text);
215 }
216 }
217 }
218}
219
220fn create_ua_widget_div_with_text_node(
223 cx: &mut JSContext,
224 document: &Document,
225 parent: &Node,
226 implemented_pseudo: PseudoElement,
227 as_first_child: bool,
228) -> DomRoot<Element> {
229 let el = Element::create(
230 cx,
231 QualName::new(None, ns!(html), local_name!("div")),
232 None,
233 document,
234 ElementCreator::ScriptCreated,
235 CustomElementCreationMode::Asynchronous,
236 None,
237 );
238
239 parent
240 .upcast::<Node>()
241 .AppendChild(cx, el.upcast::<Node>())
242 .unwrap();
243 el.upcast::<Node>()
244 .set_implemented_pseudo_element(implemented_pseudo);
245 let text_node = document.CreateTextNode(cx, "".into());
246
247 if !as_first_child {
248 el.upcast::<Node>()
249 .AppendChild(cx, text_node.upcast::<Node>())
250 .unwrap();
251 } else {
252 el.upcast::<Node>()
253 .InsertBefore(
254 cx,
255 text_node.upcast::<Node>(),
256 el.upcast::<Node>().GetFirstChild().as_deref(),
257 )
258 .unwrap();
259 }
260 el
261}