Properly propagate changes when range or trees are mutated

This commit is contained in:
Anthony Ramine 2015-10-25 10:26:54 +01:00
parent 1dd7c8cf01
commit 3c76835615
11 changed files with 540 additions and 4566 deletions

View file

@ -18,6 +18,10 @@ DOMInterfaces = {
'outerObjectHook': 'Some(bindings::utils::outerize_global)', 'outerObjectHook': 'Some(bindings::utils::outerize_global)',
}, },
'Range': {
'weakReferenceable': True,
},
#FIXME(jdm): This should be 'register': False, but then we don't generate enum types #FIXME(jdm): This should be 'register': False, but then we don't generate enum types
'TestBinding': {}, 'TestBinding': {},

View file

@ -19,7 +19,9 @@ use js::jsapi::{JSTracer, JS_GetReservedSlot, JS_SetReservedSlot};
use js::jsval::PrivateValue; use js::jsval::PrivateValue;
use libc::c_void; use libc::c_void;
use std::cell::{Cell, UnsafeCell}; use std::cell::{Cell, UnsafeCell};
use std::iter::Iterator;
use std::mem; use std::mem;
use std::ops::{Deref, DerefMut, Drop};
use util::mem::HeapSizeOf; use util::mem::HeapSizeOf;
/// The index of the slot wherein a pointer to the weak holder cell is /// The index of the slot wherein a pointer to the weak holder cell is
@ -113,6 +115,25 @@ impl<T: WeakReferenceable> HeapSizeOf for WeakRef<T> {
} }
} }
impl<T: WeakReferenceable> PartialEq for WeakRef<T> {
fn eq(&self, other: &Self) -> bool {
unsafe {
(**self.ptr).value.get() == (**other.ptr).value.get()
}
}
}
impl<T: WeakReferenceable> PartialEq<T> for WeakRef<T> {
fn eq(&self, other: &T) -> bool {
unsafe {
match (**self.ptr).value.get() {
Some(ptr) => *ptr == other,
None => false,
}
}
}
}
no_jsmanaged_fields!(WeakRef<T: WeakReferenceable>); no_jsmanaged_fields!(WeakRef<T: WeakReferenceable>);
impl<T: WeakReferenceable> Drop for WeakRef<T> { impl<T: WeakReferenceable> Drop for WeakRef<T> {
@ -182,3 +203,81 @@ impl<T: WeakReferenceable> JSTraceable for MutableWeakRef<T> {
} }
} }
} }
/// A vector of weak references. On tracing, the vector retains
/// only references which still point to live objects.
#[allow_unrooted_interior]
#[derive(HeapSizeOf)]
pub struct WeakRefVec<T: WeakReferenceable> {
vec: Vec<WeakRef<T>>,
}
impl<T: WeakReferenceable> WeakRefVec<T> {
/// Create a new vector of weak references.
pub fn new() -> Self {
WeakRefVec { vec: vec![] }
}
/// Calls a function on each reference which still points to a
/// live object. The order of the references isn't preserved.
pub fn update<F: FnMut(WeakRefEntry<T>)>(&mut self, mut f: F) {
let mut i = 0;
while i < self.vec.len() {
if self.vec[i].is_alive() {
f(WeakRefEntry { vec: self, index: &mut i });
} else {
self.vec.swap_remove(i);
}
}
}
/// Clears the vector of its dead references.
pub fn retain_alive(&mut self) {
self.update(|_| ());
}
}
impl<T: WeakReferenceable> Deref for WeakRefVec<T> {
type Target = Vec<WeakRef<T>>;
fn deref(&self) -> &Vec<WeakRef<T>> {
&self.vec
}
}
impl<T: WeakReferenceable> DerefMut for WeakRefVec<T> {
fn deref_mut(&mut self) -> &mut Vec<WeakRef<T>> {
&mut self.vec
}
}
/// An entry of a vector of weak references. Passed to the closure
/// given to `WeakRefVec::update`.
#[allow_unrooted_interior]
pub struct WeakRefEntry<'a, T: WeakReferenceable + 'a> {
vec: &'a mut WeakRefVec<T>,
index: &'a mut usize,
}
impl<'a, T: WeakReferenceable + 'a> WeakRefEntry<'a, T> {
/// Remove the entry from the underlying vector of weak references.
pub fn remove(self) -> WeakRef<T> {
let ref_ = self.vec.swap_remove(*self.index);
mem::forget(self);
ref_
}
}
impl<'a, T: WeakReferenceable + 'a> Deref for WeakRefEntry<'a, T> {
type Target = WeakRef<T>;
fn deref(&self) -> &WeakRef<T> {
&self.vec[*self.index]
}
}
impl<'a, T: WeakReferenceable + 'a> Drop for WeakRefEntry<'a, T> {
fn drop(&mut self) {
*self.index += 1;
}
}

