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