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: SharedInlineStyles = (&info).into();
155 element
156 .box_slot()
157 .set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
158
159 handler.enter_display_contents(shared_inline_styles);
160 traverse_children_of(&info, context, handler);
161 handler.leave_display_contents();
162 }
163 },
164 Display::GeneratingBox(display) => {
165 let contents = Contents::for_element(element, context);
166 let display = display.used_value_for_contents(&contents);
167 let box_slot = element.box_slot();
168 handler.handle_element(&info, display, contents, box_slot);
169 },
170 }
171}
172
173fn traverse_eager_pseudo_element<'dom>(
174 pseudo_element_type: PseudoElement,
175 node_info: &NodeAndStyleInfo<'dom>,
176 context: &LayoutContext,
177 handler: &mut impl TraversalHandler<'dom>,
178) {
179 assert!(pseudo_element_type.is_eager());
180
181 let Some(pseudo_element_info) = node_info.with_pseudo_element(context, pseudo_element_type)
184 else {
185 return;
186 };
187 if pseudo_element_info.style.ineffective_content_property() {
188 return;
189 }
190
191 match Display::from(pseudo_element_info.style.get_box().display) {
192 Display::None => {},
193 Display::Contents => {
194 let items = generate_pseudo_element_content(&pseudo_element_info, context);
195 let box_slot = pseudo_element_info.node.box_slot();
196 let shared_inline_styles: SharedInlineStyles = (&pseudo_element_info).into();
197 box_slot.set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
198
199 handler.enter_display_contents(shared_inline_styles);
200 traverse_pseudo_element_contents(&pseudo_element_info, context, handler, items);
201 handler.leave_display_contents();
202 },
203 Display::GeneratingBox(display) => {
204 let items = generate_pseudo_element_content(&pseudo_element_info, context);
205 let box_slot = pseudo_element_info.node.box_slot();
206 let contents = Contents::for_pseudo_element(items);
207 handler.handle_element(&pseudo_element_info, display, contents, box_slot);
208 },
209 }
210}
211
212fn traverse_pseudo_element_contents<'dom>(
213 info: &NodeAndStyleInfo<'dom>,
214 context: &LayoutContext,
215 handler: &mut impl TraversalHandler<'dom>,
216 items: Vec<PseudoElementContentItem>,
217) {
218 let mut anonymous_info = None;
219 for item in items {
220 match item {
221 PseudoElementContentItem::Text(text) => handler.handle_text(info, text.into()),
222 PseudoElementContentItem::Replaced(contents) => {
223 let anonymous_info = anonymous_info.get_or_insert_with(|| {
224 info.with_pseudo_element(context, PseudoElement::ServoAnonymousBox)
225 .unwrap_or_else(|| info.clone())
226 });
227 let display_inline = DisplayGeneratingBox::OutsideInside {
228 outside: DisplayOutside::Inline,
229 inside: DisplayInside::Flow {
230 is_list_item: false,
231 },
232 };
233 debug_assert!(
235 Display::from(anonymous_info.style.get_box().display) ==
236 Display::GeneratingBox(display_inline)
237 );
238 handler.handle_element(
239 anonymous_info,
240 display_inline,
241 Contents::Replaced(contents),
242 anonymous_info.node.box_slot(),
243 )
244 },
245 }
246 }
247}
248
249impl Contents {
250 pub fn is_replaced(&self) -> bool {
252 matches!(self, Contents::Replaced(_))
253 }
254
255 pub(crate) fn for_element(
256 node: ServoThreadSafeLayoutNode<'_>,
257 context: &LayoutContext,
258 ) -> Self {
259 if let Some(replaced) = ReplacedContents::for_element(node, context) {
260 return Self::Replaced(replaced);
261 }
262 let is_widget = matches!(
263 node.type_id(),
264 Some(LayoutNodeType::Element(
265 LayoutElementType::HTMLInputElement |
266 LayoutElementType::HTMLSelectElement |
267 LayoutElementType::HTMLTextAreaElement
268 ))
269 );
270 if is_widget {
271 Self::Widget(NonReplacedContents::OfElement)
272 } else {
273 Self::NonReplaced(NonReplacedContents::OfElement)
274 }
275 }
276
277 pub(crate) fn for_pseudo_element(contents: Vec<PseudoElementContentItem>) -> Self {
278 Self::NonReplaced(NonReplacedContents::OfPseudoElement(contents))
279 }
280
281 pub(crate) fn non_replaced_contents(self) -> Option<NonReplacedContents> {
282 match self {
283 Self::NonReplaced(contents) | Self::Widget(contents) => Some(contents),
284 Self::Replaced(_) => None,
285 }
286 }
287}
288
289impl NonReplacedContents {
290 pub(crate) fn traverse<'dom>(
291 self,
292 context: &LayoutContext,
293 info: &NodeAndStyleInfo<'dom>,
294 handler: &mut impl TraversalHandler<'dom>,
295 ) {
296 match self {
297 NonReplacedContents::OfElement => traverse_children_of(info, context, handler),
298 NonReplacedContents::OfPseudoElement(items) => {
299 traverse_pseudo_element_contents(info, context, handler, items)
300 },
301 }
302 }
303}
304
305fn get_quote_from_pair<I, S>(item: &ContentItem<I>, opening: &S, closing: &S) -> String
306where
307 S: ToString + ?Sized,
308{
309 match item {
310 ContentItem::OpenQuote => opening.to_string(),
311 ContentItem::CloseQuote => closing.to_string(),
312 _ => unreachable!("Got an unexpected ContentItem type when processing quotes."),
313 }
314}
315
316fn generate_pseudo_element_content(
318 pseudo_element_info: &NodeAndStyleInfo,
319 context: &LayoutContext,
320) -> Vec<PseudoElementContentItem> {
321 match &pseudo_element_info.style.get_counters().content {
322 Content::Items(items) => {
323 let mut vec = vec![];
324 for item in items.items.iter() {
325 match item {
326 ContentItem::String(s) => {
327 vec.push(PseudoElementContentItem::Text(s.to_string()));
328 },
329 ContentItem::Attr(attr) => {
330 let element = pseudo_element_info
331 .node
332 .as_element()
333 .expect("Expected an element");
334
335 let attr_name = match element.is_html_element_in_html_document() {
350 true => &*attr.attribute.to_ascii_lowercase(),
351 false => &*attr.attribute,
352 };
353
354 pseudo_element_info
355 .node
356 .set_uses_content_attribute_with_attr(true);
357 let attr_val =
358 element.get_attr(&attr.namespace_url, &LocalName::from(attr_name));
359 vec.push(PseudoElementContentItem::Text(
360 attr_val.map_or("".to_string(), |s| s.to_string()),
361 ));
362 },
363 ContentItem::Image(image) => {
364 if let Some(replaced_content) =
365 ReplacedContents::from_image(pseudo_element_info.node, context, image)
366 {
367 vec.push(PseudoElementContentItem::Replaced(replaced_content));
368 }
369 },
370 ContentItem::OpenQuote | ContentItem::CloseQuote => {
371 let maybe_quote = match &pseudo_element_info.style.get_list().quotes {
373 Quotes::QuoteList(quote_list) => {
374 quote_list.0.first().map(|quote_pair| {
375 get_quote_from_pair(
376 item,
377 &*quote_pair.opening,
378 &*quote_pair.closing,
379 )
380 })
381 },
382 Quotes::Auto => {
383 let lang = &pseudo_element_info.style.get_font()._x_lang;
384 let quotes = quotes_for_lang(lang.0.as_ref(), 0);
385 Some(get_quote_from_pair(item, "es.opening, "es.closing))
386 },
387 };
388 if let Some(quote) = maybe_quote {
389 vec.push(PseudoElementContentItem::Text(quote));
390 }
391 },
392 ContentItem::Counter(_, _) |
393 ContentItem::Counters(_, _, _) |
394 ContentItem::NoOpenQuote |
395 ContentItem::NoCloseQuote => {
396 },
398 }
399 }
400 vec
401 },
402 Content::Normal | Content::None => unreachable!(),
403 }
404}