View file

@ -77,13 +77,17 @@ impl CharacterDataMethods for CharacterData {
// https://dom.spec.whatwg.org/#dom-characterdata-data // https://dom.spec.whatwg.org/#dom-characterdata-data
fn SetData(&self, data: DOMString) { fn SetData(&self, data: DOMString) {
let old_length = self.Length();
let new_length = data.utf16_units().count() as u32;
*self.data.borrow_mut() = data; *self.data.borrow_mut() = data;
self.content_changed(); self.content_changed();
let node = self.upcast::<Node>();
node.ranges().replace_code_units(node, 0, old_length, new_length);
} }
// https://dom.spec.whatwg.org/#dom-characterdata-length // https://dom.spec.whatwg.org/#dom-characterdata-length
fn Length(&self) -> u32 { fn Length(&self) -> u32 {
self.data.borrow().chars().map(|c| c.len_utf16()).sum::<usize>() as u32 self.data.borrow().utf16_units().count() as u32
} }
// https://dom.spec.whatwg.org/#dom-characterdata-substringdata // https://dom.spec.whatwg.org/#dom-characterdata-substringdata
@ -144,7 +148,10 @@ impl CharacterDataMethods for CharacterData {
}; };
*self.data.borrow_mut() = DOMString::from(new_data); *self.data.borrow_mut() = DOMString::from(new_data);
self.content_changed(); self.content_changed();
// FIXME: Once we have `Range`, we should implement step 8 to step 11 // Steps 8-11.
let node = self.upcast::<Node>();
node.ranges().replace_code_units(
node, offset, count, arg.utf16_units().count() as u32);
Ok(()) Ok(())
} }

View file

