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