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