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