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 let shared_inline_styles = SharedInlineStyles::from_info_and_context(&info, context);
185 element
186 .box_slot()
187 .set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
188
189 handler.enter_display_contents(shared_inline_styles);
190 traverse_children_of(&info, context, handler);
191 handler.leave_display_contents();
192 },
193 Display::GeneratingBox(display) => {
194 let contents = Contents::for_element(element, context);
195 let display = display.used_value_for_contents(&contents, &info);
196 let box_slot = element.box_slot();
197 handler.handle_element(&info, display, contents, box_slot);
198 },
199 }
200}
201
202fn traverse_eager_pseudo_element<'dom>(
203 pseudo_element_type: PseudoElement,
204 node_info: &NodeAndStyleInfo<'dom>,
205 context: &LayoutContext,
206 handler: &mut impl TraversalHandler<'dom>,
207) {
208 assert!(pseudo_element_type.is_eager());
209
210 let Some(pseudo_element_info) = node_info.with_pseudo_element(context, pseudo_element_type)
213 else {
214 return;
215 };
216 if pseudo_element_info.style.ineffective_content_property() {
217 return;
218 }
219
220 match Display::from(pseudo_element_info.style.get_box().display) {
221 Display::None => {},
222 Display::Contents => {
223 let items = generate_pseudo_element_content(&pseudo_element_info, context);
224 let box_slot = pseudo_element_info.node.box_slot();
225 let shared_inline_styles =
226 SharedInlineStyles::from_info_and_context(&pseudo_element_info, context);
227 box_slot.set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
228
229 handler.enter_display_contents(shared_inline_styles);
230 traverse_pseudo_element_contents(&pseudo_element_info, context, handler, items);
231 handler.leave_display_contents();
232 },
233 Display::GeneratingBox(display) => {
234 let items = generate_pseudo_element_content(&pseudo_element_info, context);
235 let box_slot = pseudo_element_info.node.box_slot();
236 let contents = Contents::for_pseudo_element(items);
237 handler.handle_element(&pseudo_element_info, display, contents, box_slot);
238 },
239 }
240}
241
242fn traverse_pseudo_element_contents<'dom>(
243 info: &NodeAndStyleInfo<'dom>,
244 context: &LayoutContext,
245 handler: &mut impl TraversalHandler<'dom>,
246 items: Vec<PseudoElementContentItem>,
247) {
248 let mut anonymous_info = None;
249 for item in items {
250 match item {
251 PseudoElementContentItem::Text(text) => handler.handle_text(info, text.into()),
252 PseudoElementContentItem::Replaced(contents) => {
253 let anonymous_info = anonymous_info.get_or_insert_with(|| {
254 info.with_pseudo_element(context, PseudoElement::ServoAnonymousBox)
255 .unwrap_or_else(|| info.clone())
256 });
257 let display_inline = DisplayGeneratingBox::OutsideInside {
258 outside: DisplayOutside::Inline,
259 inside: DisplayInside::Flow {
260 is_list_item: false,
261 },
262 };
263 debug_assert!(
265 Display::from(anonymous_info.style.get_box().display) ==
266 Display::GeneratingBox(display_inline)
267 );
268 handler.handle_element(
269 anonymous_info,
270 display_inline,
271 Contents::Replaced(contents),
272 anonymous_info.node.box_slot(),
273 )
274 },
275 }
276 }
277}
278
279impl Contents {
280 pub fn is_replaced(&self) -> bool {
282 matches!(self, Contents::Replaced(_))
283 }
284
285 pub(crate) fn for_element(node: ServoLayoutNode<'_>, context: &LayoutContext) -> Self {
286 let is_widget = matches!(
287 node.type_id(),
288 Some(LayoutNodeType::Element(
289 LayoutElementType::HTMLButtonElement |
290 LayoutElementType::HTMLInputElement |
291 LayoutElementType::HTMLMeterElement |
292 LayoutElementType::HTMLProgressElement |
293 LayoutElementType::HTMLSelectElement |
294 LayoutElementType::HTMLTextAreaElement
295 ))
296 );
297 if is_widget {
298 Self::Widget(NonReplacedContents::OfElement)
299 } else if let Some(replaced) = ReplacedContents::for_element(node, context) {
300 Self::Replaced(replaced)
301 } else {
302 Self::NonReplaced(NonReplacedContents::OfElement)
303 }
304 }
305
306 pub(crate) fn for_pseudo_element(contents: Vec<PseudoElementContentItem>) -> Self {
307 Self::NonReplaced(NonReplacedContents::OfPseudoElement(contents))
308 }
309
310 pub(crate) fn non_replaced_contents(self) -> Option<NonReplacedContents> {
311 match self {
312 Self::NonReplaced(contents) | Self::Widget(contents) => Some(contents),
313 Self::Replaced(_) => None,
314 }
315 }
316}
317
318impl NonReplacedContents {
319 pub(crate) fn traverse<'dom>(
320 self,
321 context: &LayoutContext,
322 info: &NodeAndStyleInfo<'dom>,
323 handler: &mut impl TraversalHandler<'dom>,
324 ) {
325 match self {
326 NonReplacedContents::OfElement => traverse_children_of(info, context, handler),
327 NonReplacedContents::OfPseudoElement(items) => {
328 traverse_pseudo_element_contents(info, context, handler, items)
329 },
330 }
331 }
332}
333
334fn get_quote_from_pair<I, S>(item: &ContentItem<I>, opening: &S, closing: &S) -> String
335where
336 S: ToString + ?Sized,
337{
338 match item {
339 ContentItem::OpenQuote => opening.to_string(),
340 ContentItem::CloseQuote => closing.to_string(),
341 _ => unreachable!("Got an unexpected ContentItem type when processing quotes."),
342 }
343}
344
345pub(crate) fn generate_pseudo_element_content(
347 pseudo_element_info: &NodeAndStyleInfo,
348 context: &LayoutContext,
349) -> Vec<PseudoElementContentItem> {
350 match &pseudo_element_info.style.get_counters().content {
351 Content::Items(items) => {
352 items
353 .items
354 .iter()
355 .filter_map(|item| {
356 match item {
357 ContentItem::String(s) => {
358 Some(PseudoElementContentItem::Text(s.to_string()))
359 },
360 ContentItem::Image(image) => {
361 ReplacedContents::from_image(pseudo_element_info.node, context, image)
362 .map(PseudoElementContentItem::Replaced)
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(
380 item,
381 "es.opening,
382 "es.closing,
383 ))
384 },
385 };
386 maybe_quote.map(PseudoElementContentItem::Text)
387 },
388 ContentItem::Counter(_, style) | ContentItem::Counters(_, _, style) => {
389 Some(PseudoElementContentItem::Text(
391 generate_counter_representation(style).to_string(),
392 ))
393 },
394 ContentItem::NoOpenQuote | ContentItem::NoCloseQuote => None,
395 }
396 })
397 .collect()
398 },
399 Content::Normal | Content::None => unreachable!(),
400 }
401}