diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index ab814bc9108..4a0aec45024 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -752,10 +752,9 @@ impl Node { } pub(crate) fn ranges_is_empty(&self) -> bool { - match self.rare_data().as_ref() { - Some(data) => data.ranges.is_empty(), - None => false, - } + self.rare_data() + .as_ref() + .is_none_or(|data| data.ranges.is_empty()) } #[inline] @@ -2665,7 +2664,9 @@ impl Node { fn remove(node: &Node, parent: &Node, suppress_observers: SuppressObserver, can_gc: CanGc) { parent.owner_doc().add_script_and_layout_blocker(); - // Step 2. + // Step 1. Let parent be node’s parent. + // Step 2. Assert: parent is non-null. + // NOTE: We get parent as an argument instead assert!( node.GetParentNode() .is_some_and(|node_parent| &*node_parent == parent) @@ -2677,11 +2678,21 @@ impl Node { if parent.ranges_is_empty() { None } else { - // Step 1. + // Step 1. Let parent be node’s parent. + // Step 2. Assert: parent is not null. + // NOTE: We already have the parent. + + // Step 3. Let index be node’s index. let index = node.index(); - // Steps 2-3 are handled in Node::unbind_from_tree. - // Steps 4-5. + + // Steps 4-5 are handled in Node::unbind_from_tree. + + // Step 6. For each live range whose start node is parent and start offset is greater than index, + // decrease its start offset by 1. + // Step 7. For each live range whose end node is parent and end offset is greater than index, + // decrease its end offset by 1. parent.ranges().decrease_above(parent, index, 1); + // Parent had ranges, we needed the index, let's keep track of // it to avoid computing it for other ranges when calling // unbind_from_tree recursively. @@ -3925,7 +3936,12 @@ impl VirtualMethods for Node { /// fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) { self.super_type().unwrap().unbind_from_tree(context, can_gc); - if !self.ranges_is_empty() { + + // Ranges should only drain to the parent from inclusive non-shadow + // including descendants. If we're in a shadow tree at this point then the + // unbind operation happened further up in the tree and we should not + // drain any ranges. + if !self.is_in_a_shadow_tree() && !self.ranges_is_empty() { self.ranges().drain_to_parent(context, self); } } diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs index 4bf3b7fb72a..a47b060e0f2 100644 --- a/components/script/dom/range.rs +++ b/components/script/dom/range.rs @@ -344,6 +344,58 @@ impl Range { .chain(iter::once(end_clone)) .flat_map(move |node| node.content_boxes(can_gc)) } + + /// + #[allow(clippy::neg_cmp_op_on_partial_ord)] + fn set_the_start_or_end( + &self, + node: &Node, + offset: u32, + start_or_end: StartOrEnd, + ) -> ErrorResult { + // Step 1. If node is a doctype, then throw an "InvalidNodeTypeError" DOMException. + if node.is_doctype() { + return Err(Error::InvalidNodeType); + } + + // Step 2. If offset is greater than node’s length, then throw an "IndexSizeError" DOMException. + if offset > node.len() { + return Err(Error::IndexSize); + } + + // Step 3. Let bp be the boundary point (node, offset). + // NOTE: We don't need this part. + + match start_or_end { + // If these steps were invoked as "set the start" + StartOrEnd::Start => { + // Step 4.1 If range’s root is not equal to node’s root, or if bp is after the range’s end, + // set range’s end to bp. + // Step 4.2 Set range’s start to bp. + self.set_start(node, offset); + if !(self.start() <= self.end()) { + self.set_end(node, offset); + } + }, + // If these steps were invoked as "set the end" + StartOrEnd::End => { + // Step 4.1 If range’s root is not equal to node’s root, or if bp is before the range’s start, + // set range’s start to bp. + // Step 4.2 Set range’s end to bp. + self.set_end(node, offset); + if !(self.end() >= self.start()) { + self.set_start(node, offset); + } + }, + } + + Ok(()) + } +} + +enum StartOrEnd { + Start, + End, } impl RangeMethods for Range { @@ -365,43 +417,13 @@ impl RangeMethods for Range { } /// - #[allow(clippy::neg_cmp_op_on_partial_ord)] fn SetStart(&self, node: &Node, offset: u32) -> ErrorResult { - if node.is_doctype() { - // Step 1. - Err(Error::InvalidNodeType) - } else if offset > node.len() { - // Step 2. - Err(Error::IndexSize) - } else { - // Step 3. - self.set_start(node, offset); - if !(self.start() <= self.end()) { - // Step 4. - self.set_end(node, offset); - } - Ok(()) - } + self.set_the_start_or_end(node, offset, StartOrEnd::Start) } /// - #[allow(clippy::neg_cmp_op_on_partial_ord)] fn SetEnd(&self, node: &Node, offset: u32) -> ErrorResult { - if node.is_doctype() { - // Step 1. - Err(Error::InvalidNodeType) - } else if offset > node.len() { - // Step 2. - Err(Error::IndexSize) - } else { - // Step 3. - self.set_end(node, offset); - if !(self.end() >= self.start()) { - // Step 4. - self.set_start(node, offset); - } - Ok(()) - } + self.set_the_start_or_end(node, offset, StartOrEnd::End) } /// @@ -1204,6 +1226,7 @@ impl WeakRangeVec { } /// Used for steps 2-3. when removing a node. + /// /// pub(crate) fn drain_to_parent(&self, context: &UnbindContext, child: &Node) { if self.is_empty() { diff --git a/tests/wpt/meta/dom/ranges/Range-in-shadow-after-the-shadow-removed.html.ini b/tests/wpt/meta/dom/ranges/Range-in-shadow-after-the-shadow-removed.html.ini deleted file mode 100644 index b2003b5c788..00000000000 --- a/tests/wpt/meta/dom/ranges/Range-in-shadow-after-the-shadow-removed.html.ini +++ /dev/null @@ -1,14 +0,0 @@ -[Range-in-shadow-after-the-shadow-removed.html?mode=closed] - [Range in shadow should stay in the shadow after the host is removed] - expected: FAIL - - [Range in shadow should stay in the shadow after the host parent is removed] - expected: FAIL - - -[Range-in-shadow-after-the-shadow-removed.html?mode=open] - [Range in shadow should stay in the shadow after the host is removed] - expected: FAIL - - [Range in shadow should stay in the shadow after the host parent is removed] - expected: FAIL