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 ) {
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 eprintln!("Warning: unsupported property {name}");
345 return;
346 };
347 let mut source_property_declaration = SourcePropertyDeclaration::default();
348 let mut input = ParserInput::new(value);
349 let mut parser = style::values::Parser::new(&mut input);
350 let Ok(_) = PropertyDeclaration::parse_into(
351 &mut source_property_declaration,
352 property_id,
353 &context,
354 &mut parser,
355 ) else {
356 eprintln!("Warning: invalid property value for {name}: {value}");
357 return;
358 };
359
360 if self.style_attribute.is_none() {
361 self.style_attribute = Some(ServoArc::new(guard.wrap(PropertyDeclarationBlock::new())));
362 }
363 self.style_attribute
364 .as_mut()
365 .unwrap()
366 .write_with(&mut guard.write())
367 .extend(source_property_declaration.drain(), Importance::Normal);
368 }
369
370 pub fn remove_style_property(
371 &mut self,
372 name: &str,
373 guard: &SharedRwLock,
374 url_extra_data: UrlExtraData,
375 ) {
376 let context = ParserContext::new(
377 Origin::Author,
378 &url_extra_data,
379 Some(CssRuleType::Style),
380 ParsingMode::DEFAULT,
381 QuirksMode::NoQuirks,
382 Default::default(),
383 None,
384 None,
385 );
386 let Ok(property_id) = PropertyId::parse(name, &context) else {
387 eprintln!("Warning: unsupported property {name}");
388 return;
389 };
390
391 if let Some(style) = &mut self.style_attribute {
392 let mut guard = guard.write();
393 let style = style.write_with(&mut guard);
394 if let Some(index) = style.first_declaration_to_remove(&property_id) {
395 style.remove_property(&property_id, index);
396 }
397 }
398 }
399
400 pub fn set_sub_document(&mut self, sub_document: Box<dyn Document>) {
401 self.special_data = SpecialElementData::SubDocument(sub_document);
402 }
403
404 pub fn remove_sub_document(&mut self) {
405 self.special_data = SpecialElementData::None;
406 }
407
408 pub fn take_inline_layout(&mut self) -> Option<Box<TextLayout>> {
409 std::mem::take(&mut self.inline_layout_data)
410 }
411
412 pub fn is_submit_button(&self) -> bool {
413 if self.name.local != local_name!("button") {
414 return false;
415 }
416 let type_attr = self.attr(local_name!("type"));
417 let is_submit = type_attr == Some("submit");
418 let is_auto_submit = type_attr.is_none()
419 && self.attr(LocalName::from("command")).is_none()
420 && self.attr(LocalName::from("commandfor")).is_none();
421 is_submit || is_auto_submit
422 }
423}
424
425#[derive(Debug, Clone, PartialEq)]
426pub struct RasterImageData {
427 pub width: u32,
429 pub height: u32,
431 pub data: Blob<u8>,
433}
434impl RasterImageData {
435 pub fn new(width: u32, height: u32, data: Arc<Vec<u8>>) -> Self {
436 Self {
437 width,
438 height,
439 data: Blob::new(data),
440 }
441 }
442}
443
444#[derive(Debug, Clone)]
445pub enum ImageData {
446 Raster(RasterImageData),
447 #[cfg(feature = "svg")]
448 Svg(Arc<usvg::Tree>),
449 None,
450}
451#[cfg(feature = "svg")]
452impl From<usvg::Tree> for ImageData {
453 fn from(value: usvg::Tree) -> Self {
454 Self::Svg(Arc::new(value))
455 }
456}
457
458#[derive(Debug, Clone, PartialEq)]
459pub enum Status {
460 Ok,
461 Error,
462 Loading,
463}
464
465#[derive(Debug, Clone)]
466pub struct BackgroundImageData {
467 pub url: ServoArc<Url>,
469 pub status: Status,
471 pub image: ImageData,
473}
474
475impl BackgroundImageData {
476 pub fn new(url: ServoArc<Url>) -> Self {
477 Self {
478 url,
479 status: Status::Loading,
480 image: ImageData::None,
481 }
482 }
483}
484
485pub struct TextInputData {
486 pub editor: Box<parley::PlainEditor<TextBrush>>,
488 pub is_multiline: bool,
490}
491
492impl Clone for TextInputData {
494 fn clone(&self) -> Self {
495 TextInputData::new(self.is_multiline)
496 }
497}
498
499impl TextInputData {
500 pub fn new(is_multiline: bool) -> Self {
501 let editor = Box::new(parley::PlainEditor::new(16.0));
502 Self {
503 editor,
504 is_multiline,
505 }
506 }
507
508 pub fn set_text(
509 &mut self,
510 font_ctx: &mut FontContext,
511 layout_ctx: &mut LayoutContext<TextBrush>,
512 text: &str,
513 ) {
514 if self.editor.text() != text {
515 self.editor.set_text(text);
516 self.editor.driver(font_ctx, layout_ctx).refresh_layout();
517 }
518 }
519}
520
521#[derive(Debug, Clone)]
522pub struct CanvasData {
523 pub custom_paint_source_id: u64,
524}
525
526impl std::fmt::Debug for SpecialElementData {
527 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528 match self {
529 SpecialElementData::SubDocument(_) => f.write_str("NodeSpecificData::SubDocument"),
530 SpecialElementData::Stylesheet(_) => f.write_str("NodeSpecificData::Stylesheet"),
531 SpecialElementData::Image(data) => match **data {
532 ImageData::Raster(_) => f.write_str("NodeSpecificData::Image(Raster)"),
533 #[cfg(feature = "svg")]
534 ImageData::Svg(_) => f.write_str("NodeSpecificData::Image(Svg)"),
535 ImageData::None => f.write_str("NodeSpecificData::Image(None)"),
536 },
537 SpecialElementData::Canvas(_) => f.write_str("NodeSpecificData::Canvas"),
538 SpecialElementData::TableRoot(_) => f.write_str("NodeSpecificData::TableRoot"),
539 SpecialElementData::TextInput(_) => f.write_str("NodeSpecificData::TextInput"),
540 SpecialElementData::CheckboxInput(_) => f.write_str("NodeSpecificData::CheckboxInput"),
541 #[cfg(feature = "file_input")]
542 SpecialElementData::FileInput(_) => f.write_str("NodeSpecificData::FileInput"),
543 SpecialElementData::None => f.write_str("NodeSpecificData::None"),
544 }
545 }
546}
547
548#[derive(Clone)]
549pub struct ListItemLayout {
550 pub marker: Marker,
551 pub position: ListItemLayoutPosition,
552}
553
554#[derive(Debug, PartialEq, Clone)]
557pub enum Marker {
558 Char(char),
559 String(String),
560}
561
562#[derive(Clone)]
564pub enum ListItemLayoutPosition {
565 Inside,
566 Outside(Box<parley::Layout<TextBrush>>),
567}
568
569impl std::fmt::Debug for ListItemLayout {
570 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571 write!(f, "ListItemLayout - marker {:?}", self.marker)
572 }
573}
574
575#[derive(Debug, Clone, Copy, Default, PartialEq)]
576pub struct TextBrush {
578 pub id: usize,
580}
581
582impl TextBrush {
583 pub(crate) fn from_id(id: usize) -> Self {
584 Self { id }
585 }
586}
587
588#[derive(Clone, Default)]
589pub struct TextLayout {
590 pub text: String,
591 pub content_widths: Option<ContentWidths>,
592 pub layout: parley::layout::Layout<TextBrush>,
593}
594
595impl TextLayout {
596 pub fn new() -> Self {
597 Default::default()
598 }
599
600 pub fn content_widths(&mut self) -> ContentWidths {
601 *self
602 .content_widths
603 .get_or_insert_with(|| self.layout.calculate_content_widths())
604 }
605}
606
607impl std::fmt::Debug for TextLayout {
608 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
609 write!(f, "TextLayout")
610 }
611}
612
613#[cfg(feature = "file_input")]
614mod file_data {
615 use std::ops::{Deref, DerefMut};
616 use std::path::PathBuf;
617
618 #[derive(Clone, Debug)]
619 pub struct FileData(pub Vec<PathBuf>);
620 impl Deref for FileData {
621 type Target = Vec<PathBuf>;
622
623 fn deref(&self) -> &Self::Target {
624 &self.0
625 }
626 }
627 impl DerefMut for FileData {
628 fn deref_mut(&mut self) -> &mut Self::Target {
629 &mut self.0
630 }
631 }
632 impl From<Vec<PathBuf>> for FileData {
633 fn from(files: Vec<PathBuf>) -> Self {
634 Self(files)
635 }
636 }
637}
638#[cfg(feature = "file_input")]
639pub use file_data::FileData;