Files
servo/components/script/dom/abstractrange.rs
Tim van der Lippe c1c5e9373c script: Complete basic implementation of delete command (#43082)
While we are not there yet, this is the majority of the implementation
of the delete command.

What's missing:
1. Handling of style attributes (most of the test failures)
2. Handling of lists

Since these are quite complicated on their own, deferring that to their
own PR.

Also added more assertions regarding parent nodes, which should always
exist in the spec. By adding those, discovered that the logic for
`is_visible` was wrong where its display value wasn't checked for
`"none"`.

Part of #25005

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
2026-03-11 06:06:51 +00:00

189 lines
5.6 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 std::cell::Cell;
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use deny_public_fields::DenyPublicFields;
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::AbstractRangeBinding::AbstractRangeMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::{DomRoot, MutDom};
use crate::dom::document::Document;
use crate::dom::node::{Node, ShadowIncluding};
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct AbstractRange {
reflector_: Reflector,
start: BoundaryPoint,
end: BoundaryPoint,
}
impl AbstractRange {
pub(crate) fn new_inherited(
start_container: &Node,
start_offset: u32,
end_container: &Node,
end_offset: u32,
) -> AbstractRange {
AbstractRange {
reflector_: Reflector::new(),
start: BoundaryPoint::new(start_container, start_offset),
end: BoundaryPoint::new(end_container, end_offset),
}
}
pub(crate) fn new(
document: &Document,
start_container: &Node,
start_offset: u32,
end_container: &Node,
end_offset: u32,
can_gc: CanGc,
) -> DomRoot<AbstractRange> {
reflect_dom_object(
Box::new(AbstractRange::new_inherited(
start_container,
start_offset,
end_container,
end_offset,
)),
document.window(),
can_gc,
)
}
pub(crate) fn start(&self) -> &BoundaryPoint {
&self.start
}
pub(crate) fn end(&self) -> &BoundaryPoint {
&self.end
}
}
impl AbstractRangeMethods<crate::DomTypeHolder> for AbstractRange {
/// <https://dom.spec.whatwg.org/#dom-range-startcontainer>
fn StartContainer(&self) -> DomRoot<Node> {
self.start.node.get()
}
/// <https://dom.spec.whatwg.org/#dom-range-startoffset>
fn StartOffset(&self) -> u32 {
self.start.offset.get()
}
/// <https://dom.spec.whatwg.org/#dom-range-endcontainer>
fn EndContainer(&self) -> DomRoot<Node> {
self.end.node.get()
}
/// <https://dom.spec.whatwg.org/#dom-range-endoffset>
fn EndOffset(&self) -> u32 {
self.end.offset.get()
}
/// <https://dom.spec.whatwg.org/#dom-range-collapsed>
fn Collapsed(&self) -> bool {
// > The collapsed getter steps are to return true if this is collapsed; otherwise false.
self.start == self.end
}
}
/// <https://dom.spec.whatwg.org/#concept-range-bp>
#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct BoundaryPoint {
/// <https://dom.spec.whatwg.org/#boundary-point-node>
node: MutDom<Node>,
/// <https://dom.spec.whatwg.org/#concept-range-bp-offset>
offset: Cell<u32>,
}
impl BoundaryPoint {
fn new(node: &Node, offset: u32) -> BoundaryPoint {
debug_assert!(!node.is_doctype());
BoundaryPoint {
node: MutDom::new(node),
offset: Cell::new(offset),
}
}
pub(crate) fn set(&self, node: &Node, offset: u32) {
self.node.set(node);
self.set_offset(offset);
}
pub(crate) fn set_offset(&self, offset: u32) {
self.offset.set(offset);
}
pub(crate) fn node(&self) -> &MutDom<Node> {
&self.node
}
}
impl PartialOrd for BoundaryPoint {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
bp_position(
&self.node.get(),
self.offset.get(),
&other.node.get(),
other.offset.get(),
)
}
}
/// <https://dom.spec.whatwg.org/#range-collapsed>
impl PartialEq for BoundaryPoint {
fn eq(&self, other: &Self) -> bool {
// > A range is collapsed if its start node is its end node and its start offset is its end offset.
self.node.get() == other.node.get() && self.offset.get() == other.offset.get()
}
}
/// <https://dom.spec.whatwg.org/#concept-range-bp-position>
pub(crate) fn bp_position(
a_node: &Node,
a_offset: u32,
b_node: &Node,
b_offset: u32,
) -> Option<Ordering> {
if std::ptr::eq(a_node, b_node) {
// Step 1.
return Some(a_offset.cmp(&b_offset));
}
let position = b_node.CompareDocumentPosition(a_node);
if position & NodeConstants::DOCUMENT_POSITION_DISCONNECTED != 0 {
// No order is defined for nodes not in the same tree.
None
} else if position & NodeConstants::DOCUMENT_POSITION_FOLLOWING != 0 {
// Step 2.
match bp_position(b_node, b_offset, a_node, a_offset).unwrap() {
Ordering::Less => Some(Ordering::Greater),
Ordering::Greater => Some(Ordering::Less),
Ordering::Equal => unreachable!(),
}
} else if position & NodeConstants::DOCUMENT_POSITION_CONTAINS != 0 {
// Step 3-1, 3-2.
let mut b_ancestors = b_node.inclusive_ancestors(ShadowIncluding::No);
let child = b_ancestors
.find(|child| &*child.GetParentNode().unwrap() == a_node)
.unwrap();
// Step 3-3.
if child.index() < a_offset {
Some(Ordering::Greater)
} else {
// Step 4.
Some(Ordering::Less)
}
} else {
// Step 4.
Some(Ordering::Less)
}
}