1use cssparser::ParserInput;
2use markup5ever::{LocalName, QualName, local_name};
3use parley::{ContentWidths, FontContext, LayoutContext};
4use selectors::matching::QuirksMode;
5use std::str::FromStr;
6use std::sync::Arc;
7use style::Atom;
8use style::parser::ParserContext;
9use style::properties::{Importance, PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
10use style::stylesheets::{DocumentStyleSheet, Origin, UrlExtraData};
11use style::{
12 properties::{PropertyDeclarationBlock, parse_style_attribute},
13 servo_arc::Arc as ServoArc,
14 shared_lock::{Locked, SharedRwLock},
15 stylesheets::CssRuleType,
16};
17use style_traits::ParsingMode;
18use url::Url;
19
20use super::{Attribute, Attributes};
21use crate::layout::table::TableContext;
22
23#[derive(Debug, Clone)]
24pub struct ElementData {
25 pub name: QualName,
27
28 pub id: Option<Atom>,
30
31 pub attrs: Attributes,
33
34 pub is_focussable: bool,
36
37 pub style_attribute: Option<ServoArc<Locked<PropertyDeclarationBlock>>>,
39
40 pub special_data: SpecialElementData,
46
47 pub background_images: Vec<Option<BackgroundImageData>>,
48
49 pub inline_layout_data: Option<Box<TextLayout>>,
51
52 pub list_item_data: Option<Box<ListItemLayout>>,
55
56 pub template_contents: Option<usize>,
58 }
61
62#[derive(Copy, Clone, Default)]
63#[non_exhaustive]
64pub enum SpecialElementType {
65 Stylesheet,
66 Image,
67 Canvas,
68 TableRoot,
69 TextInput,
70 CheckboxInput,
71 #[cfg(feature = "file_input")]
72 FileInput,
73 #[default]
74 None,
75}
76
77#[derive(Clone, Default)]
79pub enum SpecialElementData {
80 Stylesheet(DocumentStyleSheet),
81 Image(Box<ImageData>),
83 Canvas(CanvasData),
85 TableRoot(Arc<TableContext>),
87 TextInput(TextInputData),
89 CheckboxInput(bool),
91 #[cfg(feature = "file_input")]
93 FileInput(FileData),
94 #[default]
96 None,
97}
98
99impl SpecialElementData {
100 pub fn take(&mut self) -> Self {
101 std::mem::take(self)
102 }
103}
104
105impl ElementData {
106 pub fn new(name: QualName, attrs: Vec<Attribute>) -> Self {
107 let id_attr_atom = attrs
108 .iter()
109 .find(|attr| &attr.name.local == "id")
110 .map(|attr| attr.value.as_ref())
111 .map(|value: &str| Atom::from(value));
112
113 let mut data = ElementData {
114 name,
115 id: id_attr_atom,
116 attrs: Attributes::new(attrs),
117 is_focussable: false,
118 style_attribute: Default::default(),
119 inline_layout_data: None,
120 list_item_data: None,
121 special_data: SpecialElementData::None,
122 template_contents: None,
123 background_images: Vec::new(),
124 };
125 data.flush_is_focussable();
126 data
127 }
128
129 pub fn attrs(&self) -> &[Attribute] {
130 &self.attrs
131 }
132
133 pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
134 let attr = self.attrs.iter().find(|attr| name == attr.name.local)?;
135 Some(&attr.value)
136 }
137
138 pub fn attr_parsed<T: FromStr>(&self, name: impl PartialEq<LocalName>) -> Option<T> {
139 let attr = self.attrs.iter().find(|attr| name == attr.name.local)?;
140 attr.value.parse::<T>().ok()
141 }
142
143 pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
145 self.attrs.iter().any(|attr| name == attr.name.local)
146 }
147
148 pub fn image_data(&self) -> Option<&ImageData> {
149 match &self.special_data {
150 SpecialElementData::Image(data) => Some(&**data),
151 _ => None,
152 }
153 }
154
155 pub fn image_data_mut(&mut self) -> Option<&mut ImageData> {
156 match self.special_data {
157 SpecialElementData::Image(ref mut data) => Some(&mut **data),
158 _ => None,
159 }
160 }
161
162 pub fn raster_image_data(&self) -> Option<&RasterImageData> {
163 match self.image_data()? {
164 ImageData::Raster(data) => Some(data),
165 _ => None,
166 }
167 }
168
169 pub fn raster_image_data_mut(&mut self) -> Option<&mut RasterImageData> {
170 match self.image_data_mut()? {
171 ImageData::Raster(data) => Some(data),
172 _ => None,
173 }
174 }
175
176 pub fn canvas_data(&self) -> Option<&CanvasData> {
177 match &self.special_data {
178 SpecialElementData::Canvas(data) => Some(data),
179 _ => None,
180 }
181 }
182
183 #[cfg(feature = "svg")]
184 pub fn svg_data(&self) -> Option<&usvg::Tree> {
185 match self.image_data()? {
186 ImageData::Svg(data) => Some(data),
187 _ => None,
188 }
189 }
190
191 #[cfg(feature = "svg")]
192 pub fn svg_data_mut(&mut self) -> Option<&mut usvg::Tree> {
193 match self.image_data_mut()? {
194 ImageData::Svg(data) => Some(data),
195 _ => None,
196 }
197 }
198
199 pub fn text_input_data(&self) -> Option<&TextInputData> {
200 match &self.special_data {
201 SpecialElementData::TextInput(data) => Some(data),
202 _ => None,
203 }
204 }
205
206 pub fn text_input_data_mut(&mut self) -> Option<&mut TextInputData> {
207 match &mut self.special_data {
208 SpecialElementData::TextInput(data) => Some(data),
209 _ => None,
210 }
211 }
212
213 pub fn checkbox_input_checked(&self) -> Option<bool> {
214 match self.special_data {
215 SpecialElementData::CheckboxInput(checked) => Some(checked),
216 _ => None,
217 }
218 }
219
220 pub fn checkbox_input_checked_mut(&mut self) -> Option<&mut bool> {
221 match self.special_data {
222 SpecialElementData::CheckboxInput(ref mut checked) => Some(checked),
223 _ => None,
224 }
225 }
226
227 #[cfg(feature = "file_input")]
228 pub fn file_data(&self) -> Option<&FileData> {
229 match &self.special_data {
230 SpecialElementData::FileInput(data) => Some(data),
231 _ => None,
232 }
233 }
234
235 #[cfg(feature = "file_input")]
236 pub fn file_data_mut(&mut self) -> Option<&mut FileData> {
237 match &mut self.special_data {
238 SpecialElementData::FileInput(data) => Some(data),
239 _ => None,
240 }
241 }
242
243 pub fn flush_is_focussable(&mut self) {
244 let disabled: bool = self.attr_parsed(local_name!("disabled")).unwrap_or(false);
245 let tabindex: Option<i32> = self.attr_parsed(local_name!("tabindex"));
246
247 self.is_focussable = !disabled
248 && match tabindex {
249 Some(index) => index >= 0,
250 None => {
251 if [local_name!("a"), local_name!("area")].contains(&self.name.local) {
258 self.attr(local_name!("href")).is_some()
259 } else {
260 const DEFAULT_FOCUSSABLE_ELEMENTS: [LocalName; 6] = [
261 local_name!("button"),
262 local_name!("input"),
263 local_name!("select"),
264 local_name!("textarea"),
265 local_name!("frame"),
266 local_name!("iframe"),
267 ];
268 DEFAULT_FOCUSSABLE_ELEMENTS.contains(&self.name.local)
269 }
270 }
271 }
272 }
273
274 pub fn flush_style_attribute(&mut self, guard: &SharedRwLock, url_extra_data: &UrlExtraData) {
275 self.style_attribute = self.attr(local_name!("style")).map(|style_str| {
276 ServoArc::new(guard.wrap(parse_style_attribute(
277 style_str,
278 url_extra_data,
279 None,
280 QuirksMode::NoQuirks,
281 CssRuleType::Style,
282 )))
283 });
284 }
285
286 pub fn set_style_property(
287 &mut self,
288 name: &str,
289 value: &str,
290 guard: &SharedRwLock,
291 url_extra_data: UrlExtraData,
292 ) {
293 let context = ParserContext::new(
294 Origin::Author,
295 &url_extra_data,
296 Some(CssRuleType::Style),
297 ParsingMode::DEFAULT,
298 QuirksMode::NoQuirks,
299 Default::default(),
300 None,
301 None,
302 );
303
304 let Ok(property_id) = PropertyId::parse(name, &context) else {
305 eprintln!("Warning: unsupported property {name}");
306 return;
307 };
308 let mut source_property_declaration = SourcePropertyDeclaration::default();
309 let mut input = ParserInput::new(value);
310 let mut parser = style::values::Parser::new(&mut input);
311 let Ok(_) = PropertyDeclaration::parse_into(
312 &mut source_property_declaration,
313 property_id,
314 &context,
315 &mut parser,
316 ) else {
317 eprintln!("Warning: invalid property value for {name}: {value}");
318 return;
319 };
320
321 if self.style_attribute.is_none() {
322 self.style_attribute = Some(ServoArc::new(guard.wrap(PropertyDeclarationBlock::new())));
323 }
324 self.style_attribute
325 .as_mut()
326 .unwrap()
327 .write_with(&mut guard.write())
328 .extend(source_property_declaration.drain(), Importance::Normal);
329 }
330
331 pub fn remove_style_property(
332 &mut self,
333 name: &str,
334 guard: &SharedRwLock,
335 url_extra_data: UrlExtraData,
336 ) {
337 let context = ParserContext::new(
338 Origin::Author,
339 &url_extra_data,
340 Some(CssRuleType::Style),
341 ParsingMode::DEFAULT,
342 QuirksMode::NoQuirks,
343 Default::default(),
344 None,
345 None,
346 );
347 let Ok(property_id) = PropertyId::parse(name, &context) else {
348 eprintln!("Warning: unsupported property {name}");
349 return;
350 };
351
352 if let Some(style) = &mut self.style_attribute {
353 let mut guard = guard.write();
354 let style = style.write_with(&mut guard);
355 if let Some(index) = style.first_declaration_to_remove(&property_id) {
356 style.remove_property(&property_id, index);
357 }
358 }
359 }
360
361 pub fn take_inline_layout(&mut self) -> Option<Box<TextLayout>> {
362 std::mem::take(&mut self.inline_layout_data)
363 }
364
365 pub fn is_submit_button(&self) -> bool {
366 if self.name.local != local_name!("button") {
367 return false;
368 }
369 let type_attr = self.attr(local_name!("type"));
370 let is_submit = type_attr == Some("submit");
371 let is_auto_submit = type_attr.is_none()
372 && self.attr(LocalName::from("command")).is_none()
373 && self.attr(LocalName::from("commandfor")).is_none();
374 is_submit || is_auto_submit
375 }
376}
377
378#[derive(Debug, Clone, PartialEq, Default)]
379pub struct RasterImageData {
380 pub width: u32,
382 pub height: u32,
384 pub data: Arc<Vec<u8>>,
386}
387impl RasterImageData {
388 pub fn new(width: u32, height: u32, data: Arc<Vec<u8>>) -> Self {
389 Self {
390 width,
391 height,
392 data,
393 }
394 }
395}
396
397#[derive(Debug, Clone)]
398pub enum ImageData {
399 Raster(RasterImageData),
400 #[cfg(feature = "svg")]
401 Svg(Box<usvg::Tree>),
402 None,
403}
404#[cfg(feature = "svg")]
405impl From<usvg::Tree> for ImageData {
406 fn from(value: usvg::Tree) -> Self {
407 Self::Svg(Box::new(value))
408 }
409}
410
411#[derive(Debug, Clone, PartialEq)]
412pub enum Status {
413 Ok,
414 Error,
415 Loading,
416}
417
418#[derive(Debug, Clone)]
419pub struct BackgroundImageData {
420 pub url: ServoArc<Url>,
422 pub status: Status,
424 pub image: ImageData,
426}
427
428impl BackgroundImageData {
429 pub fn new(url: ServoArc<Url>) -> Self {
430 Self {
431 url,
432 status: Status::Loading,
433 image: ImageData::None,
434 }
435 }
436}
437
438pub struct TextInputData {
439 pub editor: Box<parley::PlainEditor<TextBrush>>,
441 pub is_multiline: bool,
443}
444
445impl Clone for TextInputData {
447 fn clone(&self) -> Self {
448 TextInputData::new(self.is_multiline)
449 }
450}
451
452impl TextInputData {
453 pub fn new(is_multiline: bool) -> Self {
454 let editor = Box::new(parley::PlainEditor::new(16.0));
455 Self {
456 editor,
457 is_multiline,
458 }
459 }
460
461 pub fn set_text(
462 &mut self,
463 font_ctx: &mut FontContext,
464 layout_ctx: &mut LayoutContext<TextBrush>,
465 text: &str,
466 ) {
467 if self.editor.text() != text {
468 self.editor.set_text(text);
469 self.editor.driver(font_ctx, layout_ctx).refresh_layout();
470 }
471 }
472}
473
474#[derive(Debug, Clone)]
475pub struct CanvasData {
476 pub custom_paint_source_id: u64,
477}
478
479impl std::fmt::Debug for SpecialElementData {
480 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481 match self {
482 SpecialElementData::Stylesheet(_) => f.write_str("NodeSpecificData::Stylesheet"),
483 SpecialElementData::Image(data) => match **data {
484 ImageData::Raster(_) => f.write_str("NodeSpecificData::Image(Raster)"),
485 #[cfg(feature = "svg")]
486 ImageData::Svg(_) => f.write_str("NodeSpecificData::Image(Svg)"),
487 ImageData::None => f.write_str("NodeSpecificData::Image(None)"),
488 },
489 SpecialElementData::Canvas(_) => f.write_str("NodeSpecificData::Canvas"),
490 SpecialElementData::TableRoot(_) => f.write_str("NodeSpecificData::TableRoot"),
491 SpecialElementData::TextInput(_) => f.write_str("NodeSpecificData::TextInput"),
492 SpecialElementData::CheckboxInput(_) => f.write_str("NodeSpecificData::CheckboxInput"),
493 #[cfg(feature = "file_input")]
494 SpecialElementData::FileInput(_) => f.write_str("NodeSpecificData::FileInput"),
495 SpecialElementData::None => f.write_str("NodeSpecificData::None"),
496 }
497 }
498}
499
500#[derive(Clone)]
501pub struct ListItemLayout {
502 pub marker: Marker,
503 pub position: ListItemLayoutPosition,
504}
505
506#[derive(Debug, PartialEq, Clone)]
509pub enum Marker {
510 Char(char),
511 String(String),
512}
513
514#[derive(Clone)]
516pub enum ListItemLayoutPosition {
517 Inside,
518 Outside(Box<parley::Layout<TextBrush>>),
519}
520
521impl std::fmt::Debug for ListItemLayout {
522 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523 write!(f, "ListItemLayout - marker {:?}", self.marker)
524 }
525}
526
527#[derive(Debug, Clone, Copy, Default, PartialEq)]
528pub struct TextBrush {
530 pub id: usize,
532}
533
534impl TextBrush {
535 pub(crate) fn from_id(id: usize) -> Self {
536 Self { id }
537 }
538}
539
540#[derive(Clone, Default)]
541pub struct TextLayout {
542 pub text: String,
543 pub content_widths: Option<ContentWidths>,
544 pub layout: parley::layout::Layout<TextBrush>,
545}
546
547impl TextLayout {
548 pub fn new() -> Self {
549 Default::default()
550 }
551
552 pub fn content_widths(&mut self) -> ContentWidths {
553 *self
554 .content_widths
555 .get_or_insert_with(|| self.layout.calculate_content_widths())
556 }
557}
558
559impl std::fmt::Debug for TextLayout {
560 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
561 write!(f, "TextLayout")
562 }
563}
564
565#[cfg(feature = "file_input")]
566mod file_data {
567 use std::ops::{Deref, DerefMut};
568 use std::path::PathBuf;
569
570 #[derive(Clone, Debug)]
571 pub struct FileData(pub Vec<PathBuf>);
572 impl Deref for FileData {
573 type Target = Vec<PathBuf>;
574
575 fn deref(&self) -> &Self::Target {
576 &self.0
577 }
578 }
579 impl DerefMut for FileData {
580 fn deref_mut(&mut self) -> &mut Self::Target {
581 &mut self.0
582 }
583 }
584 impl From<Vec<PathBuf>> for FileData {
585 fn from(files: Vec<PathBuf>) -> Self {
586 Self(files)
587 }
588 }
589}
590#[cfg(feature = "file_input")]
591pub use file_data::FileData;