diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2015-12-26 04:08:15 +0530 |
---|---|---|
committer | bors-servo <lbergstrom+bors@mozilla.com> | 2015-12-26 04:08:15 +0530 |
commit | 89ab368258eb827b0dcc8d6e6deecd3ed3c1de71 (patch) | |
tree | cf25fa3290e5c6c9b60ddeef014c54c334a28201 /components/script | |
parent | 7db6ce41d2a0748285d2a3c3f4141e8bd30ee8ef (diff) | |
parent | 3c768356159c19a4ce5f0a07684b6dee9b20f2e4 (diff) | |
download | servo-89ab368258eb827b0dcc8d6e6deecd3ed3c1de71.tar.gz servo-89ab368258eb827b0dcc8d6e6deecd3ed3c1de71.zip |
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 -->
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/bindings/codegen/Bindings.conf | 4 | ||||
-rw-r--r-- | components/script/dom/bindings/weakref.rs | 99 | ||||
-rw-r--r-- | components/script/dom/characterdata.rs | 66 | ||||
-rw-r--r-- | components/script/dom/element.rs | 10 | ||||
-rw-r--r-- | components/script/dom/htmlbaseelement.rs | 8 | ||||
-rw-r--r-- | components/script/dom/htmlbuttonelement.rs | 8 | ||||
-rw-r--r-- | components/script/dom/htmliframeelement.rs | 8 | ||||
-rw-r--r-- | components/script/dom/htmlinputelement.rs | 8 | ||||
-rw-r--r-- | components/script/dom/htmloptionelement.rs | 8 | ||||
-rw-r--r-- | components/script/dom/htmlselectelement.rs | 8 | ||||
-rw-r--r-- | components/script/dom/htmltextareaelement.rs | 8 | ||||
-rw-r--r-- | components/script/dom/node.rs | 223 | ||||
-rw-r--r-- | components/script/dom/range.rs | 392 | ||||
-rw-r--r-- | components/script/dom/text.rs | 9 | ||||
-rw-r--r-- | components/script/dom/virtualmethods.rs | 6 | ||||
-rw-r--r-- | components/script/lib.rs | 1 |
16 files changed, 662 insertions, 204 deletions
diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf index 00ccea7fb63..dfb31685fd1 100644 --- a/components/script/dom/bindings/codegen/Bindings.conf +++ b/components/script/dom/bindings/codegen/Bindings.conf @@ -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': {}, diff --git a/components/script/dom/bindings/weakref.rs b/components/script/dom/bindings/weakref.rs index 248944d9962..e311d4a40bb 100644 --- a/components/script/dom/bindings/weakref.rs +++ b/components/script/dom/bindings/weakref.rs @@ -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; + } +} diff --git a/components/script/dom/characterdata.rs b/components/script/dom/characterdata.rs index 8ed455c507a..d9798f76a11 100644 --- a/components/script/dom/characterdata.rs +++ b/components/script/dom/characterdata.rs @@ -6,13 +6,18 @@ use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; +use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; +use dom::bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId}; use dom::bindings::codegen::UnionTypes::NodeOrString; use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{LayoutJS, Root}; +use dom::comment::Comment; use dom::document::Document; use dom::element::Element; use dom::node::{Node, NodeDamage}; +use dom::processinginstruction::ProcessingInstruction; +use dom::text::Text; use std::cell::Ref; use util::str::DOMString; @@ -30,6 +35,38 @@ impl CharacterData { data: DOMRefCell::new(data), } } + + pub fn clone_with_data(&self, data: DOMString, document: &Document) -> Root<Node> { + match self.upcast::<Node>().type_id() { + NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => { + Root::upcast(Comment::new(data, &document)) + } + NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => { + let pi = self.downcast::<ProcessingInstruction>().unwrap(); + Root::upcast(ProcessingInstruction::new(pi.Target(), data, &document)) + }, + NodeTypeId::CharacterData(CharacterDataTypeId::Text) => { + Root::upcast(Text::new(data, &document)) + }, + _ => unreachable!(), + } + } + + #[inline] + pub fn data(&self) -> Ref<DOMString> { + self.data.borrow() + } + + #[inline] + pub fn append_data(&self, data: &str) { + self.data.borrow_mut().push_str(data); + self.content_changed(); + } + + fn content_changed(&self) { + let node = self.upcast::<Node>(); + node.owner_doc().content_changed(node, NodeDamage::OtherNodeDamage); + } } impl CharacterDataMethods for CharacterData { @@ -40,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 @@ -107,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(()) } @@ -143,24 +187,6 @@ impl CharacterDataMethods for CharacterData { } } -impl CharacterData { - #[inline] - pub fn data(&self) -> Ref<DOMString> { - self.data.borrow() - } - #[inline] - pub fn append_data(&self, data: &str) { - // FIXME(ajeffrey): Efficient append on DOMStrings? - self.data.borrow_mut().push_str(data); - self.content_changed(); - } - - fn content_changed(&self) { - let node = self.upcast::<Node>(); - node.owner_doc().content_changed(node, NodeDamage::OtherNodeDamage); - } -} - #[allow(unsafe_code)] pub trait LayoutCharacterDataHelpers { unsafe fn data_for_layout(&self) -> &str; diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 0293281892d..6f766ea961c 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -53,7 +53,7 @@ use dom::htmltemplateelement::HTMLTemplateElement; use dom::htmltextareaelement::{HTMLTextAreaElement, RawLayoutHTMLTextAreaElementHelpers}; use dom::namednodemap::NamedNodeMap; use dom::node::{CLICK_IN_PROGRESS, LayoutNodeHelpers, Node}; -use dom::node::{NodeDamage, SEQUENTIALLY_FOCUSABLE}; +use dom::node::{NodeDamage, SEQUENTIALLY_FOCUSABLE, UnbindContext}; use dom::node::{document_from_node, window_from_node}; use dom::nodelist::NodeList; use dom::text::Text; @@ -1615,12 +1615,10 @@ impl VirtualMethods for Element { } } - fn unbind_from_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.unbind_from_tree(tree_in_doc); - } + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); - if !tree_in_doc { + if !context.tree_in_doc { return; } diff --git a/components/script/dom/htmlbaseelement.rs b/components/script/dom/htmlbaseelement.rs index c281d1269ad..7f1ce7641d3 100644 --- a/components/script/dom/htmlbaseelement.rs +++ b/components/script/dom/htmlbaseelement.rs @@ -9,7 +9,7 @@ use dom::bindings::js::Root; use dom::document::Document; use dom::element::{AttributeMutation, Element}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, document_from_node}; +use dom::node::{Node, UnbindContext, document_from_node}; use dom::virtualmethods::VirtualMethods; use string_cache::Atom; use url::Url; @@ -77,8 +77,8 @@ impl VirtualMethods for HTMLBaseElement { self.bind_unbind(tree_in_doc); } - fn unbind_from_tree(&self, tree_in_doc: bool) { - self.super_type().unwrap().unbind_from_tree(tree_in_doc); - self.bind_unbind(tree_in_doc); + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); + self.bind_unbind(context.tree_in_doc); } } diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 07c508bd03d..ad60d8fb38c 100644 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -16,7 +16,7 @@ use dom::htmlelement::HTMLElement; use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlformelement::{FormControl, FormSubmitter}; use dom::htmlformelement::{SubmittedFrom, HTMLFormElement}; -use dom::node::{Node, document_from_node, window_from_node}; +use dom::node::{Node, UnbindContext, document_from_node, window_from_node}; use dom::nodelist::NodeList; use dom::validitystate::ValidityState; use dom::virtualmethods::VirtualMethods; @@ -174,10 +174,8 @@ impl VirtualMethods for HTMLButtonElement { self.upcast::<Element>().check_ancestors_disabled_state_for_form_control(); } - fn unbind_from_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.unbind_from_tree(tree_in_doc); - } + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index c51e98d8f92..12a432f4c91 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -20,7 +20,7 @@ use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; use dom::event::Event; use dom::eventtarget::EventTarget; use dom::htmlelement::HTMLElement; -use dom::node::{Node, window_from_node}; +use dom::node::{Node, UnbindContext, window_from_node}; use dom::urlhelper::UrlHelper; use dom::virtualmethods::VirtualMethods; use dom::window::Window; @@ -483,10 +483,8 @@ impl VirtualMethods for HTMLIFrameElement { } } - fn unbind_from_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.unbind_from_tree(tree_in_doc); - } + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded if let Some(pipeline_id) = self.pipeline_id.get() { diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index a7143133b05..9413699ede7 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -23,7 +23,7 @@ use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlformelement::{FormControl, FormDatum, FormSubmitter, HTMLFormElement}; use dom::htmlformelement::{ResetFrom, SubmittedFrom}; use dom::keyboardevent::KeyboardEvent; -use dom::node::{Node, NodeDamage}; +use dom::node::{Node, NodeDamage, UnbindContext}; use dom::node::{document_from_node, window_from_node}; use dom::nodelist::NodeList; use dom::virtualmethods::VirtualMethods; @@ -645,10 +645,8 @@ impl VirtualMethods for HTMLInputElement { self.upcast::<Element>().check_ancestors_disabled_state_for_form_control(); } - fn unbind_from_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.unbind_from_tree(tree_in_doc); - } + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); diff --git a/components/script/dom/htmloptionelement.rs b/components/script/dom/htmloptionelement.rs index 67e58b3d46e..3aff70c70f7 100644 --- a/components/script/dom/htmloptionelement.rs +++ b/components/script/dom/htmloptionelement.rs @@ -15,7 +15,7 @@ use dom::element::{AttributeMutation, Element}; use dom::htmlelement::HTMLElement; use dom::htmlscriptelement::HTMLScriptElement; use dom::htmlselectelement::HTMLSelectElement; -use dom::node::Node; +use dom::node::{Node, UnbindContext}; use dom::text::Text; use dom::virtualmethods::VirtualMethods; use selectors::states::*; @@ -207,10 +207,8 @@ impl VirtualMethods for HTMLOptionElement { self.pick_if_selected_and_reset(); } - fn unbind_from_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.unbind_from_tree(tree_in_doc); - } + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index a9310997851..daf4d6f9ba5 100644 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -16,7 +16,7 @@ use dom::htmlelement::HTMLElement; use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlformelement::{FormControl, HTMLFormElement}; use dom::htmloptionelement::HTMLOptionElement; -use dom::node::{Node, window_from_node}; +use dom::node::{Node, UnbindContext, window_from_node}; use dom::nodelist::NodeList; use dom::validitystate::ValidityState; use dom::virtualmethods::VirtualMethods; @@ -196,10 +196,8 @@ impl VirtualMethods for HTMLSelectElement { self.upcast::<Element>().check_ancestors_disabled_state_for_form_control(); } - fn unbind_from_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.unbind_from_tree(tree_in_doc); - } + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 386d52f591d..8d856ff6b80 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -20,7 +20,7 @@ use dom::htmlelement::HTMLElement; use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlformelement::{FormControl, HTMLFormElement}; use dom::keyboardevent::KeyboardEvent; -use dom::node::{ChildrenMutation, Node, NodeDamage}; +use dom::node::{ChildrenMutation, Node, NodeDamage, UnbindContext}; use dom::node::{document_from_node, window_from_node}; use dom::nodelist::NodeList; use dom::virtualmethods::VirtualMethods; @@ -300,10 +300,8 @@ impl VirtualMethods for HTMLTextAreaElement { } } - fn unbind_from_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.unbind_from_tree(tree_in_doc); - } + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index c54d85e8b28..a2a39979ad2 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -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 document = document_from_node(self); - Node::adopt(node, document.r()); + let previous_sibling = child.GetPreviousSibling(); // Step 10. - let previous_sibling = child.GetPreviousSibling(); + let document = document_from_node(self); + Node::adopt(node, document.r()); - // 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 + } +} diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs index 58200ee35f2..03db51ba0d8 100644 --- a/components/script/dom/range.rs +++ b/components/script/dom/range.rs @@ -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, - end_container, end_offset), - GlobalRef::Window(document.window()), - RangeBinding::Wrap) + let range = reflect_dom_object(box Range::new_inherited(start_container, start_offset, + end_container, end_offset), + GlobalRef::Window(document.window()), + 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,19 +129,31 @@ 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 @@ -206,7 +226,7 @@ impl RangeMethods for Range { unreachable!(); } - // https://dom.spec.whatwg.org/#dom-range-setstartnode-offset + // https://dom.spec.whatwg.org/#dom-range-setstart fn SetStart(&self, node: &Node, offset: u32) -> ErrorResult { if node.is_doctype() { // Step 1. @@ -215,13 +235,17 @@ 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(()) } } - // https://dom.spec.whatwg.org/#dom-range-setendnode-offset + // https://dom.spec.whatwg.org/#dom-range-setend fn SetEnd(&self, node: &Node, offset: u32) -> ErrorResult { if node.is_doctype() { // Step 1. @@ -230,59 +254,63 @@ 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(()) } } - // https://dom.spec.whatwg.org/#dom-range-setstartbeforenode + // https://dom.spec.whatwg.org/#dom-range-setstartbefore fn SetStartBefore(&self, node: &Node) -> ErrorResult { let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); self.SetStart(parent.r(), node.index()) } - // https://dom.spec.whatwg.org/#dom-range-setstartafternode + // https://dom.spec.whatwg.org/#dom-range-setstartafter fn SetStartAfter(&self, node: &Node) -> ErrorResult { let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); self.SetStart(parent.r(), node.index() + 1) } - // https://dom.spec.whatwg.org/#dom-range-setendbeforenode + // https://dom.spec.whatwg.org/#dom-range-setendbefore fn SetEndBefore(&self, node: &Node) -> ErrorResult { let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); self.SetEnd(parent.r(), node.index()) } - // https://dom.spec.whatwg.org/#dom-range-setendafternode + // https://dom.spec.whatwg.org/#dom-range-setendafter fn SetEndAfter(&self, node: &Node) -> ErrorResult { let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); self.SetEnd(parent.r(), node.index() + 1) } - // https://dom.spec.whatwg.org/#dom-range-collapsetostart + // 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()); } } - // https://dom.spec.whatwg.org/#dom-range-selectnodenode + // https://dom.spec.whatwg.org/#dom-range-selectnode fn SelectNode(&self, node: &Node) -> ErrorResult { // Steps 1, 2. let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); // 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(()) } - // https://dom.spec.whatwg.org/#dom-range-selectnodecontentsnode + // https://dom.spec.whatwg.org/#dom-range-selectnodecontents fn SelectNodeContents(&self, node: &Node) -> ErrorResult { if node.is_doctype() { // Step 1. @@ -291,13 +319,13 @@ 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(()) } - // https://dom.spec.whatwg.org/#dom-range-compareboundarypointshow-sourcerange + // https://dom.spec.whatwg.org/#dom-range-compareboundarypoints fn CompareBoundaryPoints(&self, how: u16, other: &Range) -> Fallible<i16> { if how > RangeConstants::END_TO_START { @@ -342,7 +370,7 @@ impl RangeMethods for Range { &self.EndContainer(), self.EndOffset()) } - // https://dom.spec.whatwg.org/#dom-range-ispointinrangenode-offset + // https://dom.spec.whatwg.org/#dom-range-ispointinrange fn IsPointInRange(&self, node: &Node, offset: u32) -> Fallible<bool> { match self.compare_point(node, offset) { Ok(Ordering::Less) => Ok(false), @@ -356,7 +384,7 @@ impl RangeMethods for Range { } } - // https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset + // https://dom.spec.whatwg.org/#dom-range-comparepoint fn ComparePoint(&self, node: &Node, offset: u32) -> Fallible<i16> { self.compare_point(node, offset).map(|order| { match order { @@ -410,12 +438,10 @@ impl RangeMethods for Range { } if end_node == start_node { - if let Some(text) = start_node.downcast::<CharacterData>() { - // Step 4.1. - let clone = start_node.CloneNode(true); - // Step 4.2. - let text = text.SubstringData(start_offset, end_offset - start_offset); - clone.downcast::<CharacterData>().unwrap().SetData(text.unwrap()); + if let Some(cdata) = start_node.downcast::<CharacterData>() { + // Steps 4.1-2. + let data = cdata.SubstringData(start_offset, end_offset - start_offset).unwrap(); + let clone = cdata.clone_with_data(data, &start_node.owner_doc()); // Step 4.3. try!(fragment.upcast::<Node>().AppendChild(&clone)); // Step 4.4 @@ -429,13 +455,11 @@ impl RangeMethods for Range { if let Some(child) = first_contained_child { // Step 13. - if let Some(text) = child.downcast::<CharacterData>() { + if let Some(cdata) = child.downcast::<CharacterData>() { assert!(child == start_node); - // Step 13.1. - let clone = start_node.CloneNode(true); // CharacterData has no children. - // Step 13.2 - let text = text.SubstringData(start_offset, start_node.len() - start_offset); - clone.downcast::<CharacterData>().unwrap().SetData(text.unwrap()); + // Steps 13.1-2. + let data = cdata.SubstringData(start_offset, start_node.len() - start_offset).unwrap(); + let clone = cdata.clone_with_data(data, &start_node.owner_doc()); // Step 13.3. try!(fragment.upcast::<Node>().AppendChild(&clone)); } else { @@ -466,13 +490,11 @@ impl RangeMethods for Range { if let Some(child) = last_contained_child { // Step 16. - if let Some(text) = child.downcast::<CharacterData>() { + if let Some(cdata) = child.downcast::<CharacterData>() { assert!(child == end_node); - // Step 16.1. - let clone = end_node.CloneNode(true); // CharacterData has no children. - // Step 16.2. - let text = text.SubstringData(0, end_offset); - clone.downcast::<CharacterData>().unwrap().SetData(text.unwrap()); + // Steps 16.1-2. + let data = cdata.SubstringData(0, end_offset).unwrap(); + let clone = cdata.clone_with_data(data, &start_node.owner_doc()); // Step 16.3. try!(fragment.upcast::<Node>().AppendChild(&clone)); } else { @@ -839,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); } } @@ -900,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() } + } +} diff --git a/components/script/dom/text.rs b/components/script/dom/text.rs index ee92332752c..1c834f563cf 100644 --- a/components/script/dom/text.rs +++ b/components/script/dom/text.rs @@ -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) diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index d2a436606d7..3540fbddf0f 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -42,7 +42,7 @@ use dom::htmltablesectionelement::HTMLTableSectionElement; use dom::htmltemplateelement::HTMLTemplateElement; use dom::htmltextareaelement::HTMLTextAreaElement; use dom::htmltitleelement::HTMLTitleElement; -use dom::node::{ChildrenMutation, CloneChildrenFlag, Node}; +use dom::node::{ChildrenMutation, CloneChildrenFlag, Node, UnbindContext}; use string_cache::Atom; use util::str::DOMString; @@ -82,9 +82,9 @@ pub trait VirtualMethods { /// Called when a Node is removed from a tree, where 'tree_in_doc' /// indicates whether the tree is part of a Document. - fn unbind_from_tree(&self, tree_in_doc: bool) { + fn unbind_from_tree(&self, context: &UnbindContext) { if let Some(ref s) = self.super_type() { - s.unbind_from_tree(tree_in_doc); + s.unbind_from_tree(context); } } diff --git a/components/script/lib.rs b/components/script/lib.rs index 743b56be20f..ff07264362e 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -13,7 +13,6 @@ #![feature(custom_derive)] #![feature(fnbox)] #![feature(hashmap_hasher)] -#![feature(iter_arith)] #![feature(mpsc_select)] #![feature(nonzero)] #![feature(on_unimplemented)] |