mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
Properly propagate changes when range or trees are mutated
This commit is contained in:
parent
1dd7c8cf01
commit
3c76835615
11 changed files with 540 additions and 4566 deletions
|
@ -18,6 +18,10 @@ DOMInterfaces = {
|
|||
'outerObjectHook': 'Some(bindings::utils::outerize_global)',
|
||||
},
|
||||
|
||||
'Range': {
|
||||
'weakReferenceable': True,
|
||||
},
|
||||
|
||||
#FIXME(jdm): This should be 'register': False, but then we don't generate enum types
|
||||
'TestBinding': {},
|
||||
|
||||
|
|
|
@ -19,7 +19,9 @@ use js::jsapi::{JSTracer, JS_GetReservedSlot, JS_SetReservedSlot};
|
|||
use js::jsval::PrivateValue;
|
||||
use libc::c_void;
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
use std::iter::Iterator;
|
||||
use std::mem;
|
||||
use std::ops::{Deref, DerefMut, Drop};
|
||||
use util::mem::HeapSizeOf;
|
||||
|
||||
/// 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>);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,13 +77,17 @@ impl CharacterDataMethods for CharacterData {
|
|||
|
||||
// https://dom.spec.whatwg.org/#dom-characterdata-data
|
||||
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.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
|
||||
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
|
||||
|
@ -144,7 +148,10 @@ impl CharacterDataMethods for CharacterData {
|
|||
};
|
||||
*self.data.borrow_mut() = DOMString::from(new_data);
|
||||
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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -42,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;
|
||||
|
@ -108,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
|
||||
|
@ -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,9 +324,7 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
let context = UnbindContext {
|
||||
tree_in_doc: child.is_in_doc(),
|
||||
};
|
||||
let context = UnbindContext::new(self, prev_sibling.r(), cached_index);
|
||||
|
||||
child.prev_sibling.set(None);
|
||||
child.next_sibling.set(None);
|
||||
|
@ -437,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
|
||||
|
@ -1305,6 +1314,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(),
|
||||
|
||||
|
@ -1479,7 +1489,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.
|
||||
|
@ -1569,14 +1592,27 @@ 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(),
|
||||
|
@ -2078,28 +2114,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);
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
node.Normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2338,6 +2372,13 @@ impl VirtualMethods for Node {
|
|||
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.
|
||||
|
@ -2413,7 +2454,39 @@ impl<'a> ChildrenMutation<'a> {
|
|||
|
||||
/// The context of the unbinding from a tree of a node when one of its
|
||||
/// 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.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,17 @@ use dom::bindings::inheritance::Castable;
|
|||
use dom::bindings::inheritance::{CharacterDataTypeId, NodeTypeId};
|
||||
use dom::bindings::js::{JS, MutHeap, Root, RootedReference};
|
||||
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::document::Document;
|
||||
use dom::documentfragment::DocumentFragment;
|
||||
use dom::node::Node;
|
||||
use dom::node::{Node, UnbindContext};
|
||||
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 util::mem::HeapSizeOf;
|
||||
use util::str::DOMString;
|
||||
|
||||
#[dom_struct]
|
||||
|
@ -52,10 +55,15 @@ impl Range {
|
|||
start_container: &Node, start_offset: u32,
|
||||
end_container: &Node, end_offset: u32)
|
||||
-> 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),
|
||||
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
|
||||
|
@ -121,20 +129,32 @@ impl Range {
|
|||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-range-bp-set
|
||||
pub fn set_start(&self, node: &Node, offset: u32) {
|
||||
self.start.set(node, offset);
|
||||
if !(self.start <= self.end) {
|
||||
self.end.set(node, offset);
|
||||
fn set_start(&self, node: &Node, offset: u32) {
|
||||
if &self.start.node != node {
|
||||
if self.start.node == self.end.node {
|
||||
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
|
||||
pub fn set_end(&self, node: &Node, offset: u32) {
|
||||
self.end.set(node, offset);
|
||||
if !(self.end >= self.start) {
|
||||
self.start.set(node, offset);
|
||||
fn set_end(&self, node: &Node, offset: u32) {
|
||||
if &self.end.node != node {
|
||||
if self.end.node == self.start.node {
|
||||
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
|
||||
fn compare_point(&self, node: &Node, offset: u32) -> Fallible<Ordering> {
|
||||
|
@ -215,8 +235,12 @@ impl RangeMethods for Range {
|
|||
// Step 2.
|
||||
Err(Error::IndexSize)
|
||||
} else {
|
||||
// Step 3-4.
|
||||
// Step 3.
|
||||
self.set_start(node, offset);
|
||||
if !(self.start <= self.end) {
|
||||
// Step 4.
|
||||
self.set_end(node, offset);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -230,8 +254,12 @@ impl RangeMethods for Range {
|
|||
// Step 2.
|
||||
Err(Error::IndexSize)
|
||||
} else {
|
||||
// Step 3-4.
|
||||
// Step 3.
|
||||
self.set_end(node, offset);
|
||||
if !(self.end >= self.start) {
|
||||
// Step 4.
|
||||
self.set_start(node, offset);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -263,9 +291,9 @@ impl RangeMethods for Range {
|
|||
// https://dom.spec.whatwg.org/#dom-range-collapse
|
||||
fn Collapse(&self, to_start: bool) {
|
||||
if to_start {
|
||||
self.end.set(&self.StartContainer(), self.StartOffset());
|
||||
self.set_end(&self.StartContainer(), self.StartOffset());
|
||||
} 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.
|
||||
let index = node.index();
|
||||
// Step 4.
|
||||
self.start.set(&parent, index);
|
||||
self.set_start(&parent, index);
|
||||
// Step 5.
|
||||
self.end.set(&parent, index + 1);
|
||||
self.set_end(&parent, index + 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -291,9 +319,9 @@ impl RangeMethods for Range {
|
|||
// Step 2.
|
||||
let length = node.len();
|
||||
// Step 3.
|
||||
self.start.set(node, 0);
|
||||
self.set_start(node, 0);
|
||||
// Step 4.
|
||||
self.end.set(node, length);
|
||||
self.set_end(node, length);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -833,10 +861,12 @@ impl BoundaryPoint {
|
|||
}
|
||||
}
|
||||
|
||||
fn set(&self, node: &Node, offset: u32) {
|
||||
debug_assert!(!node.is_doctype());
|
||||
debug_assert!(offset <= node.len());
|
||||
pub fn set(&self, node: &Node, offset: u32) {
|
||||
self.node.set(node);
|
||||
self.set_offset(offset);
|
||||
}
|
||||
|
||||
pub fn set_offset(&self, offset: u32) {
|
||||
self.offset.set(offset);
|
||||
}
|
||||
}
|
||||
|
@ -894,3 +924,259 @@ fn bp_position(a_node: &Node, a_offset: u32,
|
|||
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() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,15 +62,18 @@ impl TextMethods for Text {
|
|||
// Step 6.
|
||||
let parent = node.GetParentNode();
|
||||
if let Some(ref parent) = parent {
|
||||
// Step 7.
|
||||
// Step 7.1.
|
||||
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.
|
||||
cdata.DeleteData(offset, count).unwrap();
|
||||
if parent.is_none() {
|
||||
// Step 9.
|
||||
// TODO: Ranges
|
||||
node.ranges().clamp_above(&node, offset);
|
||||
}
|
||||
// Step 10.
|
||||
Ok(new_node)
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#![feature(custom_derive)]
|
||||
#![feature(fnbox)]
|
||||
#![feature(hashmap_hasher)]
|
||||
#![feature(iter_arith)]
|
||||
#![feature(mpsc_select)]
|
||||
#![feature(nonzero)]
|
||||
#![feature(on_unimplemented)]
|
||||
|
|
|
@ -38,10 +38,10 @@ macro_rules! sizeof_checker (
|
|||
|
||||
// Update the sizes here
|
||||
sizeof_checker!(size_event_target, EventTarget, 40);
|
||||
sizeof_checker!(size_node, Node, 160);
|
||||
sizeof_checker!(size_element, Element, 304);
|
||||
sizeof_checker!(size_htmlelement, HTMLElement, 320);
|
||||
sizeof_checker!(size_div, HTMLDivElement, 320);
|
||||
sizeof_checker!(size_span, HTMLSpanElement, 320);
|
||||
sizeof_checker!(size_text, Text, 192);
|
||||
sizeof_checker!(size_characterdata, CharacterData, 192);
|
||||
sizeof_checker!(size_node, Node, 184);
|
||||
sizeof_checker!(size_element, Element, 328);
|
||||
sizeof_checker!(size_htmlelement, HTMLElement, 344);
|
||||
sizeof_checker!(size_div, HTMLDivElement, 344);
|
||||
sizeof_checker!(size_span, HTMLSpanElement, 344);
|
||||
sizeof_checker!(size_text, Text, 216);
|
||||
sizeof_checker!(size_characterdata, CharacterData, 216);
|
||||
|
|
|
@ -1,23 +1,5 @@
|
|||
[Range-deleteContents.html]
|
||||
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\]]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -30,27 +12,6 @@
|
|||
[Resulting DOM for range 24 [document, 0, document, 2\]]
|
||||
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\]]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -66,21 +27,6 @@
|
|||
[Resulting DOM for range 27 [foreignDoc, 1, foreignComment, 2\]]
|
||||
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\]]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,23 +1,5 @@
|
|||
[Range-extractContents.html]
|
||||
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\]]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -27,42 +9,6 @@
|
|||
[Resulting cursor position for range 20 [paras[0\].firstChild, 3, paras[3\], 1\]]
|
||||
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\]]
|
||||
expected: FAIL
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue