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::cell::DomRefCell;
10use script_bindings::codegen::GenericBindings::CharacterDataBinding::CharacterDataMethods;
11use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
12use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
13use script_bindings::inheritance::Castable;
14use script_bindings::root::{Dom, DomRoot};
15use style::selector_parser::PseudoElement;
16
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)
58 .update(cx, element)
59 }
60
61 pub(crate) fn update_placeholder_contents(
62 &self,
63 cx: &mut JSContext,
64 element: &impl TextControlElement,
65 ) {
66 self.get_or_create_shadow_tree(cx, element)
67 .update_placeholder(cx, element);
68 }
69}
70
71#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
72#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
73pub(crate) struct TextInputWidgetShadowTree {
93 inner_container: Dom<Element>,
94 text_container: Dom<Element>,
95 placeholder_container: DomRefCell<Option<Dom<Element>>>,
96}
97
98impl TextInputWidgetShadowTree {
99 pub(crate) fn new(cx: &mut JSContext, shadow_root: &Node) -> Self {
100 let document = shadow_root.owner_document();
101 let inner_container = Element::create(
102 cx,
103 QualName::new(None, ns!(html), local_name!("div")),
104 None,
105 &document,
106 ElementCreator::ScriptCreated,
107 CustomElementCreationMode::Asynchronous,
108 None,
109 );
110
111 Node::replace_all(cx, Some(inner_container.upcast()), shadow_root.upcast());
112 inner_container
113 .upcast::<Node>()
114 .set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
115
116 let text_container = create_ua_widget_div_with_text_node(
117 cx,
118 &document,
119 inner_container.upcast(),
120 PseudoElement::ServoTextControlInnerEditor,
121 false,
122 );
123
124 Self {
125 inner_container: inner_container.as_traced(),
126 text_container: text_container.as_traced(),
127 placeholder_container: DomRefCell::new(None),
128 }
129 }
130
131 fn init_placeholder_container_if_necessary(
134 &self,
135 cx: &mut JSContext,
136 element: &impl TextControlElement,
137 ) -> Option<DomRoot<Element>> {
138 if let Some(placeholder_container) = &*self.placeholder_container.borrow() {
139 return Some(placeholder_container.root_element());
140 }
141 let placeholder = element.placeholder_text();
144 if placeholder.is_empty() {
145 return None;
146 }
147
148 let placeholder_container = create_ua_widget_div_with_text_node(
149 cx,
150 &element.owner_document(),
151 self.inner_container.upcast::<Node>(),
152 PseudoElement::Placeholder,
153 true,
154 );
155 *self.placeholder_container.borrow_mut() = Some(placeholder_container.as_traced());
156 Some(placeholder_container)
157 }
158
159 fn placeholder_character_data(
160 &self,
161 cx: &mut JSContext,
162 element: &impl TextControlElement,
163 ) -> Option<DomRoot<CharacterData>> {
164 self.init_placeholder_container_if_necessary(cx, element)
165 .and_then(|placeholder_container| {
166 let first_child = placeholder_container.upcast::<Node>().GetFirstChild()?;
167 Some(DomRoot::from_ref(first_child.downcast::<CharacterData>()?))
168 })
169 }
170
171 pub(crate) fn update_placeholder(&self, cx: &mut JSContext, element: &impl TextControlElement) {
172 if let Some(character_data) = self.placeholder_character_data(cx, element) {
173 let placeholder_value = element.placeholder_text();
174 if character_data.Data() != *placeholder_value {
175 character_data.SetData(cx, placeholder_value.clone());
176 }
177 }
178 }
179
180 fn value_character_data(&self) -> Option<DomRoot<CharacterData>> {
181 Some(DomRoot::from_ref(
182 self.text_container
183 .upcast::<Node>()
184 .GetFirstChild()?
185 .downcast::<CharacterData>()?,
186 ))
187 }
188
189 pub(crate) fn update(&self, cx: &mut JSContext, element: &impl TextControlElement) {
192 let value = element.value_text();
201 let value_text = match (value.is_empty(), element.is_password_field()) {
202 (false, true) => value
204 .str()
205 .chars()
206 .map(|_| PASSWORD_REPLACEMENT_CHAR)
207 .collect::<String>()
208 .into(),
209 (false, _) => value,
210 (true, _) => "\u{200B}".into(),
211 };
212
213 if let Some(character_data) = self.value_character_data() &&
214 character_data.Data() != value_text
215 {
216 character_data.SetData(cx, value_text);
217 }
218 }
219}
220
221fn create_ua_widget_div_with_text_node(
224 cx: &mut JSContext,
225 document: &Document,
226 parent: &Node,
227 implemented_pseudo: PseudoElement,
228 as_first_child: bool,
229) -> DomRoot<Element> {
230 let el = Element::create(
231 cx,
232 QualName::new(None, ns!(html), local_name!("div")),
233 None,
234 document,
235 ElementCreator::ScriptCreated,
236 CustomElementCreationMode::Asynchronous,
237 None,
238 );
239
240 parent
241 .upcast::<Node>()
242 .AppendChild(cx, el.upcast::<Node>())
243 .unwrap();
244 el.upcast::<Node>()
245 .set_implemented_pseudo_element(implemented_pseudo);
246 let text_node = document.CreateTextNode(cx, "".into());
247
248 if !as_first_child {
249 el.upcast::<Node>()
250 .AppendChild(cx, text_node.upcast::<Node>())
251 .unwrap();
252 } else {
253 el.upcast::<Node>()
254 .InsertBefore(
255 cx,
256 text_node.upcast::<Node>(),
257 el.upcast::<Node>().GetFirstChild().as_deref(),
258 )
259 .unwrap();
260 }
261 el
262}