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 if let Some(replaced) = ReplacedContents::for_element(node, context) {
255 return Self::Replaced(replaced);
256 }
257 let is_widget = matches!(
258 node.type_id(),
259 Some(LayoutNodeType::Element(
260 LayoutElementType::HTMLInputElement |
261 LayoutElementType::HTMLSelectElement |
262 LayoutElementType::HTMLTextAreaElement
263 ))
264 );
265 if is_widget {
266 Self::Widget(NonReplacedContents::OfElement)
267 } else {
268 Self::NonReplaced(NonReplacedContents::OfElement)
269 }
270 }
271
272 pub(crate) fn for_pseudo_element(contents: Vec<PseudoElementContentItem>) -> Self {
273 Self::NonReplaced(NonReplacedContents::OfPseudoElement(contents))
274 }
275
276 pub(crate) fn non_replaced_contents(self) -> Option<NonReplacedContents> {
277 match self {
278 Self::NonReplaced(contents) | Self::Widget(contents) => Some(contents),
279 Self::Replaced(_) => None,
280 }
281 }
282}
283
284impl NonReplacedContents {
285 pub(crate) fn traverse<'dom>(
286 self,
287 context: &LayoutContext,
288 info: &NodeAndStyleInfo<'dom>,
289 handler: &mut impl TraversalHandler<'dom>,
290 ) {
291 match self {
292 NonReplacedContents::OfElement => traverse_children_of(info, context, handler),
293 NonReplacedContents::OfPseudoElement(items) => {
294 traverse_pseudo_element_contents(info, context, handler, items)
295 },
296 }
297 }
298}
299
300fn get_quote_from_pair<I, S>(item: &ContentItem<I>, opening: &S, closing: &S) -> String
301where
302 S: ToString + ?Sized,
303{
304 match item {
305 ContentItem::OpenQuote => opening.to_string(),
306 ContentItem::CloseQuote => closing.to_string(),
307 _ => unreachable!("Got an unexpected ContentItem type when processing quotes."),
308 }
309}
310
311pub(crate) fn generate_pseudo_element_content(
313 pseudo_element_info: &NodeAndStyleInfo,
314 context: &LayoutContext,
315) -> Vec<PseudoElementContentItem> {
316 match &pseudo_element_info.style.get_counters().content {
317 Content::Items(items) => {
318 let mut vec = vec![];
319 for item in items.items.iter() {
320 match item {
321 ContentItem::String(s) => {
322 vec.push(PseudoElementContentItem::Text(s.to_string()));
323 },
324 ContentItem::Attr(attr) => {
325 let element = pseudo_element_info
326 .node
327 .as_element()
328 .expect("Expected an element");
329
330 let attr_name = match element.is_html_element_in_html_document() {
345 true => &*attr.attribute.to_ascii_lowercase(),
346 false => &*attr.attribute,
347 };
348
349 pseudo_element_info
350 .node
351 .set_uses_content_attribute_with_attr(true);
352 let attr_val =
353 element.attribute(&attr.namespace_url, &LocalName::from(attr_name));
354 vec.push(PseudoElementContentItem::Text(
355 attr_val.map_or("".to_string(), |s| s.to_string()),
356 ));
357 },
358 ContentItem::Image(image) => {
359 if let Some(replaced_content) =
360 ReplacedContents::from_image(pseudo_element_info.node, context, image)
361 {
362 vec.push(PseudoElementContentItem::Replaced(replaced_content));
363 }
364 },
365 ContentItem::OpenQuote | ContentItem::CloseQuote => {
366 let maybe_quote = match &pseudo_element_info.style.get_list().quotes {
368 Quotes::QuoteList(quote_list) => {
369 quote_list.0.first().map(|quote_pair| {
370 get_quote_from_pair(
371 item,
372 &*quote_pair.opening,
373 &*quote_pair.closing,
374 )
375 })
376 },
377 Quotes::Auto => {
378 let lang = &pseudo_element_info.style.get_font()._x_lang;
379 let quotes = quotes_for_lang(lang.0.as_ref(), 0);
380 Some(get_quote_from_pair(item, "es.opening, "es.closing))
381 },
382 };
383 if let Some(quote) = maybe_quote {
384 vec.push(PseudoElementContentItem::Text(quote));
385 }
386 },
387 ContentItem::Counter(_, style) | ContentItem::Counters(_, _, style) => {
388 vec.push(PseudoElementContentItem::Text(
390 generate_counter_representation(style).to_string(),
391 ));
392 },
393 ContentItem::NoOpenQuote | ContentItem::NoCloseQuote => {},
394 }
395 }
396 vec
397 },
398 Content::Normal | Content::None => unreachable!(),
399 }
400}