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

@ -48,7 +48,7 @@ use style::attr::AttrValue;
use style::context::QuirksMode;
use style::dom::OpaqueNode;
use style::properties::ComputedValues;
use style::selector_parser::{SelectorImpl, SelectorParser};
use style::selector_parser::{PseudoElement, SelectorImpl, SelectorParser};
use style::stylesheets::{Stylesheet, UrlExtraData};
use uuid::Uuid;
use xml5ever::{local_name, serialize as xml_serialize};
@ -233,6 +233,10 @@ bitflags! {
/// Whether this node has a weird parser insertion mode. i.e whether setting innerHTML
/// needs extra work or not
const HAS_WEIRD_PARSER_INSERTION_MODE = 1 << 11;
/// Whether this node resides in UA shadow DOM. Element within UA Shadow DOM
/// will have a different style computation behavior
const IS_IN_UA_WIDGET = 1 << 12;
}
}
@ -291,6 +295,7 @@ impl Node {
let parent_is_in_a_document_tree = self.is_in_a_document_tree();
let parent_in_shadow_tree = self.is_in_a_shadow_tree();
let parent_is_connected = self.is_connected();
let parent_is_in_ua_widget = self.is_in_ua_widget();
for node in new_child.traverse_preorder(ShadowIncluding::No) {
if parent_in_shadow_tree {
@ -305,6 +310,7 @@ impl Node {
);
node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, parent_in_shadow_tree);
node.set_flag(NodeFlags::IS_CONNECTED, parent_is_connected);
node.set_flag(NodeFlags::IS_IN_UA_WIDGET, parent_is_in_ua_widget);
// Out-of-document elements never have the descendants flag set.
debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS));
@ -696,6 +702,14 @@ impl Node {
self.flags.get().contains(NodeFlags::IS_CONNECTED)
}
pub(crate) fn set_in_ua_widget(&self, in_ua_widget: bool) {
self.set_flag(NodeFlags::IS_IN_UA_WIDGET, in_ua_widget)
}
pub(crate) fn is_in_ua_widget(&self) -> bool {
self.flags.get().contains(NodeFlags::IS_IN_UA_WIDGET)
}
/// Returns the type ID of this node.
pub(crate) fn type_id(&self) -> NodeTypeId {
match *self.eventtarget.type_id() {
@ -1546,6 +1560,20 @@ impl Node {
next_node: move |n| n.parent_in_flat_tree(),
}
}
/// We are marking this as an implemented pseudo element.
pub(crate) fn set_implemented_pseudo_element(&self, pseudo_element: PseudoElement) {
// Implemented pseudo element should exist only in the UA shadow DOM.
debug_assert!(self.is_in_ua_widget());
self.ensure_rare_data().implemented_pseudo_element = Some(pseudo_element);
}
pub(crate) fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
self.rare_data
.borrow()
.as_ref()
.and_then(|rare_data| rare_data.implemented_pseudo_element)
}
}
/// Iterate through `nodes` until we find a `Node` that is not in `not_in`
@ -1630,6 +1658,8 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
fn iframe_browsing_context_id(self) -> Option<BrowsingContextId>;
fn iframe_pipeline_id(self) -> Option<PipelineId>;
fn opaque(self) -> OpaqueNode;
fn implemented_pseudo_element(&self) -> Option<PseudoElement>;
fn is_in_ua_widget(&self) -> bool;
}
impl<'dom> LayoutDom<'dom, Node> {
@ -1880,6 +1910,14 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
fn opaque(self) -> OpaqueNode {
unsafe { OpaqueNode(self.get_jsobject() as usize) }
}
fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
self.unsafe_get().implemented_pseudo_element()
}
fn is_in_ua_widget(&self) -> bool {
self.unsafe_get().is_in_ua_widget()
}
}
//