mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Auto merge of #8506 - nox:finish-ranges, r=dzbarsky
Properly propagate changes when range or trees are mutated Does the same thing as #6817, but storing Range instances directly in their start and end containers. Cc @dzbarsky <!-- Reviewable:start --> [<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/8506) <!-- Reviewable:end -->
This commit is contained in:
commit
89ab368258
20 changed files with 669 additions and 4708 deletions
|
@ -33,7 +33,6 @@ use dom::bindings::trace::JSTraceable;
|
|||
use dom::bindings::trace::RootedVec;
|
||||
use dom::bindings::xmlname::namespace_from_domstring;
|
||||
use dom::characterdata::CharacterData;
|
||||
use dom::comment::Comment;
|
||||
use dom::document::{Document, DocumentSource, IsHTMLDocument};
|
||||
use dom::documentfragment::DocumentFragment;
|
||||
use dom::documenttype::DocumentType;
|
||||
|
@ -43,6 +42,7 @@ use dom::htmlcollection::HTMLCollection;
|
|||
use dom::htmlelement::HTMLElement;
|
||||
use dom::nodelist::NodeList;
|
||||
use dom::processinginstruction::ProcessingInstruction;
|
||||
use dom::range::WeakRangeVec;
|
||||
use dom::text::Text;
|
||||
use dom::virtualmethods::{VirtualMethods, vtable_for};
|
||||
use dom::window::Window;
|
||||
|
@ -109,6 +109,12 @@ pub struct Node {
|
|||
/// The maximum version of any inclusive descendant of this node.
|
||||
inclusive_descendants_version: Cell<u64>,
|
||||
|
||||
/// A vector of weak references to Range instances of which the start
|
||||
/// or end containers are this node. No range should ever be found
|
||||
/// twice in this vector, even if both the start and end containers
|
||||
/// are this node.
|
||||
ranges: WeakRangeVec,
|
||||
|
||||
/// Layout information. Only the layout task may touch this data.
|
||||
///
|
||||
/// Must be sent back to the layout task to be destroyed when this
|
||||
|
@ -283,6 +289,7 @@ impl Node {
|
|||
}
|
||||
|
||||
new_child.parent_node.set(Some(self));
|
||||
self.children_count.set(self.children_count.get() + 1);
|
||||
|
||||
let parent_in_doc = self.is_in_doc();
|
||||
for node in new_child.traverse_preorder() {
|
||||
|
@ -296,7 +303,7 @@ impl Node {
|
|||
/// Removes the given child from this node's list of children.
|
||||
///
|
||||
/// Fails unless `child` is a child of this node.
|
||||
fn remove_child(&self, child: &Node) {
|
||||
fn remove_child(&self, child: &Node, cached_index: Option<u32>) {
|
||||
assert!(child.parent_node.get().r() == Some(self));
|
||||
let prev_sibling = child.GetPreviousSibling();
|
||||
match prev_sibling {
|
||||
|
@ -317,14 +324,16 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
let context = UnbindContext::new(self, prev_sibling.r(), cached_index);
|
||||
|
||||
child.prev_sibling.set(None);
|
||||
child.next_sibling.set(None);
|
||||
child.parent_node.set(None);
|
||||
self.children_count.set(self.children_count.get() - 1);
|
||||
|
||||
let parent_in_doc = self.is_in_doc();
|
||||
for node in child.traverse_preorder() {
|
||||
node.set_flag(IS_IN_DOC, false);
|
||||
vtable_for(&&*node).unbind_from_tree(parent_in_doc);
|
||||
vtable_for(&&*node).unbind_from_tree(&context);
|
||||
node.layout_data.dispose(&node);
|
||||
}
|
||||
|
||||
|
@ -433,6 +442,10 @@ impl Node {
|
|||
self.children_count.get()
|
||||
}
|
||||
|
||||
pub fn ranges(&self) -> &WeakRangeVec {
|
||||
&self.ranges
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_doctype(&self) -> bool {
|
||||
self.type_id() == NodeTypeId::DocumentType
|
||||
|
@ -1307,6 +1320,7 @@ impl Node {
|
|||
children_count: Cell::new(0u32),
|
||||
flags: Cell::new(flags),
|
||||
inclusive_descendants_version: Cell::new(0),
|
||||
ranges: WeakRangeVec::new(),
|
||||
|
||||
layout_data: LayoutDataRef::new(),
|
||||
|
||||
|
@ -1481,7 +1495,20 @@ impl Node {
|
|||
debug_assert!(&*node.owner_doc() == &*parent.owner_doc());
|
||||
debug_assert!(child.map_or(true, |child| Some(parent) == child.GetParentNode().r()));
|
||||
|
||||
// Steps 1-2: ranges.
|
||||
// Step 1.
|
||||
let count = if node.is::<DocumentFragment>() {
|
||||
node.children_count()
|
||||
} else {
|
||||
1
|
||||
};
|
||||
// Step 2.
|
||||
if let Some(child) = child {
|
||||
if !parent.ranges.is_empty() {
|
||||
let index = child.index();
|
||||
// Steps 2.1-2.
|
||||
parent.ranges.increase_above(parent, index, count);
|
||||
}
|
||||
}
|
||||
let mut new_nodes = RootedVec::new();
|
||||
let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() {
|
||||
// Step 3.
|
||||
|
@ -1571,18 +1598,31 @@ impl Node {
|
|||
// https://dom.spec.whatwg.org/#concept-node-remove
|
||||
fn remove(node: &Node, parent: &Node, suppress_observers: SuppressObserver) {
|
||||
assert!(node.GetParentNode().map_or(false, |node_parent| node_parent.r() == parent));
|
||||
|
||||
// Step 1-5: ranges.
|
||||
let cached_index = {
|
||||
if parent.ranges.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// Step 1.
|
||||
let index = node.index();
|
||||
// Steps 2-3 are handled in Node::unbind_from_tree.
|
||||
// Steps 4-5.
|
||||
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.
|
||||
Some(index)
|
||||
}
|
||||
};
|
||||
// Step 6.
|
||||
let old_previous_sibling = node.GetPreviousSibling();
|
||||
// Steps 7-8: mutation observers.
|
||||
// Step 9.
|
||||
let old_next_sibling = node.GetNextSibling();
|
||||
parent.remove_child(node);
|
||||
parent.remove_child(node, cached_index);
|
||||
if let SuppressObserver::Unsuppressed = suppress_observers {
|
||||
vtable_for(&parent).children_changed(
|
||||
&ChildrenMutation::replace(old_previous_sibling.r(),
|
||||
&node, &[],
|
||||
&Some(&node), &[],
|
||||
old_next_sibling.r()));
|
||||
}
|
||||
}
|
||||
|
@ -1611,10 +1651,9 @@ impl Node {
|
|||
let doc_fragment = DocumentFragment::new(document.r());
|
||||
Root::upcast::<Node>(doc_fragment)
|
||||
},
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
|
||||
NodeTypeId::CharacterData(_) => {
|
||||
let cdata = node.downcast::<CharacterData>().unwrap();
|
||||
let comment = Comment::new(cdata.Data(), document.r());
|
||||
Root::upcast::<Node>(comment)
|
||||
cdata.clone_with_data(cdata.Data(), &document)
|
||||
},
|
||||
NodeTypeId::Document(_) => {
|
||||
let document = node.downcast::<Document>().unwrap();
|
||||
|
@ -1640,17 +1679,6 @@ impl Node {
|
|||
document.r(), ElementCreator::ScriptCreated);
|
||||
Root::upcast::<Node>(element)
|
||||
},
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Text) => {
|
||||
let cdata = node.downcast::<CharacterData>().unwrap();
|
||||
let text = Text::new(cdata.Data(), document.r());
|
||||
Root::upcast::<Node>(text)
|
||||
},
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
|
||||
let pi = node.downcast::<ProcessingInstruction>().unwrap();
|
||||
let pi = ProcessingInstruction::new(pi.Target(),
|
||||
pi.upcast::<CharacterData>().Data(), document.r());
|
||||
Root::upcast::<Node>(pi)
|
||||
},
|
||||
};
|
||||
|
||||
// Step 3.
|
||||
|
@ -2038,11 +2066,6 @@ impl NodeMethods for Node {
|
|||
}
|
||||
}
|
||||
|
||||
// Ok if not caught by previous error checks.
|
||||
if node == child {
|
||||
return Ok(Root::from_ref(child));
|
||||
}
|
||||
|
||||
// Step 7-8.
|
||||
let child_next_sibling = child.GetNextSibling();
|
||||
let node_next_sibling = node.GetNextSibling();
|
||||
|
@ -2053,14 +2076,19 @@ impl NodeMethods for Node {
|
|||
};
|
||||
|
||||
// Step 9.
|
||||
let previous_sibling = child.GetPreviousSibling();
|
||||
|
||||
// Step 10.
|
||||
let document = document_from_node(self);
|
||||
Node::adopt(node, document.r());
|
||||
|
||||
// Step 10.
|
||||
let previous_sibling = child.GetPreviousSibling();
|
||||
|
||||
// Step 11.
|
||||
Node::remove(child, self, SuppressObserver::Suppressed);
|
||||
let removed_child = if node != child {
|
||||
// Step 11.
|
||||
Node::remove(child, self, SuppressObserver::Suppressed);
|
||||
Some(child)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Step 12.
|
||||
let mut nodes = RootedVec::new();
|
||||
|
@ -2077,7 +2105,7 @@ impl NodeMethods for Node {
|
|||
// Step 14.
|
||||
vtable_for(&self).children_changed(
|
||||
&ChildrenMutation::replace(previous_sibling.r(),
|
||||
&child, nodes,
|
||||
&removed_child, nodes,
|
||||
reference_child));
|
||||
|
||||
// Step 15.
|
||||
|
@ -2092,28 +2120,26 @@ impl NodeMethods for Node {
|
|||
|
||||
// https://dom.spec.whatwg.org/#dom-node-normalize
|
||||
fn Normalize(&self) {
|
||||
let mut prev_text: Option<Root<Text>> = None;
|
||||
for child in self.children() {
|
||||
match child.downcast::<Text>() {
|
||||
Some(text) => {
|
||||
let characterdata = text.upcast::<CharacterData>();
|
||||
if characterdata.Length() == 0 {
|
||||
Node::remove(&*child, self, SuppressObserver::Unsuppressed);
|
||||
} else {
|
||||
match prev_text {
|
||||
Some(ref text_node) => {
|
||||
let prev_characterdata = text_node.upcast::<CharacterData>();
|
||||
prev_characterdata.append_data(&**characterdata.data());
|
||||
Node::remove(&*child, self, SuppressObserver::Unsuppressed);
|
||||
},
|
||||
None => prev_text = Some(Root::from_ref(text))
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
child.Normalize();
|
||||
prev_text = None;
|
||||
let mut children = self.children().enumerate().peekable();
|
||||
while let Some((_, node)) = children.next() {
|
||||
if let Some(text) = node.downcast::<Text>() {
|
||||
let cdata = text.upcast::<CharacterData>();
|
||||
let mut length = cdata.Length();
|
||||
if length == 0 {
|
||||
Node::remove(&node, self, SuppressObserver::Unsuppressed);
|
||||
continue;
|
||||
}
|
||||
while children.peek().map_or(false, |&(_, ref sibling)| sibling.is::<Text>()) {
|
||||
let (index, sibling) = children.next().unwrap();
|
||||
sibling.ranges.drain_to_preceding_text_sibling(&sibling, &node, length);
|
||||
self.ranges.move_to_text_child_at(self, index as u32, &node, length as u32);
|
||||
let sibling_cdata = sibling.downcast::<CharacterData>().unwrap();
|
||||
length += sibling_cdata.Length();
|
||||
cdata.append_data(&sibling_cdata.data());
|
||||
Node::remove(&sibling, self, SuppressObserver::Unsuppressed);
|
||||
}
|
||||
} else {
|
||||
node.Normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2348,25 +2374,17 @@ impl VirtualMethods for Node {
|
|||
if let Some(ref s) = self.super_type() {
|
||||
s.children_changed(mutation);
|
||||
}
|
||||
match *mutation {
|
||||
ChildrenMutation::Append { added, .. } |
|
||||
ChildrenMutation::Insert { added, .. } |
|
||||
ChildrenMutation::Prepend { added, .. } => {
|
||||
self.children_count.set(
|
||||
self.children_count.get() + added.len() as u32);
|
||||
},
|
||||
ChildrenMutation::Replace { added, .. } => {
|
||||
self.children_count.set(
|
||||
self.children_count.get() - 1u32 + added.len() as u32);
|
||||
},
|
||||
ChildrenMutation::ReplaceAll { added, .. } => {
|
||||
self.children_count.set(added.len() as u32);
|
||||
},
|
||||
}
|
||||
if let Some(list) = self.child_list.get() {
|
||||
list.as_children_list().children_changed(mutation);
|
||||
}
|
||||
}
|
||||
|
||||
// This handles the ranges mentioned in steps 2-3 when removing a node.
|
||||
// https://dom.spec.whatwg.org/#concept-node-remove
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
self.ranges.drain_to_parent(context, self);
|
||||
}
|
||||
}
|
||||
|
||||
/// A summary of the changes that happened to a node.
|
||||
|
@ -2411,22 +2429,26 @@ impl<'a> ChildrenMutation<'a> {
|
|||
}
|
||||
|
||||
fn replace(prev: Option<&'a Node>,
|
||||
removed: &'a &'a Node,
|
||||
removed: &'a Option<&'a Node>,
|
||||
added: &'a [&'a Node],
|
||||
next: Option<&'a Node>)
|
||||
-> ChildrenMutation<'a> {
|
||||
if let (None, None) = (prev, next) {
|
||||
ChildrenMutation::ReplaceAll {
|
||||
removed: ref_slice(removed),
|
||||
added: added,
|
||||
if let Some(ref removed) = *removed {
|
||||
if let (None, None) = (prev, next) {
|
||||
ChildrenMutation::ReplaceAll {
|
||||
removed: ref_slice(removed),
|
||||
added: added,
|
||||
}
|
||||
} else {
|
||||
ChildrenMutation::Replace {
|
||||
prev: prev,
|
||||
removed: *removed,
|
||||
added: added,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ChildrenMutation::Replace {
|
||||
prev: prev,
|
||||
removed: *removed,
|
||||
added: added,
|
||||
next: next,
|
||||
}
|
||||
ChildrenMutation::insert(prev, added, next)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2435,3 +2457,42 @@ impl<'a> ChildrenMutation<'a> {
|
|||
ChildrenMutation::ReplaceAll { removed: removed, added: added }
|
||||
}
|
||||
}
|
||||
|
||||
/// The context of the unbinding from a tree of a node when one of its
|
||||
/// inclusive ancestors is removed.
|
||||
pub struct UnbindContext<'a> {
|
||||
/// The index of the inclusive ancestor that was removed.
|
||||
index: Cell<Option<u32>>,
|
||||
/// The parent of the inclusive ancestor that was removed.
|
||||
pub parent: &'a Node,
|
||||
/// The previous sibling of the inclusive ancestor that was removed.
|
||||
prev_sibling: Option<&'a Node>,
|
||||
/// Whether the tree is in a document.
|
||||
pub tree_in_doc: bool,
|
||||
}
|
||||
|
||||
impl<'a> UnbindContext<'a> {
|
||||
/// Create a new `UnbindContext` value.
|
||||
fn new(parent: &'a Node,
|
||||
prev_sibling: Option<&'a Node>,
|
||||
cached_index: Option<u32>) -> Self {
|
||||
UnbindContext {
|
||||
index: Cell::new(cached_index),
|
||||
parent: parent,
|
||||
prev_sibling: prev_sibling,
|
||||
tree_in_doc: parent.is_in_doc(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The index of the inclusive ancestor that was removed from the tree.
|
||||
#[allow(unsafe_code)]
|
||||
pub fn index(&self) -> u32 {
|
||||
if let Some(index) = self.index.get() {
|
||||
return index;
|
||||
}
|
||||
let index =
|
||||
self.prev_sibling.map(|sibling| sibling.index() + 1).unwrap_or(0);
|
||||
self.index.set(Some(index));
|
||||
index
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue