Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ranges observe mutations on document #6639

Closed
wants to merge 7 commits into from
Next

Implement Range#cloneContents

  • Loading branch information
dzbarsky committed Jul 14, 2015
commit 18fa0a081278902aea4c73f5a0596cdafd026cc6
@@ -2,18 +2,21 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeConstants;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::Bindings::RangeBinding::{self, RangeConstants};
use dom::bindings::codegen::Bindings::RangeBinding::RangeMethods;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::codegen::InheritTypes::NodeCast;
use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeCast};
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::error::Error::HierarchyRequest;
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, Root};
use dom::bindings::utils::{Reflector, reflect_dom_object};
use dom::document::{Document, DocumentHelpers};
use dom::node::{Node, NodeHelpers};
use dom::documentfragment::DocumentFragment;
use dom::node::{Node, NodeHelpers, NodeTypeId};

use std::cell::RefCell;
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
@@ -57,6 +60,29 @@ impl Range {
let document = global.as_window().Document();
Ok(Range::new_with_doc(document.r()))
}

// https://dom.spec.whatwg.org/#contained
fn contains(self: &Range, node: &Node) -> bool {
let inner = self.inner.borrow();
let start = &inner.start;
let end = &inner.end;
match (bp_position(node, 0, start.node().r(), start.offset()).unwrap(),
bp_position(node, node.len(), end.node().r(), end.offset()).unwrap()) {
(Ordering::Greater, Ordering::Less) => {
true
},
_ => {
false
}
}
}

// https://dom.spec.whatwg.org/#partially-contained
fn partially_contains(self: &Range, node: &Node) -> bool {
let inner = self.inner.borrow();
inner.start.node().inclusive_ancestors().any(|n| n.r() == node) !=
inner.end.node().inclusive_ancestors().any(|n| n.r() == node)
}
}

pub trait RangeHelpers<'a> {
@@ -247,7 +273,7 @@ impl<'a> RangeMethods for &'a Range {
})
}

// https://dom.spec.whatwg.org/#dom-range-intersectsnodenode
// https://dom.spec.whatwg.org/#dom-range-intersectsnode
fn IntersectsNode(self, node: &Node) -> bool {
let inner = self.inner().borrow();
let start = &inner.start;
@@ -284,6 +310,163 @@ impl<'a> RangeMethods for &'a Range {
}
}

// https://dom.spec.whatwg.org/#dom-range-clonecontents
// https://dom.spec.whatwg.org/#concept-range-clone
fn CloneContents(self) -> Fallible<Root<DocumentFragment>> {
let inner = self.inner.borrow();
let start = &inner.start;
let end = &inner.end;

// Step 3.
let start_node = start.node();
let start_offset = start.offset();
let end_node = end.node();
let end_offset = end.offset();

// Step 1.
let fragment = DocumentFragment::new(start_node.r().owner_doc().r());

// Step 2.
if start == end {
return Ok(fragment);
}

if end_node == start_node {
match end_node.r().type_id() {
NodeTypeId::CharacterData(_) => {
// Step 4.1.
let clone = start_node.r().CloneNode(true);
// Step 4.2.
let text = CharacterDataCast::to_ref(start_node.r())
.unwrap()
.SubstringData(start_offset,
end_offset - start_offset);
CharacterDataCast::to_ref(clone.r()).unwrap().SetData(text.unwrap());
// Step 4.3.
try!(NodeCast::from_ref(fragment.r()).AppendChild(clone.r()));
// Step 4.4
return Ok(fragment);
},
_ => ()
}
}

// Steps 5-6.
let common_ancestor =
start_node.inclusive_ancestors()
.find(|node| node.is_inclusive_ancestor_of(end_node.r()))
.unwrap();

let first_contained_child =
if start_node.is_inclusive_ancestor_of(end_node.r()) {
// Step 7.
None
} else {
// Step 8.
common_ancestor.children()
.find(|node| Range::partially_contains(self, node))
};

let last_contained_child =
if end_node.is_inclusive_ancestor_of(start_node.r()) {
// Step 9.
None
} else {
// Step 10.
common_ancestor.rev_children()
.find(|node| Range::partially_contains(self, node))
};

// Step 11.
let contained_children =
common_ancestor.children().filter(|n| Range::contains(self, n));

// Step 12.
if common_ancestor.children()
.filter(|n| Range::contains(self, n))
.any(|n| n.is_doctype()) {
return Err(HierarchyRequest);
}

if let Some(child) = first_contained_child {
match child.r().type_id() {
// Step 13.
NodeTypeId::CharacterData(_) => {
// Step 13.1.
let clone = start_node.r().CloneNode(true);
// Step 13.2.
let text = CharacterDataCast::to_ref(start_node.r())
.unwrap()
.SubstringData(start_offset,
start_node.len() - start_offset);
CharacterDataCast::to_ref(clone.r()).unwrap().SetData(text.unwrap());
// Step 13.3.
try!(NodeCast::from_ref(fragment.r()).AppendChild(clone.r()));
},
_ => {
// Step 14.1.
let clone = child.CloneNode(true);
// Step 14.2.
try!(NodeCast::from_ref(fragment.r()).AppendChild(clone.r()));
// Step 14.3.
let subrange = Range::new(clone.owner_doc().r(),
start_node.r(),
start_offset,
child.r(),
child.len());
// Step 14.4.
let subfragment = try!(subrange.CloneContents());
// Step 14.5.
try!(clone.AppendChild(NodeCast::from_ref(subfragment.r())));
}
}
}

// Step 15.
for child in contained_children {
// Step 15.1.
let clone = child.CloneNode(true);
// Step 15.2.
try!(NodeCast::from_ref(fragment.r()).AppendChild(clone.r()));
}

if let Some(child) = last_contained_child {
match child.r().type_id() {
// Step 16.
NodeTypeId::CharacterData(_) => {
// Step 16.1.
let clone = end_node.r().CloneNode(true);
// Step 16.2.
let text = CharacterDataCast::to_ref(end_node.r())
.unwrap()
.SubstringData(0, end_offset);
CharacterDataCast::to_ref(clone.r()).unwrap().SetData(text.unwrap());
// Step 16.3.
try!(NodeCast::from_ref(fragment.r()).AppendChild(clone.r()));
},
_ => {
// Step 17.1.
let clone = child.CloneNode(true);
// Step 17.2.
try!(NodeCast::from_ref(fragment.r()).AppendChild(clone.r()));
// Step 17.3.
let subrange = Range::new(clone.owner_doc().r(),
child.r(),
0,
end_node.r(),
end_offset);
// Step 17.4.
let subfragment = try!(subrange.CloneContents());
// Step 17.5.
try!(clone.AppendChild(NodeCast::from_ref(subfragment.r())));
}
}
}

// Step 18.
Ok(fragment)
}

// http://dom.spec.whatwg.org/#dom-range-detach
fn Detach(self) {
// This method intentionally left blank.
@@ -46,8 +46,8 @@ interface Range {
// void deleteContents();
// [NewObject, Throws]
// DocumentFragment extractContents();
// [NewObject, Throws]
// DocumentFragment cloneContents();
[NewObject, Throws]
DocumentFragment cloneContents();
// [Throws]
// void insertNode(Node node);
// [Throws]
@@ -46,10 +46,11 @@
var originalEndOffset = range.endOffset;

// "If original start node and original end node are the same, and they are
// a Text or Comment node:"
// a Text, Processing Instruction, or Comment node:"
if (range.startContainer == range.endContainer
&& (range.startContainer.nodeType == Node.TEXT_NODE
|| range.startContainer.nodeType == Node.COMMENT_NODE)) {
|| range.startContainer.nodeType == Node.COMMENT_NODE
|| range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE)) {
// "Let clone be the result of calling cloneNode(false) on original
// start node."
var clone = originalStartNode.cloneNode(false);
@@ -130,10 +131,11 @@
}
}

// "If first partially contained child is a Text or Comment node:"
// "If first partially contained child is a Text, ProcessingInstruction, or Comment node:"
if (firstPartiallyContainedChild
&& (firstPartiallyContainedChild.nodeType == Node.TEXT_NODE
|| firstPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
|| firstPartiallyContainedChild.nodeType == Node.COMMENT_NODE
|| firstPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE)) {
// "Let clone be the result of calling cloneNode(false) on original
// start node."
var clone = originalStartNode.cloneNode(false);
@@ -185,10 +187,11 @@
frag.appendChild(clone);
}

// "If last partially contained child is a Text or Comment node:"
// "If last partially contained child is a Text, ProcessingInstruction, or Comment node:"
if (lastPartiallyContainedChild
&& (lastPartiallyContainedChild.nodeType == Node.TEXT_NODE
|| lastPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
|| lastPartiallyContainedChild.nodeType == Node.COMMENT_NODE
|| lastPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE)) {
// "Let clone be the result of calling cloneNode(false) on original
// end node."
var clone = originalEndNode.cloneNode(false);
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.