@ -42,6 +42,7 @@ use dom::htmlcollection::HTMLCollection;
use dom::htmlelement::HTMLElement; use dom::htmlelement::HTMLElement;
use dom::nodelist::NodeList; use dom::nodelist::NodeList;
use dom::processinginstruction::ProcessingInstruction; use dom::processinginstruction::ProcessingInstruction;
use dom::range::WeakRangeVec;
use dom::text::Text; use dom::text::Text;
use dom::virtualmethods::{VirtualMethods, vtable_for}; use dom::virtualmethods::{VirtualMethods, vtable_for};
use dom::window::Window; use dom::window::Window;
@ -108,6 +109,12 @@ pub struct Node {
/// The maximum version of any inclusive descendant of this node. /// The maximum version of any inclusive descendant of this node.
inclusive_descendants_version: Cell<u64>, 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. /// Layout information. Only the layout task may touch this data.
/// ///
/// Must be sent back to the layout task to be destroyed when this /// Must be sent back to the layout task to be destroyed when this
@ -296,7 +303,7 @@ impl Node {
/// Removes the given child from this node's list of children. /// Removes the given child from this node's list of children.
/// ///
/// Fails unless `child` is a child of this node. /// 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)); assert!(child.parent_node.get().r() == Some(self));
let prev_sibling = child.GetPreviousSibling(); let prev_sibling = child.GetPreviousSibling();
match prev_sibling { match prev_sibling {
@ -317,9 +324,7 @@ impl Node {
} }
} }
let context = UnbindContext { let context = UnbindContext::new(self, prev_sibling.r(), cached_index);
tree_in_doc: child.is_in_doc(),
};
child.prev_sibling.set(None); child.prev_sibling.set(None);
child.next_sibling.set(None); child.next_sibling.set(None);
@ -437,6 +442,10 @@ impl Node {
self.children_count.get() self.children_count.get()
} }
pub fn ranges(&self) -> &WeakRangeVec {
&self.ranges
}
#[inline] #[inline]
pub fn is_doctype(&self) -> bool { pub fn is_doctype(&self) -> bool {
self.type_id() == NodeTypeId::DocumentType self.type_id() == NodeTypeId::DocumentType
@ -1305,6 +1314,7 @@ impl Node {
children_count: Cell::new(0u32), children_count: Cell::new(0u32),
flags: Cell::new(flags), flags: Cell::new(flags),
inclusive_descendants_version: Cell::new(0), inclusive_descendants_version: Cell::new(0),
ranges: WeakRangeVec::new(),
layout_data: LayoutDataRef::new(), layout_data: LayoutDataRef::new(),
@ -1479,7 +1489,20 @@ impl Node {
debug_assert!(&*node.owner_doc() == &*parent.owner_doc()); debug_assert!(&*node.owner_doc() == &*parent.owner_doc());
debug_assert!(child.map_or(true, |child| Some(parent) == child.GetParentNode().r())); 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 mut new_nodes = RootedVec::new();
let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() { let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() {
// Step 3. // Step 3.
@ -1569,14 +1592,27 @@ impl Node {
// https://dom.spec.whatwg.org/#concept-node-remove // https://dom.spec.whatwg.org/#concept-node-remove
fn remove(node: &Node, parent: &Node, suppress_observers: SuppressObserver) { fn remove(node: &Node, parent: &Node, suppress_observers: SuppressObserver) {
assert!(node.GetParentNode().map_or(false, |node_parent| node_parent.r() == parent)); assert!(node.GetParentNode().map_or(false, |node_parent| node_parent.r() == parent));
let cached_index = {
// Step 1-5: ranges. 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. // Step 6.
let old_previous_sibling = node.GetPreviousSibling(); let old_previous_sibling = node.GetPreviousSibling();
// Steps 7-8: mutation observers. // Steps 7-8: mutation observers.
// Step 9. // Step 9.
let old_next_sibling = node.GetNextSibling(); let old_next_sibling = node.GetNextSibling();
parent.remove_child(node); parent.remove_child(node, cached_index);
if let SuppressObserver::Unsuppressed = suppress_observers { if let SuppressObserver::Unsuppressed = suppress_observers {
vtable_for(&parent).children_changed( vtable_for(&parent).children_changed(
&ChildrenMutation::replace(old_previous_sibling.r(), &ChildrenMutation::replace(old_previous_sibling.r(),
@ -2078,28 +2114,26 @@ impl NodeMethods for Node {
// https://dom.spec.whatwg.org/#dom-node-normalize // https://dom.spec.whatwg.org/#dom-node-normalize
fn Normalize(&self) { fn Normalize(&self) {
let mut prev_text: Option<Root<Text>> = None; let mut children = self.children().enumerate().peekable();
for child in self.children() { while let Some((_, node)) = children.next() {
match child.downcast::<Text>() { if let Some(text) = node.downcast::<Text>() {
Some(text) => { let cdata = text.upcast::<CharacterData>();
let characterdata = text.upcast::<CharacterData>(); let mut length = cdata.Length();
if characterdata.Length() == 0 { if length == 0 {
Node::remove(&*child, self, SuppressObserver::Unsuppressed); Node::remove(&node, self, SuppressObserver::Unsuppressed);
} else { continue;
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;
} }
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();
} }
} }
} }
@ -2338,6 +2372,13 @@ impl VirtualMethods for Node {
list.as_children_list().children_changed(mutation); 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. /// A summary of the changes that happened to a node.
@ -2413,7 +2454,39 @@ impl<'a> ChildrenMutation<'a> {
/// The context of the unbinding from a tree of a node when one of its /// The context of the unbinding from a tree of a node when one of its
/// inclusive ancestors is removed. /// inclusive ancestors is removed.
pub struct UnbindContext { 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. /// Whether the tree is in a document.
pub tree_in_doc: bool, 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
}
}

View file

@ -16,14 +16,17 @@ use dom::bindings::inheritance::Castable;
use dom::bindings::inheritance::{CharacterDataTypeId, NodeTypeId}; use dom::bindings::inheritance::{CharacterDataTypeId, NodeTypeId};
use dom::bindings::js::{JS, MutHeap, Root, RootedReference}; use dom::bindings::js::{JS, MutHeap, Root, RootedReference};
use dom::bindings::reflector::{Reflector, reflect_dom_object}; use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::trace::RootedVec; use dom::bindings::trace::{JSTraceable, RootedVec};
use dom::bindings::weakref::{WeakRef, WeakRefVec};
use dom::characterdata::CharacterData; use dom::characterdata::CharacterData;
use dom::document::Document; use dom::document::Document;
use dom::documentfragment::DocumentFragment; use dom::documentfragment::DocumentFragment;
use dom::node::Node; use dom::node::{Node, UnbindContext};
use dom::text::Text; use dom::text::Text;
use std::cell::Cell; use js::jsapi::JSTracer;
use std::cell::{Cell, UnsafeCell};
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use util::mem::HeapSizeOf;
use util::str::DOMString; use util::str::DOMString;
#[dom_struct] #[dom_struct]
@ -52,10 +55,15 @@ impl Range {
start_container: &Node, start_offset: u32, start_container: &Node, start_offset: u32,
end_container: &Node, end_offset: u32) end_container: &Node, end_offset: u32)
-> Root<Range> { -> Root<Range> {
reflect_dom_object(box Range::new_inherited(start_container, start_offset, let range = reflect_dom_object(box Range::new_inherited(start_container, start_offset,
end_container, end_offset), end_container, end_offset),
GlobalRef::Window(document.window()), GlobalRef::Window(document.window()),
RangeBinding::Wrap) RangeBinding::Wrap);
start_container.ranges().push(WeakRef::new(&range));
if start_container != end_container {
end_container.ranges().push(WeakRef::new(&range));
}
range
} }
// https://dom.spec.whatwg.org/#dom-range // https://dom.spec.whatwg.org/#dom-range
@ -121,19 +129,31 @@ impl Range {
} }
// https://dom.spec.whatwg.org/#concept-range-bp-set // https://dom.spec.whatwg.org/#concept-range-bp-set
pub fn set_start(&self, node: &Node, offset: u32) { fn set_start(&self, node: &Node, offset: u32) {
self.start.set(node, offset); if &self.start.node != node {
if !(self.start <= self.end) { if self.start.node == self.end.node {
self.end.set(node, offset); node.ranges().push(WeakRef::new(&self));
} else if &self.end.node == node {
self.StartContainer().ranges().remove(self);
} else {
node.ranges().push(self.StartContainer().ranges().remove(self));
}
} }
self.start.set(node, offset);
} }
// https://dom.spec.whatwg.org/#concept-range-bp-set // https://dom.spec.whatwg.org/#concept-range-bp-set
pub fn set_end(&self, node: &Node, offset: u32) { fn set_end(&self, node: &Node, offset: u32) {
self.end.set(node, offset); if &self.end.node != node {
if !(self.end >= self.start) { if self.end.node == self.start.node {
self.start.set(node, offset); node.ranges().push(WeakRef::new(&self));
} else if &self.start.node == node {
self.EndContainer().ranges().remove(self);
} else {
node.ranges().push(self.EndContainer().ranges().remove(self));
}
} }
self.end.set(node, offset);
} }
// https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset // https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset
@ -215,8 +235,12 @@ impl RangeMethods for Range {
// Step 2. // Step 2.
Err(Error::IndexSize) Err(Error::IndexSize)
} else { } else {
// Step 3-4. // Step 3.
self.set_start(node, offset); self.set_start(node, offset);
if !(self.start <= self.end) {
// Step 4.
self.set_end(node, offset);
}
Ok(()) Ok(())
} }
} }
@ -230,8 +254,12 @@ impl RangeMethods for Range {
// Step 2. // Step 2.
Err(Error::IndexSize) Err(Error::IndexSize)
} else { } else {
// Step 3-4. // Step 3.
self.set_end(node, offset); self.set_end(node, offset);
if !(self.end >= self.start) {
// Step 4.
self.set_start(node, offset);
}
Ok(()) Ok(())
} }
} }
@ -263,9 +291,9 @@ impl RangeMethods for Range {
// https://dom.spec.whatwg.org/#dom-range-collapse // https://dom.spec.whatwg.org/#dom-range-collapse
fn Collapse(&self, to_start: bool) { fn Collapse(&self, to_start: bool) {
if to_start { if to_start {
self.end.set(&self.StartContainer(), self.StartOffset()); self.set_end(&self.StartContainer(), self.StartOffset());
} else { } else {
self.start.set(&self.EndContainer(), self.EndOffset()); self.set_start(&self.EndContainer(), self.EndOffset());
} }
} }
@ -276,9 +304,9 @@ impl RangeMethods for Range {
// Step 3. // Step 3.
let index = node.index(); let index = node.index();
// Step 4. // Step 4.
self.start.set(&parent, index); self.set_start(&parent, index);
// Step 5. // Step 5.
self.end.set(&parent, index + 1); self.set_end(&parent, index + 1);
Ok(()) Ok(())
} }
@ -291,9 +319,9 @@ impl RangeMethods for Range {
// Step 2. // Step 2.
let length = node.len(); let length = node.len();
// Step 3. // Step 3.
self.start.set(node, 0); self.set_start(node, 0);
// Step 4. // Step 4.
self.end.set(node, length); self.set_end(node, length);
Ok(()) Ok(())
} }
@ -833,10 +861,12 @@ impl BoundaryPoint {
} }
} }
fn set(&self, node: &Node, offset: u32) { pub fn set(&self, node: &Node, offset: u32) {
debug_assert!(!node.is_doctype());
debug_assert!(offset <= node.len());
self.node.set(node); self.node.set(node);
self.set_offset(offset);
}
pub fn set_offset(&self, offset: u32) {
self.offset.set(offset); self.offset.set(offset);
} }
} }
@ -894,3 +924,259 @@ fn bp_position(a_node: &Node, a_offset: u32,
Some(Ordering::Less) Some(Ordering::Less)
} }
} }
pub struct WeakRangeVec {
cell: UnsafeCell<WeakRefVec<Range>>,
}
#[allow(unsafe_code)]
impl WeakRangeVec {
/// Create a new vector of weak references.
pub fn new() -> Self {
WeakRangeVec { cell: UnsafeCell::new(WeakRefVec::new()) }
}
/// Whether that vector of ranges is empty.
pub fn is_empty(&self) -> bool {
unsafe { (*self.cell.get()).is_empty() }
}
/// Used for steps 2.1-2. when inserting a node.
/// https://dom.spec.whatwg.org/#concept-node-insert
pub fn increase_above(&self, node: &Node, offset: u32, delta: u32) {
self.map_offset_above(node, offset, |offset| offset + delta);
}
/// Used for steps 4-5. when removing a node.
/// https://dom.spec.whatwg.org/#concept-node-remove
pub fn decrease_above(&self, node: &Node, offset: u32, delta: u32) {
self.map_offset_above(node, offset, |offset| offset - delta);
}
/// Used for steps 2-3. when removing a node.
/// https://dom.spec.whatwg.org/#concept-node-remove
pub fn drain_to_parent(&self, context: &UnbindContext, child: &Node) {
if self.is_empty() {
return;
}
let offset = context.index();
let parent = context.parent;
unsafe {
let mut ranges = &mut *self.cell.get();
ranges.update(|entry| {
let range = entry.root().unwrap();
if &range.start.node == parent || &range.end.node == parent {
entry.remove();
}
if &range.start.node == child {
range.start.set(context.parent, offset);
}
if &range.end.node == child {
range.end.set(context.parent, offset);
}
});
(*context.parent.ranges().cell.get()).extend(ranges.drain(..));
}
}
/// Used for steps 7.1-2. when normalizing a node.
/// https://dom.spec.whatwg.org/#dom-node-normalize
pub fn drain_to_preceding_text_sibling(&self, node: &Node, sibling: &Node, length: u32) {
if self.is_empty() {
return;
}
unsafe {
let mut ranges = &mut *self.cell.get();
ranges.update(|entry| {
let range = entry.root().unwrap();
if &range.start.node == sibling || &range.end.node == sibling {
entry.remove();
}
if &range.start.node == node {
range.start.set(sibling, range.StartOffset() + length);
}
if &range.end.node == node {
range.end.set(sibling, range.EndOffset() + length);
}
});
(*sibling.ranges().cell.get()).extend(ranges.drain(..));
}
}
/// Used for steps 7.3-4. when normalizing a node.
/// https://dom.spec.whatwg.org/#dom-node-normalize
pub fn move_to_text_child_at(&self,
node: &Node, offset: u32,
child: &Node, new_offset: u32) {
unsafe {
let child_ranges = &mut *child.ranges().cell.get();
(*self.cell.get()).update(|entry| {
let range = entry.root().unwrap();
let node_is_start = &range.start.node == node;
let node_is_end = &range.end.node == node;
let move_start = node_is_start && range.StartOffset() == offset;
let move_end = node_is_end && range.EndOffset() == offset;
let remove_from_node = move_start && move_end ||
move_start && !node_is_end ||
move_end && !node_is_start;
let already_in_child = &range.start.node == child || &range.end.node == child;
let push_to_child = !already_in_child && (move_start || move_end);
if remove_from_node {
let ref_ = entry.remove();
if push_to_child {
child_ranges.push(ref_);
}
} else if push_to_child {
child_ranges.push(WeakRef::new(&range));
}
if move_start {
range.start.set(child, new_offset);
}
if move_end {
range.end.set(child, new_offset);
}
});
}
}
/// Used for steps 8-11. when replacing character data.
/// https://dom.spec.whatwg.org/#concept-cd-replace
pub fn replace_code_units(&self,
node: &Node, offset: u32,
removed_code_units: u32, added_code_units: u32) {
self.map_offset_above(node, offset, |range_offset| {
if range_offset <= offset + removed_code_units {
offset
} else {
range_offset + added_code_units - removed_code_units
}
});
}
/// Used for steps 7.2-3. when splitting a text node.
/// https://dom.spec.whatwg.org/#concept-text-split
pub fn move_to_following_text_sibling_above(&self,
node: &Node, offset: u32,
sibling: &Node) {
unsafe {
let sibling_ranges = &mut *sibling.ranges().cell.get();
(*self.cell.get()).update(|entry| {
let range = entry.root().unwrap();
let start_offset = range.StartOffset();
let end_offset = range.EndOffset();
let node_is_start = &range.start.node == node;
let node_is_end = &range.end.node == node;
let move_start = node_is_start && start_offset > offset;
let move_end = node_is_end && end_offset > offset;
let remove_from_node = move_start && move_end ||
move_start && !node_is_end ||
move_end && !node_is_start;
let already_in_sibling =
&range.start.node == sibling || &range.end.node == sibling;
let push_to_sibling = !already_in_sibling && (move_start || move_end);
if remove_from_node {
let ref_ = entry.remove();
if push_to_sibling {
sibling_ranges.push(ref_);
}
} else if push_to_sibling {
sibling_ranges.push(WeakRef::new(&range));
}
if move_start {
range.start.set(sibling, start_offset - offset);
}
if move_end {
range.end.set(sibling, end_offset - offset);
}
});
}
}
/// Used for steps 7.4-5. when splitting a text node.
/// https://dom.spec.whatwg.org/#concept-text-split
pub fn increment_at(&self, node: &Node, offset: u32) {
unsafe {
(*self.cell.get()).update(|entry| {
let range = entry.root().unwrap();
if &range.start.node == node && offset == range.StartOffset() {
range.start.set_offset(offset + 1);
}
if &range.end.node == node && offset == range.EndOffset() {
range.end.set_offset(offset + 1);
}
});
}
}
/// Used for steps 9.1-2. when splitting a text node.
/// https://dom.spec.whatwg.org/#concept-text-split
pub fn clamp_above(&self, node: &Node, offset: u32) {
self.map_offset_above(node, offset, |_| offset);
}
fn map_offset_above<F: FnMut(u32) -> u32>(&self, node: &Node, offset: u32, mut f: F) {
unsafe {
(*self.cell.get()).update(|entry| {
let range = entry.root().unwrap();
let start_offset = range.StartOffset();
if &range.start.node == node && start_offset > offset {
range.start.set_offset(f(start_offset));
}
let end_offset = range.EndOffset();
if &range.end.node == node && end_offset > offset {
range.end.set_offset(f(end_offset));
}
});
}
}
fn push(&self, ref_: WeakRef<Range>) {
unsafe {
(*self.cell.get()).push(ref_);
}
}
fn remove(&self, range: &Range) -> WeakRef<Range> {
unsafe {
let ranges = &mut *self.cell.get();
let position = ranges.iter().position(|ref_| {
ref_ == range
}).unwrap();
ranges.swap_remove(position)
}
}
}
#[allow(unsafe_code)]
impl HeapSizeOf for WeakRangeVec {
fn heap_size_of_children(&self) -> usize {
unsafe { (*self.cell.get()).heap_size_of_children() }
}
}
#[allow(unsafe_code)]
impl JSTraceable for WeakRangeVec {
fn trace(&self, _: *mut JSTracer) {
unsafe { (*self.cell.get()).retain_alive() }
}
}

View file

@ -62,15 +62,18 @@ impl TextMethods for Text {
// Step 6. // Step 6.
let parent = node.GetParentNode(); let parent = node.GetParentNode();
if let Some(ref parent) = parent { if let Some(ref parent) = parent {
// Step 7. // Step 7.1.
parent.InsertBefore(new_node.upcast(), node.GetNextSibling().r()).unwrap(); parent.InsertBefore(new_node.upcast(), node.GetNextSibling().r()).unwrap();
// TODO: Ranges. // Steps 7.2-3.
node.ranges().move_to_following_text_sibling_above(node, offset, new_node.upcast());
// Steps 7.4-5.
parent.ranges().increment_at(&parent, node.index() + 1);
} }
// Step 8. // Step 8.
cdata.DeleteData(offset, count).unwrap(); cdata.DeleteData(offset, count).unwrap();
if parent.is_none() { if parent.is_none() {
// Step 9. // Step 9.
// TODO: Ranges node.ranges().clamp_above(&node, offset);
} }
// Step 10. // Step 10.
Ok(new_node) Ok(new_node)

View file

@ -13,7 +13,6 @@
#![feature(custom_derive)] #![feature(custom_derive)]
#![feature(fnbox)] #![feature(fnbox)]
#![feature(hashmap_hasher)] #![feature(hashmap_hasher)]
#![feature(iter_arith)]
#![feature(mpsc_select)] #![feature(mpsc_select)]
#![feature(nonzero)] #![feature(nonzero)]
#![feature(on_unimplemented)] #![feature(on_unimplemented)]

View file

@ -38,10 +38,10 @@ macro_rules! sizeof_checker (
// Update the sizes here // Update the sizes here
sizeof_checker!(size_event_target, EventTarget, 40); sizeof_checker!(size_event_target, EventTarget, 40);
sizeof_checker!(size_node, Node, 160); sizeof_checker!(size_node, Node, 184);
sizeof_checker!(size_element, Element, 304); sizeof_checker!(size_element, Element, 328);
sizeof_checker!(size_htmlelement, HTMLElement, 320); sizeof_checker!(size_htmlelement, HTMLElement, 344);
sizeof_checker!(size_div, HTMLDivElement, 320); sizeof_checker!(size_div, HTMLDivElement, 344);
sizeof_checker!(size_span, HTMLSpanElement, 320); sizeof_checker!(size_span, HTMLSpanElement, 344);
sizeof_checker!(size_text, Text, 192); sizeof_checker!(size_text, Text, 216);
sizeof_checker!(size_characterdata, CharacterData, 192); sizeof_checker!(size_characterdata, CharacterData, 216);

View file

@ -1,23 +1,5 @@
[Range-deleteContents.html] [Range-deleteContents.html]
type: testharness type: testharness
[Resulting cursor position for range 1 [paras[0\].firstChild, 0, paras[0\].firstChild, 1\]]
expected: FAIL
[Resulting cursor position for range 2 [paras[0\].firstChild, 2, paras[0\].firstChild, 8\]]
expected: FAIL
[Resulting cursor position for range 3 [paras[0\].firstChild, 2, paras[0\].firstChild, 9\]]
expected: FAIL
[Resulting cursor position for range 5 [paras[1\].firstChild, 2, paras[1\].firstChild, 9\]]
expected: FAIL
[Resulting cursor position for range 7 [detachedPara1.firstChild, 2, detachedPara1.firstChild, 8\]]
expected: FAIL
[Resulting cursor position for range 9 [foreignPara1.firstChild, 2, foreignPara1.firstChild, 8\]]
expected: FAIL
[Resulting cursor position for range 18 [paras[0\].firstChild, 0, paras[1\].firstChild, 0\]] [Resulting cursor position for range 18 [paras[0\].firstChild, 0, paras[1\].firstChild, 0\]]
expected: FAIL expected: FAIL
@ -30,27 +12,6 @@
[Resulting DOM for range 24 [document, 0, document, 2\]] [Resulting DOM for range 24 [document, 0, document, 2\]]
expected: FAIL expected: FAIL
[Resulting cursor position for range 25 [comment, 2, comment, 3\]]
expected: FAIL
[Resulting cursor position for range 30 [detachedTextNode, 0, detachedTextNode, 8\]]
expected: FAIL
[Resulting cursor position for range 31 [detachedForeignTextNode, 0, detachedForeignTextNode, 8\]]
expected: FAIL
[Resulting cursor position for range 32 [detachedXmlTextNode, 0, detachedXmlTextNode, 8\]]
expected: FAIL
[Resulting cursor position for range 33 [detachedComment, 3, detachedComment, 4\]]
expected: FAIL
[Resulting cursor position for range 34 [detachedForeignComment, 0, detachedForeignComment, 1\]]
expected: FAIL
[Resulting cursor position for range 35 [detachedXmlComment, 2, detachedXmlComment, 6\]]
expected: FAIL
[Resulting DOM for range 10 [document.documentElement, 0, document.documentElement, 1\]] [Resulting DOM for range 10 [document.documentElement, 0, document.documentElement, 1\]]
expected: FAIL expected: FAIL
@ -66,21 +27,6 @@
[Resulting DOM for range 27 [foreignDoc, 1, foreignComment, 2\]] [Resulting DOM for range 27 [foreignDoc, 1, foreignComment, 2\]]
expected: FAIL expected: FAIL
[Resulting cursor position for range 37 [processingInstruction, 0, processingInstruction, 4\]]
expected: FAIL
[Resulting cursor position for range 38 [paras[1\].firstChild, 0, paras[1\].firstChild, 1\]]
expected: FAIL
[Resulting cursor position for range 39 [paras[1\].firstChild, 2, paras[1\].firstChild, 8\]]
expected: FAIL
[Resulting cursor position for range 40 [detachedPara1.firstChild, 0, detachedPara1.firstChild, 1\]]
expected: FAIL
[Resulting cursor position for range 41 [foreignPara1.firstChild, 0, foreignPara1.firstChild, 1\]]
expected: FAIL
[Resulting DOM for range 49 [document, 1, document, 2\]] [Resulting DOM for range 49 [document, 1, document, 2\]]
expected: FAIL expected: FAIL

View file

@ -1,23 +1,5 @@
[Range-extractContents.html] [Range-extractContents.html]
type: testharness type: testharness
[Resulting cursor position for range 1 [paras[0\].firstChild, 0, paras[0\].firstChild, 1\]]
expected: FAIL
[Resulting cursor position for range 2 [paras[0\].firstChild, 2, paras[0\].firstChild, 8\]]
expected: FAIL
[Resulting cursor position for range 3 [paras[0\].firstChild, 2, paras[0\].firstChild, 9\]]
expected: FAIL
[Resulting cursor position for range 5 [paras[1\].firstChild, 2, paras[1\].firstChild, 9\]]
expected: FAIL
[Resulting cursor position for range 7 [detachedPara1.firstChild, 2, detachedPara1.firstChild, 8\]]
expected: FAIL
[Resulting cursor position for range 9 [foreignPara1.firstChild, 2, foreignPara1.firstChild, 8\]]
expected: FAIL
[Resulting cursor position for range 18 [paras[0\].firstChild, 0, paras[1\].firstChild, 0\]] [Resulting cursor position for range 18 [paras[0\].firstChild, 0, paras[1\].firstChild, 0\]]
expected: FAIL expected: FAIL
@ -27,42 +9,6 @@
[Resulting cursor position for range 20 [paras[0\].firstChild, 3, paras[3\], 1\]] [Resulting cursor position for range 20 [paras[0\].firstChild, 3, paras[3\], 1\]]
expected: FAIL expected: FAIL
[Resulting cursor position for range 25 [comment, 2, comment, 3\]]
expected: FAIL
[Resulting cursor position for range 30 [detachedTextNode, 0, detachedTextNode, 8\]]
expected: FAIL
[Resulting cursor position for range 31 [detachedForeignTextNode, 0, detachedForeignTextNode, 8\]]
expected: FAIL
[Resulting cursor position for range 32 [detachedXmlTextNode, 0, detachedXmlTextNode, 8\]]
expected: FAIL
[Resulting cursor position for range 33 [detachedComment, 3, detachedComment, 4\]]
expected: FAIL
[Resulting cursor position for range 34 [detachedForeignComment, 0, detachedForeignComment, 1\]]
expected: FAIL
[Resulting cursor position for range 35 [detachedXmlComment, 2, detachedXmlComment, 6\]]
expected: FAIL
[Resulting cursor position for range 37 [processingInstruction, 0, processingInstruction, 4\]]
expected: FAIL
[Resulting cursor position for range 38 [paras[1\].firstChild, 0, paras[1\].firstChild, 1\]]
expected: FAIL
[Resulting cursor position for range 39 [paras[1\].firstChild, 2, paras[1\].firstChild, 8\]]
expected: FAIL
[Resulting cursor position for range 40 [detachedPara1.firstChild, 0, detachedPara1.firstChild, 1\]]
expected: FAIL
[Resulting cursor position for range 41 [foreignPara1.firstChild, 0, foreignPara1.firstChild, 1\]]
expected: FAIL
[Resulting cursor position for range 50 [paras[2\].firstChild, 4, comment, 2\]] [Resulting cursor position for range 50 [paras[2\].firstChild, 4, comment, 2\]]
expected: FAIL expected: FAIL

File diff suppressed because it is too large Load diff