script: Correctly handle modifiable elements in contenteditable (#44250)

First of all, the effective command value was wrong, since there is no
relevant CSS property for the underline command. Instead, we should
directly use the text-decoration property. This then allows us to
implement reordering of modifiable elements.

We also need to "change the element to a span", which is quite annoying
to do. Instead, it mimics what would have happened by moving children
and copying attributes.

There are some regressions, but overall this is another big step towards
the right track. The regressions look related to tricky edge cases that
I am not even sure other browsers handle.

Part of #25005

Testing: WPT

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe
2026-04-16 22:50:06 +02:00
committed by GitHub
parent 3ffecfb60c
commit d096560b51
8 changed files with 265 additions and 411 deletions

View File

@@ -45,7 +45,6 @@ use servo_config::pref;
use servo_url::ServoUrl;
use smallvec::SmallVec;
use style::Atom;
use style::attr::AttrValue;
use style::context::QuirksMode;
use style::dom::OpaqueNode;
use style::dom_apis::{QueryAll, QueryFirst};
@@ -100,9 +99,7 @@ use crate::dom::customelementregistry::{
use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::documenttype::DocumentType;
use crate::dom::element::{
AttributeMutationReason, CustomElementCreationMode, Element, ElementCreator,
};
use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator};
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventFlags};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
@@ -3425,27 +3422,6 @@ impl Node {
Some(index)
}
/// Ensure that for styles, we clone the already-parsed property declaration block.
/// This does two things:
/// 1. it uses the same fast-path as CSSStyleDeclaration
/// 2. it also avoids the CSP checks when cloning (it shouldn't run any when cloning
/// existing valid attributes)
fn compute_attribute_value_with_style_fast_path(attr: &Dom<Attr>, elem: &Element) -> AttrValue {
if *attr.local_name() == local_name!("style") {
if let Some(ref pdb) = *elem.style_attribute().borrow() {
let document = elem.owner_document();
let shared_lock = document.style_shared_lock();
let new_pdb = pdb.read_with(&shared_lock.read()).clone();
return AttrValue::Declaration(
(**attr.value()).to_owned(),
ServoArc::new(shared_lock.wrap(new_pdb)),
);
}
}
attr.value().clone()
}
/// <https://dom.spec.whatwg.org/#concept-node-clone>
pub(crate) fn clone(
cx: &mut JSContext,
@@ -3594,21 +3570,7 @@ impl Node {
let copy_elem = copy.downcast::<Element>().unwrap();
// Step 2.5. For each attribute of nodes attribute list:
for attr in node_elem.attrs().iter() {
// Step 2.5.1. Let copyAttribute be the result of cloning a single node given attribute, document, and null.
let new_value =
Node::compute_attribute_value_with_style_fast_path(attr, node_elem);
// Step 2.5.2. Append copyAttribute to copy.
copy_elem.push_new_attribute(
attr.local_name().clone(),
new_value,
attr.name().clone(),
attr.namespace().clone(),
attr.prefix().cloned(),
AttributeMutationReason::ByCloning,
CanGc::from_cx(cx),
);
}
node_elem.copy_all_attributes_to_other_element(cx, copy_elem);
},
_ => (),
}