script: Use an implemented pseudo-element to fortype=color ::color-swatch (#37427)

Implement internal pseudo element, which would be resolved as a
"Implemented Pseudo Element" within style computation. This is an
concrete element that would has a primary style after the style
computation, but could match and style resolved like an pseudo element.
Therefore, it would have a different behavior compared to how does
`pseudo`s that `ServoLayoutNode` had. Where they would not have a
concrete element behind it. Note that, due to the nature of these pseudo
elements residing inside a UA widget, these pseudo elements would
therefore not be accessible in JavaScript by default.

This kind of element is required in order to implement the [form control
pseudo element](https://drafts.csswg.org/css-forms-1/#pseudo-elements)
like `::placeholder`, `::color-swatch`, `::field-text`, etc.
 
See [this docs](https://hackmd.io/@ChaKweTiau/BJ3zRdLQlg) for more
details of the implementation.

Then, the implemented pseudo element is utilized to implement style
matching for input `type=text`.

Servo's side of: https://github.com/servo/stylo/pull/212

Testing: No WPT regression.

---------

Signed-off-by: stevennovaryo <steven.novaryo@gmail.com>
This commit is contained in:
Steven Novaryo 2025-07-09 23:36:58 +08:00 committed by GitHub
parent d2ccf419c3
commit 378c4648e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 201 additions and 163 deletions

View file

@ -201,6 +201,22 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
self.as_node().traversal_parent()
}
fn inheritance_parent(&self) -> Option<Self> {
if self.is_pseudo_element() {
// The inheritance parent of an implemented pseudo-element should be the
// originating element, except if `is_element_backed()` is true, then it should
// be the flat tree parent. Note `is_element_backed()` differs from the CSS term.
// At the current time, `is_element_backed()` is always false in Servo.
//
// FIXME: handle the cases of element-backed pseudo-elements.
return self.pseudo_element_originating_element();
}
// FIXME: By default the inheritance parent would be the Self::parent_element
// but probably we should use the flattened tree parent.
self.parent_element()
}
fn is_html_element(&self) -> bool {
ServoLayoutElement::is_html_element(self)
}
@ -355,6 +371,21 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, false)
}
/// Whether this element should match user and content rules.
/// We would like to match rules from the same tree in all cases and optimize computation.
/// UA Widget is an exception since we could have a pseudo element selector inside it.
#[inline]
fn matches_user_and_content_rules(&self) -> bool {
!self.as_node().node.is_in_ua_widget()
}
/// Returns the pseudo-element implemented by this element, if any. In other words,
/// the element will match the specified pseudo element throughout the style computation.
#[inline]
fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
self.as_node().node.implemented_pseudo_element()
}
fn store_children_to_process(&self, n: isize) {
let data = self.get_style_data().unwrap();
data.parallel
@ -585,6 +616,18 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
self.containing_shadow().map(|s| s.host())
}
#[inline]
fn is_pseudo_element(&self) -> bool {
self.implemented_pseudo_element().is_some()
}
#[inline]
fn pseudo_element_originating_element(&self) -> Option<Self> {
debug_assert!(self.is_pseudo_element());
debug_assert!(!self.matches_user_and_content_rules());
self.containing_shadow_host()
}
fn prev_sibling_element(&self) -> Option<Self> {
let mut node = self.as_node();
while let Some(sibling) = node.prev_sibling() {
@ -663,18 +706,6 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
self.element.namespace() == other.element.namespace()
}
fn is_pseudo_element(&self) -> bool {
false
}
fn match_pseudo_element(
&self,
_pseudo: &PseudoElement,
_context: &mut MatchingContext<Self::Impl>,
) -> bool {
false
}
fn match_non_ts_pseudo_class(
&self,
pseudo_class: &NonTSPseudoClass,
@ -733,6 +764,14 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
}
}
fn match_pseudo_element(
&self,
pseudo: &PseudoElement,
_context: &mut MatchingContext<Self::Impl>,
) -> bool {
self.implemented_pseudo_element() == Some(*pseudo)
}
#[inline]
fn is_link(&self) -> bool {
match self.as_node().script_type_id() {
@ -934,21 +973,33 @@ impl ::selectors::Element for ServoThreadSafeLayoutElement<'_> {
::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) })
}
fn is_pseudo_element(&self) -> bool {
false
}
fn parent_element(&self) -> Option<Self> {
warn!("ServoThreadSafeLayoutElement::parent_element called");
None
}
#[inline]
fn parent_node_is_shadow_root(&self) -> bool {
false
self.element.parent_node_is_shadow_root()
}
#[inline]
fn containing_shadow_host(&self) -> Option<Self> {
None
self.element
.containing_shadow_host()
.and_then(|element| element.as_node().to_threadsafe().as_element())
}
#[inline]
fn is_pseudo_element(&self) -> bool {
self.element.is_pseudo_element()
}
#[inline]
fn pseudo_element_originating_element(&self) -> Option<Self> {
self.element
.pseudo_element_originating_element()
.and_then(|element| element.as_node().to_threadsafe().as_element())
}
// Skips non-element nodes
@ -993,14 +1044,6 @@ impl ::selectors::Element for ServoThreadSafeLayoutElement<'_> {
self.element.namespace() == other.element.namespace()
}
fn match_pseudo_element(
&self,
_pseudo: &PseudoElement,
_context: &mut MatchingContext<Self::Impl>,
) -> bool {
false
}
fn attr_matches(
&self,
ns: &NamespaceConstraint<&style::Namespace>,
@ -1030,6 +1073,14 @@ impl ::selectors::Element for ServoThreadSafeLayoutElement<'_> {
false
}
fn match_pseudo_element(
&self,
pseudo: &PseudoElement,
context: &mut MatchingContext<Self::Impl>,
) -> bool {
self.element.match_pseudo_element(pseudo, context)
}
fn is_link(&self) -> bool {
warn!("ServoThreadSafeLayoutElement::is_link called");
false