1use std::borrow::Cow;
6
7use html5ever::LocalName;
8use layout_api::wrapper_traits::{
9 PseudoElementChain, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
10};
11use layout_api::{LayoutElementType, LayoutNodeType};
12use script::layout_dom::ServoThreadSafeLayoutNode;
13use selectors::Element as SelectorsElement;
14use servo_arc::Arc as ServoArc;
15use style::dom::NodeInfo;
16use style::properties::ComputedValues;
17use style::selector_parser::PseudoElement;
18use style::values::generics::counters::{Content, ContentItem};
19use style::values::specified::Quotes;
20
21use crate::context::LayoutContext;
22use crate::dom::{BoxSlot, LayoutBox, NodeExt};
23use crate::flow::inline::SharedInlineStyles;
24use crate::quotes::quotes_for_lang;
25use crate::replaced::ReplacedContents;
26use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside, DisplayOutside};
27
28#[derive(Clone)]
31pub(crate) struct NodeAndStyleInfo<'dom> {
32 pub node: ServoThreadSafeLayoutNode<'dom>,
33 pub style: ServoArc<ComputedValues>,
34}
35
36impl<'dom> NodeAndStyleInfo<'dom> {
37 pub(crate) fn new(
38 node: ServoThreadSafeLayoutNode<'dom>,
39 style: ServoArc<ComputedValues>,
40 ) -> Self {
41 Self { node, style }
42 }
43
44 pub(crate) fn pseudo_element_chain(&self) -> PseudoElementChain {
45 self.node.pseudo_element_chain()
46 }
47
48 pub(crate) fn with_pseudo_element(
49 &self,
50 context: &LayoutContext,
51 pseudo_element_type: PseudoElement,
52 ) -> Option<Self> {
53 let element = self.node.as_element()?.with_pseudo(pseudo_element_type)?;
54 let style = element.style(&context.style_context);
55 Some(NodeAndStyleInfo {
56 node: element.as_node(),
57 style,
58 })
59 }
60}
61
62#[derive(Debug)]
63pub(super) enum Contents {
64 NonReplaced(NonReplacedContents),
66 Widget(NonReplacedContents),
71 Replaced(ReplacedContents),
74}
75
76#[derive(Debug)]
77pub(super) enum NonReplacedContents {
78 OfElement,
80 OfPseudoElement(Vec<PseudoElementContentItem>),
83}
84
85#[derive(Debug)]
86pub(super) enum PseudoElementContentItem {
87 Text(String),
88 Replaced(ReplacedContents),
89}
90
91pub(super) trait TraversalHandler<'dom> {
92 fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>);
93
94 fn handle_element(
96 &mut self,
97 info: &NodeAndStyleInfo<'dom>,
98 display: DisplayGeneratingBox,
99 contents: Contents,
100 box_slot: BoxSlot<'dom>,
101 );
102
103 fn enter_display_contents(&mut self, _: SharedInlineStyles) {}
105
106 fn leave_display_contents(&mut self) {}
108}
109
110fn traverse_children_of<'dom>(
111 parent_element_info: &NodeAndStyleInfo<'dom>,
112 context: &LayoutContext,
113 handler: &mut impl TraversalHandler<'dom>,
114) {
115 parent_element_info
116 .node
117 .set_uses_content_attribute_with_attr(false);
118
119 let is_element = parent_element_info.pseudo_element_chain().is_empty();
120 if is_element {
121 traverse_eager_pseudo_element(PseudoElement::Before, parent_element_info, context, handler);
122 }
123
124 for child in parent_element_info.node.children() {
125 if child.is_text_node() {
126 let info = NodeAndStyleInfo::new(child, child.style(&context.style_context));
127 handler.handle_text(&info, child.text_content());
128 } else if child.is_element() {
129 traverse_element(child, context, handler);
130 }
131 }
132
133 if is_element {
134 traverse_eager_pseudo_element(PseudoElement::After, parent_element_info, context, handler);
135 }
136}
137
138fn traverse_element<'dom>(
139 element: ServoThreadSafeLayoutNode<'dom>,
140 context: &LayoutContext,
141 handler: &mut impl TraversalHandler<'dom>,
142) {
143 let style = element.style(&context.style_context);
144 let info = NodeAndStyleInfo::new(element, style);
145
146 match Display::from(info.style.get_box().display) {
147 Display::None => {},
148 Display::Contents => {
149 if ReplacedContents::for_element(element, context).is_some() {
150 element.unset_all_boxes()
153 } else {
154 let shared_inline_styles =
155 SharedInlineStyles::from_info_and_context(&info, context);
156 element
157 .box_slot()
158 .set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
159
160 handler.enter_display_contents(shared_inline_styles);
161 traverse_children_of(&info, context, handler);
162 handler.leave_display_contents();
163 }
164 },
165 Display::GeneratingBox(display) => {
166 let contents = Contents::for_element(element, context);
167 let display = display.used_value_for_contents(&contents);
168 let box_slot = element.box_slot();
169 handler.handle_element(&info, display, contents, box_slot);
170 },
171 }
172}
173
174fn traverse_eager_pseudo_element<'dom>(
175 pseudo_element_type: PseudoElement,
176 node_info: &NodeAndStyleInfo<'dom>,
177 context: &LayoutContext,
178 handler: &mut impl TraversalHandler<'dom>,
179) {
180 assert!(pseudo_element_type.is_eager());
181
182 let Some(pseudo_element_info) = node_info.with_pseudo_element(context, pseudo_element_type)
185 else {
186 return;
187 };
188 if pseudo_element_info.style.ineffective_content_property() {
189 return;
190 }
191
192 match Display::from(pseudo_element_info.style.get_box().display) {
193 Display::None => {},
194 Display::Contents => {
195 let items = generate_pseudo_element_content(&pseudo_element_info, context);
196 let box_slot = pseudo_element_info.node.box_slot();
197 let shared_inline_styles =
198 SharedInlineStyles::from_info_and_context(&pseudo_element_info, context);
199 box_slot.set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
200
201 handler.enter_display_contents(shared_inline_styles);
202 traverse_pseudo_element_contents(&pseudo_element_info, context, handler, items);
203 handler.leave_display_contents();
204 },
205 Display::GeneratingBox(display) => {
206 let items = generate_pseudo_element_content(&pseudo_element_info, context);
207 let box_slot = pseudo_element_info.node.box_slot();
208 let contents = Contents::for_pseudo_element(items);
209 handler.handle_element(&pseudo_element_info, display, contents, box_slot);
210 },
211 }
212}
213
214fn traverse_pseudo_element_contents<'dom>(
215 info: &NodeAndStyleInfo<'dom>,
216 context: &LayoutContext,
217 handler: &mut impl TraversalHandler<'dom>,
218 items: Vec<PseudoElementContentItem>,
219) {
220 let mut anonymous_info = None;
221 for item in items {
222 match item {
223 PseudoElementContentItem::Text(text) => handler.handle_text(info, text.into()),
224 PseudoElementContentItem::Replaced(contents) => {
225 let anonymous_info = anonymous_info.get_or_insert_with(|| {
226 info.with_pseudo_element(context, PseudoElement::ServoAnonymousBox)
227 .unwrap_or_else(|| info.clone())
228 });
229 let display_inline = DisplayGeneratingBox::OutsideInside {
230 outside: DisplayOutside::Inline,
231 inside: DisplayInside::Flow {
232 is_list_item: false,
233 },
234 };
235 debug_assert!(
237 Display::from(anonymous_info.style.get_box().display) ==
238 Display::GeneratingBox(display_inline)
239 );
240 handler.handle_element(
241 anonymous_info,
242 display_inline,
243 Contents::Replaced(contents),
244 anonymous_info.node.box_slot(),
245 )
246 },
247 }
248 }
249}
250
251impl Contents {
252 pub fn is_replaced(&self) -> bool {
254 matches!(self, Contents::Replaced(_))
255 }
256
257 pub(crate) fn for_element(
258 node: ServoThreadSafeLayoutNode<'_>,
259 context: &LayoutContext,
260 ) -> Self {
261 if let Some(replaced) = ReplacedContents::for_element(node, context) {
262 return Self::Replaced(replaced);
263 }
264 let is_widget = matches!(
265 node.type_id(),
266 Some(LayoutNodeType::Element(
267 LayoutElementType::HTMLInputElement |
268 LayoutElementType::HTMLSelectElement |
269 LayoutElementType::HTMLTextAreaElement
270 ))
271 );
272 if is_widget {
273 Self::Widget(NonReplacedContents::OfElement)
274 } else {
275 Self::NonReplaced(NonReplacedContents::OfElement)
276 }
277 }
278
279 pub(crate) fn for_pseudo_element(contents: Vec<PseudoElementContentItem>) -> Self {
280 Self::NonReplaced(NonReplacedContents::OfPseudoElement(contents))
281 }
282
283 pub(crate) fn non_replaced_contents(self) -> Option<NonReplacedContents> {
284 match self {
285 Self::NonReplaced(contents) | Self::Widget(contents) => Some(contents),
286 Self::Replaced(_) => None,
287 }
288 }
289}
290
291impl NonReplacedContents {
292 pub(crate) fn traverse<'dom>(
293 self,
294 context: &LayoutContext,
295 info: &NodeAndStyleInfo<'dom>,
296 handler: &mut impl TraversalHandler<'dom>,
297 ) {
298 match self {
299 NonReplacedContents::OfElement => traverse_children_of(info, context, handler),
300 NonReplacedContents::OfPseudoElement(items) => {
301 traverse_pseudo_element_contents(info, context, handler, items)
302 },
303 }
304 }
305}
306
307fn get_quote_from_pair<I, S>(item: &ContentItem<I>, opening: &S, closing: &S) -> String
308where
309 S: ToString + ?Sized,
310{
311 match item {
312 ContentItem::OpenQuote => opening.to_string(),
313 ContentItem::CloseQuote => closing.to_string(),
314 _ => unreachable!("Got an unexpected ContentItem type when processing quotes."),
315 }
316}
317
318fn generate_pseudo_element_content(
320 pseudo_element_info: &NodeAndStyleInfo,
321 context: &LayoutContext,
322) -> Vec<PseudoElementContentItem> {
323 match &pseudo_element_info.style.get_counters().content {
324 Content::Items(items) => {
325 let mut vec = vec![];
326 for item in items.items.iter() {
327 match item {
328 ContentItem::String(s) => {
329 vec.push(PseudoElementContentItem::Text(s.to_string()));
330 },
331 ContentItem::Attr(attr) => {
332 let element = pseudo_element_info
333 .node
334 .as_element()
335 .expect("Expected an element");
336
337 let attr_name = match element.is_html_element_in_html_document() {
352 true => &*attr.attribute.to_ascii_lowercase(),
353 false => &*attr.attribute,
354 };
355
356 pseudo_element_info
357 .node
358 .set_uses_content_attribute_with_attr(true);
359 let attr_val =
360 element.get_attr(&attr.namespace_url, &LocalName::from(attr_name));
361 vec.push(PseudoElementContentItem::Text(
362 attr_val.map_or("".to_string(), |s| s.to_string()),
363 ));
364 },
365 ContentItem::Image(image) => {
366 if let Some(replaced_content) =
367 ReplacedContents::from_image(pseudo_element_info.node, context, image)
368 {
369 vec.push(PseudoElementContentItem::Replaced(replaced_content));
370 }
371 },
372 ContentItem::OpenQuote | ContentItem::CloseQuote => {
373 let maybe_quote = match &pseudo_element_info.style.get_list().quotes {
375 Quotes::QuoteList(quote_list) => {
376 quote_list.0.first().map(|quote_pair| {
377 get_quote_from_pair(
378 item,
379 &*quote_pair.opening,
380 &*quote_pair.closing,
381 )
382 })
383 },
384 Quotes::Auto => {
385 let lang = &pseudo_element_info.style.get_font()._x_lang;
386 let quotes = quotes_for_lang(lang.0.as_ref(), 0);
387 Some(get_quote_from_pair(item, "es.opening, "es.closing))
388 },
389 };
390 if let Some(quote) = maybe_quote {
391 vec.push(PseudoElementContentItem::Text(quote));
392 }
393 },
394 ContentItem::Counter(_, _) |
395 ContentItem::Counters(_, _, _) |
396 ContentItem::NoOpenQuote |
397 ContentItem::NoCloseQuote => {
398 },
400 }
401 }
402 vec
403 },
404 Content::Normal | Content::None => unreachable!(),
405 }
406}