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