use cssparser::ParserInput;
use markup5ever::{LocalName, QualName, local_name};
use parley::{ContentWidths, FontContext, LayoutContext};
use selectors::matching::QuirksMode;
use std::str::FromStr;
use std::sync::Arc;
use style::Atom;
use style::parser::ParserContext;
use style::properties::{Importance, PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
use style::stylesheets::{DocumentStyleSheet, Origin, UrlExtraData};
use style::{
properties::{PropertyDeclarationBlock, parse_style_attribute},
servo_arc::Arc as ServoArc,
shared_lock::{Locked, SharedRwLock},
stylesheets::CssRuleType,
};
use style_traits::ParsingMode;
use url::Url;
use super::{Attribute, Attributes};
use crate::layout::table::TableContext;
#[derive(Debug, Clone)]
pub struct ElementData {
pub name: QualName,
pub id: Option<Atom>,
pub attrs: Attributes,
pub is_focussable: bool,
pub style_attribute: Option<ServoArc<Locked<PropertyDeclarationBlock>>>,
pub special_data: SpecialElementData,
pub background_images: Vec<Option<BackgroundImageData>>,
pub inline_layout_data: Option<Box<TextLayout>>,
pub list_item_data: Option<Box<ListItemLayout>>,
pub template_contents: Option<usize>,
}
#[derive(Copy, Clone, Default)]
#[non_exhaustive]
pub enum SpecialElementType {
Stylesheet,
Image,
Canvas,
TableRoot,
TextInput,
CheckboxInput,
#[cfg(feature = "file_input")]
FileInput,
#[default]
None,
}
#[derive(Clone, Default)]
pub enum SpecialElementData {
Stylesheet(DocumentStyleSheet),
Image(Box<ImageData>),
Canvas(CanvasData),
TableRoot(Arc<TableContext>),
TextInput(TextInputData),
CheckboxInput(bool),
#[cfg(feature = "file_input")]
FileInput(FileData),
#[default]
None,
}
impl SpecialElementData {
pub fn take(&mut self) -> Self {
std::mem::take(self)
}
}
impl ElementData {
pub fn new(name: QualName, attrs: Vec<Attribute>) -> Self {
let id_attr_atom = attrs
.iter()
.find(|attr| &attr.name.local == "id")
.map(|attr| attr.value.as_ref())
.map(|value: &str| Atom::from(value));
let mut data = ElementData {
name,
id: id_attr_atom,
attrs: Attributes::new(attrs),
is_focussable: false,
style_attribute: Default::default(),
inline_layout_data: None,
list_item_data: None,
special_data: SpecialElementData::None,
template_contents: None,
background_images: Vec::new(),
};
data.flush_is_focussable();
data
}
pub fn attrs(&self) -> &[Attribute] {
&self.attrs
}
pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
let attr = self.attrs.iter().find(|attr| name == attr.name.local)?;
Some(&attr.value)
}
pub fn attr_parsed<T: FromStr>(&self, name: impl PartialEq<LocalName>) -> Option<T> {
let attr = self.attrs.iter().find(|attr| name == attr.name.local)?;
attr.value.parse::<T>().ok()
}
pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
self.attrs.iter().any(|attr| name == attr.name.local)
}
pub fn image_data(&self) -> Option<&ImageData> {
match &self.special_data {
SpecialElementData::Image(data) => Some(&**data),
_ => None,
}
}
pub fn image_data_mut(&mut self) -> Option<&mut ImageData> {
match self.special_data {
SpecialElementData::Image(ref mut data) => Some(&mut **data),
_ => None,
}
}
pub fn raster_image_data(&self) -> Option<&RasterImageData> {
match self.image_data()? {
ImageData::Raster(data) => Some(data),
_ => None,
}
}
pub fn raster_image_data_mut(&mut self) -> Option<&mut RasterImageData> {
match self.image_data_mut()? {
ImageData::Raster(data) => Some(data),
_ => None,
}
}
pub fn canvas_data(&self) -> Option<&CanvasData> {
match &self.special_data {
SpecialElementData::Canvas(data) => Some(data),
_ => None,
}
}
#[cfg(feature = "svg")]
pub fn svg_data(&self) -> Option<&usvg::Tree> {
match self.image_data()? {
ImageData::Svg(data) => Some(data),
_ => None,
}
}
#[cfg(feature = "svg")]
pub fn svg_data_mut(&mut self) -> Option<&mut usvg::Tree> {
match self.image_data_mut()? {
ImageData::Svg(data) => Some(data),
_ => None,
}
}
pub fn text_input_data(&self) -> Option<&TextInputData> {
match &self.special_data {
SpecialElementData::TextInput(data) => Some(data),
_ => None,
}
}
pub fn text_input_data_mut(&mut self) -> Option<&mut TextInputData> {
match &mut self.special_data {
SpecialElementData::TextInput(data) => Some(data),
_ => None,
}
}
pub fn checkbox_input_checked(&self) -> Option<bool> {
match self.special_data {
SpecialElementData::CheckboxInput(checked) => Some(checked),
_ => None,
}
}
pub fn checkbox_input_checked_mut(&mut self) -> Option<&mut bool> {
match self.special_data {
SpecialElementData::CheckboxInput(ref mut checked) => Some(checked),
_ => None,
}
}
#[cfg(feature = "file_input")]
pub fn file_data(&self) -> Option<&FileData> {
match &self.special_data {
SpecialElementData::FileInput(data) => Some(data),
_ => None,
}
}
#[cfg(feature = "file_input")]
pub fn file_data_mut(&mut self) -> Option<&mut FileData> {
match &mut self.special_data {
SpecialElementData::FileInput(data) => Some(data),
_ => None,
}
}
pub fn flush_is_focussable(&mut self) {
let disabled: bool = self.attr_parsed(local_name!("disabled")).unwrap_or(false);
let tabindex: Option<i32> = self.attr_parsed(local_name!("tabindex"));
self.is_focussable = !disabled
&& match tabindex {
Some(index) => index >= 0,
None => {
if [local_name!("a"), local_name!("area")].contains(&self.name.local) {
self.attr(local_name!("href")).is_some()
} else {
const DEFAULT_FOCUSSABLE_ELEMENTS: [LocalName; 6] = [
local_name!("button"),
local_name!("input"),
local_name!("select"),
local_name!("textarea"),
local_name!("frame"),
local_name!("iframe"),
];
DEFAULT_FOCUSSABLE_ELEMENTS.contains(&self.name.local)
}
}
}
}
pub fn flush_style_attribute(&mut self, guard: &SharedRwLock, url_extra_data: &UrlExtraData) {
self.style_attribute = self.attr(local_name!("style")).map(|style_str| {
ServoArc::new(guard.wrap(parse_style_attribute(
style_str,
url_extra_data,
None,
QuirksMode::NoQuirks,
CssRuleType::Style,
)))
});
}
pub fn set_style_property(
&mut self,
name: &str,
value: &str,
guard: &SharedRwLock,
url_extra_data: UrlExtraData,
) {
let context = ParserContext::new(
Origin::Author,
&url_extra_data,
Some(CssRuleType::Style),
ParsingMode::DEFAULT,
QuirksMode::NoQuirks,
Default::default(),
None,
None,
);
let Ok(property_id) = PropertyId::parse(name, &context) else {
eprintln!("Warning: unsupported property {name}");
return;
};
let mut source_property_declaration = SourcePropertyDeclaration::default();
let mut input = ParserInput::new(value);
let mut parser = style::values::Parser::new(&mut input);
let Ok(_) = PropertyDeclaration::parse_into(
&mut source_property_declaration,
property_id,
&context,
&mut parser,
) else {
eprintln!("Warning: invalid property value for {name}: {value}");
return;
};
if self.style_attribute.is_none() {
self.style_attribute = Some(ServoArc::new(guard.wrap(PropertyDeclarationBlock::new())));
}
self.style_attribute
.as_mut()
.unwrap()
.write_with(&mut guard.write())
.extend(source_property_declaration.drain(), Importance::Normal);
}
pub fn remove_style_property(
&mut self,
name: &str,
guard: &SharedRwLock,
url_extra_data: UrlExtraData,
) {
let context = ParserContext::new(
Origin::Author,
&url_extra_data,
Some(CssRuleType::Style),
ParsingMode::DEFAULT,
QuirksMode::NoQuirks,
Default::default(),
None,
None,
);
let Ok(property_id) = PropertyId::parse(name, &context) else {
eprintln!("Warning: unsupported property {name}");
return;
};
if let Some(style) = &mut self.style_attribute {
let mut guard = guard.write();
let style = style.write_with(&mut guard);
if let Some(index) = style.first_declaration_to_remove(&property_id) {
style.remove_property(&property_id, index);
}
}
}
pub fn take_inline_layout(&mut self) -> Option<Box<TextLayout>> {
std::mem::take(&mut self.inline_layout_data)
}
pub fn is_submit_button(&self) -> bool {
if self.name.local != local_name!("button") {
return false;
}
let type_attr = self.attr(local_name!("type"));
let is_submit = type_attr == Some("submit");
let is_auto_submit = type_attr.is_none()
&& self.attr(LocalName::from("command")).is_none()
&& self.attr(LocalName::from("commandfor")).is_none();
is_submit || is_auto_submit
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct RasterImageData {
pub width: u32,
pub height: u32,
pub data: Arc<Vec<u8>>,
}
impl RasterImageData {
pub fn new(width: u32, height: u32, data: Arc<Vec<u8>>) -> Self {
Self {
width,
height,
data,
}
}
}
#[derive(Debug, Clone)]
pub enum ImageData {
Raster(RasterImageData),
#[cfg(feature = "svg")]
Svg(Box<usvg::Tree>),
None,
}
#[cfg(feature = "svg")]
impl From<usvg::Tree> for ImageData {
fn from(value: usvg::Tree) -> Self {
Self::Svg(Box::new(value))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Status {
Ok,
Error,
Loading,
}
#[derive(Debug, Clone)]
pub struct BackgroundImageData {
pub url: ServoArc<Url>,
pub status: Status,
pub image: ImageData,
}
impl BackgroundImageData {
pub fn new(url: ServoArc<Url>) -> Self {
Self {
url,
status: Status::Loading,
image: ImageData::None,
}
}
}
pub struct TextInputData {
pub editor: Box<parley::PlainEditor<TextBrush>>,
pub is_multiline: bool,
}
impl Clone for TextInputData {
fn clone(&self) -> Self {
TextInputData::new(self.is_multiline)
}
}
impl TextInputData {
pub fn new(is_multiline: bool) -> Self {
let editor = Box::new(parley::PlainEditor::new(16.0));
Self {
editor,
is_multiline,
}
}
pub fn set_text(
&mut self,
font_ctx: &mut FontContext,
layout_ctx: &mut LayoutContext<TextBrush>,
text: &str,
) {
if self.editor.text() != text {
self.editor.set_text(text);
self.editor.driver(font_ctx, layout_ctx).refresh_layout();
}
}
}
#[derive(Debug, Clone)]
pub struct CanvasData {
pub custom_paint_source_id: u64,
}
impl std::fmt::Debug for SpecialElementData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SpecialElementData::Stylesheet(_) => f.write_str("NodeSpecificData::Stylesheet"),
SpecialElementData::Image(data) => match **data {
ImageData::Raster(_) => f.write_str("NodeSpecificData::Image(Raster)"),
#[cfg(feature = "svg")]
ImageData::Svg(_) => f.write_str("NodeSpecificData::Image(Svg)"),
ImageData::None => f.write_str("NodeSpecificData::Image(None)"),
},
SpecialElementData::Canvas(_) => f.write_str("NodeSpecificData::Canvas"),
SpecialElementData::TableRoot(_) => f.write_str("NodeSpecificData::TableRoot"),
SpecialElementData::TextInput(_) => f.write_str("NodeSpecificData::TextInput"),
SpecialElementData::CheckboxInput(_) => f.write_str("NodeSpecificData::CheckboxInput"),
#[cfg(feature = "file_input")]
SpecialElementData::FileInput(_) => f.write_str("NodeSpecificData::FileInput"),
SpecialElementData::None => f.write_str("NodeSpecificData::None"),
}
}
}
#[derive(Clone)]
pub struct ListItemLayout {
pub marker: Marker,
pub position: ListItemLayoutPosition,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Marker {
Char(char),
String(String),
}
#[derive(Clone)]
pub enum ListItemLayoutPosition {
Inside,
Outside(Box<parley::Layout<TextBrush>>),
}
impl std::fmt::Debug for ListItemLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ListItemLayout - marker {:?}", self.marker)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct TextBrush {
pub id: usize,
}
impl TextBrush {
pub(crate) fn from_id(id: usize) -> Self {
Self { id }
}
}
#[derive(Clone, Default)]
pub struct TextLayout {
pub text: String,
pub content_widths: Option<ContentWidths>,
pub layout: parley::layout::Layout<TextBrush>,
}
impl TextLayout {
pub fn new() -> Self {
Default::default()
}
pub fn content_widths(&mut self) -> ContentWidths {
*self
.content_widths
.get_or_insert_with(|| self.layout.calculate_content_widths())
}
}
impl std::fmt::Debug for TextLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TextLayout")
}
}
#[cfg(feature = "file_input")]
mod file_data {
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
#[derive(Clone, Debug)]
pub struct FileData(pub Vec<PathBuf>);
impl Deref for FileData {
type Target = Vec<PathBuf>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for FileData {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<Vec<PathBuf>> for FileData {
fn from(files: Vec<PathBuf>) -> Self {
Self(files)
}
}
}
#[cfg(feature = "file_input")]
pub use file_data::FileData;