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