mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
While debugging test failures, I discovered that I had reversed the `is_allowed_child` arguments. That made a bunch more tests pass, but then also started to fail tests as we weren't clearing the previous value and computing loose equivalence. Therefore, this PR fixes the reversal and implements the relevant parts of some algorithms as to not regress too much. There are two new failures related to how integers should be parsed, but tackling that separately. Part of #25005 Testing: WPT Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
352 lines
13 KiB
Rust
352 lines
13 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use script_bindings::inheritance::Castable;
|
|
use style::properties::PropertyDeclarationId;
|
|
use style::properties::generated::LonghandId;
|
|
use style_traits::ToCss;
|
|
|
|
use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
|
|
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
|
|
use crate::dom::bindings::codegen::Bindings::HTMLFontElementBinding::HTMLFontElementMethods;
|
|
use crate::dom::bindings::str::DOMString;
|
|
use crate::dom::document::Document;
|
|
use crate::dom::element::Element;
|
|
use crate::dom::execcommand::commands::defaultparagraphseparator::execute_default_paragraph_separator_command;
|
|
use crate::dom::execcommand::commands::delete::execute_delete_command;
|
|
use crate::dom::execcommand::commands::fontsize::{
|
|
execute_fontsize_command, font_size_loosely_equivalent, value_for_fontsize_command,
|
|
};
|
|
use crate::dom::execcommand::commands::stylewithcss::execute_style_with_css_command;
|
|
use crate::dom::html::htmlelement::HTMLElement;
|
|
use crate::dom::html::htmlfontelement::HTMLFontElement;
|
|
use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
|
|
use crate::dom::selection::Selection;
|
|
use crate::script_runtime::CanGc;
|
|
|
|
#[derive(Default, Clone, Copy, MallocSizeOf)]
|
|
pub(crate) enum DefaultSingleLineContainerName {
|
|
#[default]
|
|
Div,
|
|
Paragraph,
|
|
}
|
|
|
|
impl From<DefaultSingleLineContainerName> for DOMString {
|
|
fn from(default_single_line_container_name: DefaultSingleLineContainerName) -> Self {
|
|
match default_single_line_container_name {
|
|
DefaultSingleLineContainerName::Div => DOMString::from("div"),
|
|
DefaultSingleLineContainerName::Paragraph => DOMString::from("p"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <https://w3c.github.io/editing/docs/execCommand/#relevant-css-property>
|
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
#[expect(unused)] // TODO(25005): implement all commands
|
|
pub(crate) enum CssPropertyName {
|
|
BackgroundColor,
|
|
FontSize,
|
|
FontWeight,
|
|
FontStyle,
|
|
TextDecorationLine,
|
|
}
|
|
|
|
impl CssPropertyName {
|
|
fn resolved_value_for_node(&self, element: &Element) -> Option<DOMString> {
|
|
let style = element.style()?;
|
|
|
|
Some(
|
|
match self {
|
|
CssPropertyName::BackgroundColor => style.clone_background_color().to_css_string(),
|
|
CssPropertyName::FontSize => {
|
|
// Font size is special, in that it can't use the resolved styles to compute
|
|
// values. That's because it is influenced by other factors as well, and it
|
|
// should also take into account size attributes of font elements.
|
|
//
|
|
// Therefore, we do a manual traversal up the chain to mimic what style
|
|
// resolution would have done. This also allows us to later check for
|
|
// loose equivalence for font elements, since we would return the size as an
|
|
// integer, without a size indicator (e.g. `px`).
|
|
return element
|
|
.upcast::<Node>()
|
|
.inclusive_ancestors(ShadowIncluding::No)
|
|
.find_map(|ancestor| {
|
|
if let Some(ancestor_font) = ancestor.downcast::<HTMLFontElement>() {
|
|
Some(ancestor_font.Size())
|
|
} else {
|
|
self.value_set_for_style(ancestor.downcast::<Element>()?)
|
|
}
|
|
});
|
|
},
|
|
CssPropertyName::FontWeight => style.clone_font_weight().to_css_string(),
|
|
CssPropertyName::FontStyle => style.clone_font_style().to_css_string(),
|
|
CssPropertyName::TextDecorationLine => {
|
|
style.clone_text_decoration_line().to_css_string()
|
|
},
|
|
}
|
|
.into(),
|
|
)
|
|
}
|
|
|
|
/// Retrieves a respective css longhand value from the style declarations of an
|
|
/// element. Note that this is different than the computed values, since this is
|
|
/// only relevant when the author specified rules on the specific element.
|
|
pub(crate) fn value_set_for_style(&self, element: &Element) -> Option<DOMString> {
|
|
let style_attribute = element.style_attribute().borrow();
|
|
let declarations = style_attribute.as_ref()?;
|
|
let document = element.owner_document();
|
|
let shared_lock = document.style_shared_lock();
|
|
let read_lock = shared_lock.read();
|
|
let style = declarations.read_with(&read_lock);
|
|
|
|
let longhand_id = match self {
|
|
CssPropertyName::BackgroundColor => LonghandId::BackgroundColor,
|
|
CssPropertyName::FontSize => LonghandId::FontSize,
|
|
CssPropertyName::FontWeight => LonghandId::FontWeight,
|
|
CssPropertyName::FontStyle => LonghandId::FontStyle,
|
|
CssPropertyName::TextDecorationLine => LonghandId::TextDecorationLine,
|
|
};
|
|
style
|
|
.get(PropertyDeclarationId::Longhand(longhand_id))
|
|
.and_then(|value| {
|
|
let mut dest = String::new();
|
|
value.0.to_css(&mut dest).ok()?;
|
|
Some(dest.into())
|
|
})
|
|
}
|
|
|
|
fn property_name(&self) -> DOMString {
|
|
match self {
|
|
CssPropertyName::BackgroundColor => "background-color",
|
|
CssPropertyName::FontSize => "font-size",
|
|
CssPropertyName::FontWeight => "font-weight",
|
|
CssPropertyName::FontStyle => "font-style",
|
|
CssPropertyName::TextDecorationLine => "text-decoration-line",
|
|
}
|
|
.into()
|
|
}
|
|
|
|
pub(crate) fn set_for_element(
|
|
&self,
|
|
cx: &mut js::context::JSContext,
|
|
element: &HTMLElement,
|
|
new_value: DOMString,
|
|
) {
|
|
let style = element.Style(CanGc::from_cx(cx));
|
|
|
|
let _ = style.SetProperty(cx, self.property_name(), new_value, "".into());
|
|
}
|
|
|
|
pub(crate) fn remove_from_element(
|
|
&self,
|
|
cx: &mut js::context::JSContext,
|
|
element: &HTMLElement,
|
|
) {
|
|
let _ = element
|
|
.Style(CanGc::from_cx(cx))
|
|
.RemoveProperty(cx, self.property_name());
|
|
}
|
|
|
|
pub(crate) fn value_for_element(
|
|
&self,
|
|
cx: &mut js::context::JSContext,
|
|
element: &HTMLElement,
|
|
) -> DOMString {
|
|
element
|
|
.Style(CanGc::from_cx(cx))
|
|
.GetPropertyValue(self.property_name())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Eq, Hash, MallocSizeOf, PartialEq)]
|
|
#[expect(unused)] // TODO(25005): implement all commands
|
|
pub(crate) enum CommandName {
|
|
BackColor,
|
|
Bold,
|
|
Copy,
|
|
CreateLink,
|
|
Cut,
|
|
DefaultParagraphSeparator,
|
|
Delete,
|
|
FontName,
|
|
FontSize,
|
|
ForeColor,
|
|
FormatBlock,
|
|
ForwardDelete,
|
|
HiliteColor,
|
|
Indent,
|
|
InsertHorizontalRule,
|
|
InsertHtml,
|
|
InsertLineBreak,
|
|
InsertOrderedList,
|
|
InsertParagraph,
|
|
InsertText,
|
|
InsertUnorderedList,
|
|
Italic,
|
|
JustifyCenter,
|
|
JustifyFull,
|
|
JustifyLeft,
|
|
JustifyRight,
|
|
Outdent,
|
|
Paste,
|
|
Redo,
|
|
SelectAll,
|
|
Strikethrough,
|
|
StyleWithCss,
|
|
Subscript,
|
|
Superscript,
|
|
Underline,
|
|
Undo,
|
|
Unlink,
|
|
Usecss,
|
|
}
|
|
|
|
impl CommandName {
|
|
/// <https://w3c.github.io/editing/docs/execCommand/#indeterminate>
|
|
pub(crate) fn is_indeterminate(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// <https://w3c.github.io/editing/docs/execCommand/#state>
|
|
pub(crate) fn current_state(&self, document: &Document) -> Option<bool> {
|
|
Some(match self {
|
|
CommandName::StyleWithCss => {
|
|
// https://w3c.github.io/editing/docs/execCommand/#the-stylewithcss-command
|
|
// > True if the CSS styling flag is true, otherwise false.
|
|
document.css_styling_flag()
|
|
},
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
/// <https://w3c.github.io/editing/docs/execCommand/#value>
|
|
pub(crate) fn current_value(
|
|
&self,
|
|
cx: &mut js::context::JSContext,
|
|
document: &Document,
|
|
) -> Option<DOMString> {
|
|
Some(match self {
|
|
CommandName::DefaultParagraphSeparator => {
|
|
// https://w3c.github.io/editing/docs/execCommand/#the-defaultparagraphseparator-command
|
|
// > Return the context object's default single-line container name.
|
|
document.default_single_line_container_name().into()
|
|
},
|
|
CommandName::FontSize => value_for_fontsize_command(cx, document)?,
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
/// <https://w3c.github.io/editing/docs/execCommand/#equivalent-values>
|
|
pub(crate) fn are_equivalent_values(
|
|
&self,
|
|
first: Option<&DOMString>,
|
|
second: Option<&DOMString>,
|
|
) -> bool {
|
|
match (first, second) {
|
|
// > Two quantities are equivalent values for a command if either both are null,
|
|
(None, None) => true,
|
|
(Some(first_str), Some(second_str)) => {
|
|
// > or both are strings and the command defines equivalent values and they match the definition.
|
|
match self {
|
|
CommandName::Bold => {
|
|
// https://w3c.github.io/editing/docs/execCommand/#the-bold-command
|
|
// > Either the two strings are equal, or one is "bold" and the other is "700",
|
|
// > or one is "normal" and the other is "400".
|
|
first_str == second_str ||
|
|
matches!(
|
|
(first_str.str().as_ref(), second_str.str().as_ref()),
|
|
("bold", "700") |
|
|
("700", "bold") |
|
|
("normal", "400") |
|
|
("400", "normal")
|
|
)
|
|
},
|
|
// > or both are strings and they're equal and the command does not define any equivalent values,
|
|
_ => first_str == second_str,
|
|
}
|
|
},
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// <https://w3c.github.io/editing/docs/execCommand/#loosely-equivalent-values>
|
|
pub(crate) fn are_loosely_equivalent_values(
|
|
&self,
|
|
first: Option<&DOMString>,
|
|
second: Option<&DOMString>,
|
|
) -> bool {
|
|
// > Two quantities are loosely equivalent values for a command if either they are equivalent values for the command,
|
|
if self.are_equivalent_values(first, second) {
|
|
return true;
|
|
}
|
|
// > or if the command is the fontSize command;
|
|
// > one of the quantities is one of "x-small", "small", "medium", "large", "x-large", "xx-large", or "xxx-large";
|
|
// > and the other quantity is the resolved value of "font-size" on a font element whose size attribute
|
|
// > has the corresponding value set ("1" through "7" respectively).
|
|
if let (CommandName::FontSize, Some(first), Some(second)) = (self, first, second) {
|
|
font_size_loosely_equivalent(first, second)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// <https://w3c.github.io/editing/docs/execCommand/#relevant-css-property>
|
|
pub(crate) fn relevant_css_property(&self) -> Option<CssPropertyName> {
|
|
// > This is defined for certain inline formatting commands, and is used in algorithms specific to those commands.
|
|
// > It is an implementation detail, and is not exposed to authors.
|
|
Some(match self {
|
|
CommandName::FontSize => CssPropertyName::FontSize,
|
|
CommandName::Bold => CssPropertyName::FontWeight,
|
|
CommandName::Italic => CssPropertyName::FontStyle,
|
|
// > If a command does not have a relevant CSS property specified, it defaults to null.
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn resolved_value_for_node(&self, element: &Element) -> Option<DOMString> {
|
|
let property = self.relevant_css_property()?;
|
|
property.resolved_value_for_node(element)
|
|
}
|
|
|
|
pub(crate) fn is_enabled_in_plaintext_only_state(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
CommandName::Copy |
|
|
CommandName::Cut |
|
|
CommandName::DefaultParagraphSeparator |
|
|
CommandName::FormatBlock |
|
|
CommandName::ForwardDelete |
|
|
CommandName::InsertHtml |
|
|
CommandName::InsertLineBreak |
|
|
CommandName::InsertParagraph |
|
|
CommandName::InsertText |
|
|
CommandName::Paste |
|
|
CommandName::Redo |
|
|
CommandName::StyleWithCss |
|
|
CommandName::Undo |
|
|
CommandName::Usecss |
|
|
CommandName::Delete
|
|
)
|
|
}
|
|
|
|
/// <https://w3c.github.io/editing/docs/execCommand/#action>
|
|
pub(crate) fn execute(
|
|
&self,
|
|
cx: &mut js::context::JSContext,
|
|
document: &Document,
|
|
selection: &Selection,
|
|
value: DOMString,
|
|
) -> bool {
|
|
match self {
|
|
CommandName::DefaultParagraphSeparator => {
|
|
execute_default_paragraph_separator_command(document, value)
|
|
},
|
|
CommandName::Delete => execute_delete_command(cx, document, selection),
|
|
CommandName::FontSize => execute_fontsize_command(cx, document, selection, value),
|
|
CommandName::StyleWithCss => execute_style_with_css_command(document, value),
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|