diff --git a/servo/components/script/dom/bindings/error.rs b/servo/components/script/dom/bindings/error.rs index 3fe23eaca2aa..3ea2652ee23b 100644 --- a/servo/components/script/dom/bindings/error.rs +++ b/servo/components/script/dom/bindings/error.rs @@ -31,6 +31,8 @@ pub enum Error { NotFound, /// HierarchyRequestError DOMException HierarchyRequest, + /// WrongDocumentError DOMException + WrongDocument, /// InvalidCharacterError DOMException InvalidCharacter, /// NotSupportedError DOMException @@ -53,6 +55,8 @@ pub enum Error { Abort, /// TimeoutError DOMException Timeout, + /// InvalidNodeTypeError DOMException + InvalidNodeType, /// DataCloneError DOMException DataClone, /// NoModificationAllowedError DOMException @@ -81,6 +85,7 @@ pub fn throw_dom_exception(cx: *mut JSContext, global: GlobalRef, Error::IndexSize => DOMErrorName::IndexSizeError, Error::NotFound => DOMErrorName::NotFoundError, Error::HierarchyRequest => DOMErrorName::HierarchyRequestError, + Error::WrongDocument => DOMErrorName::WrongDocumentError, Error::InvalidCharacter => DOMErrorName::InvalidCharacterError, Error::NotSupported => DOMErrorName::NotSupportedError, Error::InUseAttribute => DOMErrorName::InUseAttributeError, @@ -92,6 +97,7 @@ pub fn throw_dom_exception(cx: *mut JSContext, global: GlobalRef, Error::Network => DOMErrorName::NetworkError, Error::Abort => DOMErrorName::AbortError, Error::Timeout => DOMErrorName::TimeoutError, + Error::InvalidNodeType => DOMErrorName::InvalidNodeTypeError, Error::DataClone => DOMErrorName::DataCloneError, Error::NoModificationAllowed => DOMErrorName::NoModificationAllowedError, Error::Type(message) => { diff --git a/servo/components/script/dom/document.rs b/servo/components/script/dom/document.rs index a414f782012d..692c597d7af4 100644 --- a/servo/components/script/dom/document.rs +++ b/servo/components/script/dom/document.rs @@ -1166,7 +1166,7 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { // https://dom.spec.whatwg.org/#dom-document-createrange fn CreateRange(self) -> Temporary { - Range::new(self) + Range::new_with_doc(self) } // https://dom.spec.whatwg.org/#dom-document-createtreewalker diff --git a/servo/components/script/dom/node.rs b/servo/components/script/dom/node.rs index 23914d3f4075..9143677d1e03 100644 --- a/servo/components/script/dom/node.rs +++ b/servo/components/script/dom/node.rs @@ -427,6 +427,8 @@ pub trait NodeHelpers { fn is_parent_of(self, child: JSRef) -> bool; fn type_id(self) -> NodeTypeId; + fn len(self) -> u32; + fn index(self) -> u32; fn parent_node(self) -> Option>; fn first_child(self) -> Option>; @@ -567,6 +569,22 @@ impl<'a> NodeHelpers for JSRef<'a, Node> { self.type_id } + // https://dom.spec.whatwg.org/#concept-node-length + fn len(self) -> u32 { + match self.type_id { + NodeTypeId::DocumentType => 0, + NodeTypeId::CharacterData(_) => { + CharacterDataCast::to_ref(self).unwrap().Length() + }, + _ => self.children().count() as u32 + } + } + + // https://dom.spec.whatwg.org/#concept-tree-index + fn index(self) -> u32 { + self.preceding_siblings().count() as u32 + } + fn parent_node(self) -> Option> { self.parent_node.get().map(Temporary::from_rooted) } diff --git a/servo/components/script/dom/range.rs b/servo/components/script/dom/range.rs index f79e6685a288..c9124f1eb3dd 100644 --- a/servo/components/script/dom/range.rs +++ b/servo/components/script/dom/range.rs @@ -2,44 +2,512 @@ * 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::RangeBinding; +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::error::Fallible; +use dom::bindings::codegen::InheritTypes::NodeCast; +use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::global::GlobalRef; -use dom::bindings::js::{JSRef, Rootable, Temporary}; +use dom::bindings::js::{JS, JSRef, MutHeap, Rootable, Temporary}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::document::{Document, DocumentHelpers}; +use dom::node::{Node, NodeHelpers}; + +use std::cell::RefCell; +use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; +use std::rc::Rc; #[dom_struct] pub struct Range { - reflector_: Reflector + reflector_: Reflector, + inner: Rc>, } impl Range { - fn new_inherited() -> Range { + fn new_inherited(start_container: JSRef, start_offset: u32, + end_container: JSRef, end_offset: u32) -> Range { Range { - reflector_: Reflector::new() + reflector_: Reflector::new(), + inner: Rc::new(RefCell::new(RangeInner::new( + BoundaryPoint::new(start_container, start_offset), + BoundaryPoint::new(end_container, end_offset)))), } } - pub fn new(document: JSRef) -> Temporary { + pub fn new_with_doc(document: JSRef) -> Temporary { + let root = NodeCast::from_ref(document); + Range::new(document, root, 0, root, 0) + } + + pub fn new(document: JSRef, + start_container: JSRef, start_offset: u32, + end_container: JSRef, end_offset: u32) + -> Temporary { let window = document.window().root(); - reflect_dom_object(box Range::new_inherited(), + reflect_dom_object(box Range::new_inherited(start_container, start_offset, + end_container, end_offset), GlobalRef::Window(window.r()), RangeBinding::Wrap) } + // https://dom.spec.whatwg.org/#dom-range pub fn Constructor(global: GlobalRef) -> Fallible> { let document = global.as_window().Document().root(); - Ok(Range::new(document.r())) + Ok(Range::new_with_doc(document.r())) + } +} + +pub trait RangeHelpers<'a> { + fn inner(self) -> &'a Rc>; +} + +impl<'a> RangeHelpers<'a> for JSRef<'a, Range> { + fn inner(self) -> &'a Rc> { + &self.extended_deref().inner } } impl<'a> RangeMethods for JSRef<'a, Range> { - /// https://dom.spec.whatwg.org/#dom-range-detach + // http://dom.spec.whatwg.org/#dom-range-startcontainer + fn StartContainer(self) -> Temporary { + self.inner().borrow().start.node() + } + + /// http://dom.spec.whatwg.org/#dom-range-startoffset + fn StartOffset(self) -> u32 { + self.inner().borrow().start.offset + } + + /// http://dom.spec.whatwg.org/#dom-range-endcontainer + fn EndContainer(self) -> Temporary { + self.inner().borrow().end.node() + } + + /// http://dom.spec.whatwg.org/#dom-range-endoffset + fn EndOffset(self) -> u32 { + self.inner().borrow().end.offset + } + + // https://dom.spec.whatwg.org/#dom-range-collapsed + fn Collapsed(self) -> bool { + let inner = self.inner().borrow(); + inner.start == inner.end + } + + // https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer + fn CommonAncestorContainer(self) -> Temporary { + self.inner().borrow().common_ancestor_container() + } + + // https://dom.spec.whatwg.org/#dom-range-setstartnode-offset + fn SetStart(self, node: JSRef, offset: u32) -> ErrorResult { + if node.is_doctype() { + // Step 1. + Err(Error::InvalidNodeType) + } else if offset > node.len() { + // Step 2. + Err(Error::IndexSize) + } else { + // Step 3-4. + self.inner().borrow_mut().set_start(node, offset); + Ok(()) + } + } + + // https://dom.spec.whatwg.org/#dom-range-setendnode-offset + fn SetEnd(self, node: JSRef, offset: u32) -> ErrorResult { + if node.is_doctype() { + // Step 1. + Err(Error::InvalidNodeType) + } else if offset > node.len() { + // Step 2. + Err(Error::IndexSize) + } else { + // Step 3-4. + self.inner().borrow_mut().set_end(node, offset); + Ok(()) + } + } + + // https://dom.spec.whatwg.org/#dom-range-setstartbeforenode + fn SetStartBefore(self, node: JSRef) -> ErrorResult { + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + self.SetStart(parent.r(), node.index()) + } + + // https://dom.spec.whatwg.org/#dom-range-setstartafternode + fn SetStartAfter(self, node: JSRef) -> ErrorResult { + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + self.SetStart(parent.r(), node.index() + 1) + } + + // https://dom.spec.whatwg.org/#dom-range-setendbeforenode + fn SetEndBefore(self, node: JSRef) -> ErrorResult { + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + self.SetEnd(parent.r(), node.index()) + } + + // https://dom.spec.whatwg.org/#dom-range-setendafternode + fn SetEndAfter(self, node: JSRef) -> ErrorResult { + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + self.SetEnd(parent.r(), node.index() + 1) + } + + // https://dom.spec.whatwg.org/#dom-range-collapsetostart + fn Collapse(self, to_start: bool) { + self.inner().borrow_mut().collapse(to_start); + } + + // https://dom.spec.whatwg.org/#dom-range-selectnodenode + fn SelectNode(self, node: JSRef) -> ErrorResult { + self.inner().borrow_mut().select_node(node) + } + + // https://dom.spec.whatwg.org/#dom-range-selectnodecontentsnode + fn SelectNodeContents(self, node: JSRef) -> ErrorResult { + self.inner().borrow_mut().select_node_contents(node) + } + + // https://dom.spec.whatwg.org/#dom-range-compareboundarypointshow-sourcerange + fn CompareBoundaryPoints(self, how: u16, source_range: JSRef) + -> Fallible { + if how > RangeConstants::END_TO_START { + // Step 1. + return Err(Error::NotSupported); + } + let this_inner = self.inner().borrow(); + let other_inner = source_range.inner().borrow(); + let this_start_node = this_inner.start.node().root(); + let other_start_node = other_inner.start.node().root(); + let this_root = this_start_node.r().inclusive_ancestors().last().unwrap(); + let other_root = other_start_node.r().inclusive_ancestors().last().unwrap(); + if this_root != other_root { + // Step 2. + return Err(Error::WrongDocument); + } + // Step 3. + let (this_point, other_point) = match how { + RangeConstants::START_TO_START => { + (&this_inner.start, &other_inner.start) + }, + RangeConstants::START_TO_END => { + (&this_inner.end, &other_inner.start) + }, + RangeConstants::END_TO_END => { + (&this_inner.end, &other_inner.end) + }, + RangeConstants::END_TO_START => { + (&this_inner.start, &other_inner.end) + }, + _ => unreachable!(), + }; + // step 4. + match this_point.partial_cmp(other_point).unwrap() { + Ordering::Less => Ok(-1), + Ordering::Equal => Ok(0), + Ordering::Greater => Ok(1), + } + } + + // https://dom.spec.whatwg.org/#dom-range-clonerange + fn CloneRange(self) -> Temporary { + let inner = self.inner().borrow(); + let start = &inner.start; + let end = &inner.end; + let start_node = start.node().root(); + let owner_doc = NodeCast::from_ref(start_node.r()).owner_doc().root(); + Range::new(owner_doc.r(), start_node.r(), start.offset, + end.node().root().r(), end.offset) + } + + // https://dom.spec.whatwg.org/#dom-range-ispointinrangenode-offset + fn IsPointInRange(self, node: JSRef, offset: u32) -> Fallible { + match self.inner().borrow().compare_point(node, offset) { + Ok(Ordering::Less) => Ok(false), + Ok(Ordering::Equal) => Ok(true), + Ok(Ordering::Greater) => Ok(false), + Err(Error::WrongDocument) => { + // Step 2. + Ok(false) + } + Err(error) => Err(error), + } + } + + // https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset + fn ComparePoint(self, node: JSRef, offset: u32) -> Fallible { + self.inner().borrow().compare_point(node, offset).map(|order| { + match order { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, + } + }) + } + + // https://dom.spec.whatwg.org/#dom-range-intersectsnodenode + fn IntersectsNode(self, node: JSRef) -> bool { + let inner = self.inner().borrow(); + let start = &inner.start; + let start_node = start.node().root(); + let start_offset = start.offset; + let start_node_root = start_node.r().inclusive_ancestors().last().unwrap().root(); + let node_root = node.inclusive_ancestors().last().unwrap().root(); + if start_node_root.r() != node_root.r() { + // Step 1. + return false; + } + let parent = match node.parent_node() { + Some(parent) => parent, + None => { + // Step 3. + return true; + }, + }.root(); + // Step 4. + let offset = node.index(); + let end = &inner.end; + let end_node = end.node().root(); + let end_offset = end.offset; + match (bp_position(parent.r(), offset + 1, start_node.r(), start_offset).unwrap(), + bp_position(parent.r(), offset, end_node.r(), end_offset).unwrap()) { + (Ordering::Greater, Ordering::Less) => { + // Step 5. + true + }, + _ => { + // Step 6. + false + } + } + } + + // http://dom.spec.whatwg.org/#dom-range-detach fn Detach(self) { // This method intentionally left blank. } } +#[jstraceable] +#[must_root] +#[privatize] +pub struct RangeInner { + start: BoundaryPoint, + end: BoundaryPoint, +} + +impl RangeInner { + fn new(start: BoundaryPoint, end: BoundaryPoint) -> RangeInner { + RangeInner { start: start, end: end } + } + + // https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer + fn common_ancestor_container(&self) -> Temporary { + let start_container = self.start.node().root(); + let end_container = self.end.node().root(); + // Step 1. + for container in start_container.r().inclusive_ancestors() { + // Step 2. + if container.root().r().is_inclusive_ancestor_of(end_container.r()) { + // Step 3. + return container; + } + } + unreachable!(); + } + + // https://dom.spec.whatwg.org/#concept-range-bp-set + pub fn set_start(&mut self, bp_node: JSRef, bp_offset: u32) { + // Steps 1-3 handled in Range caller. + let end_node = self.end.node().root(); + let end_offset = self.end.offset; + match bp_position(bp_node, bp_offset, end_node.r(), end_offset) { + None | Some(Ordering::Greater) => { + // Step 4-1. + self.end.set(bp_node, bp_offset); + }, + _ => {}, + }; + // Step 4-2. + self.start.set(bp_node, bp_offset); + } + + // https://dom.spec.whatwg.org/#concept-range-bp-set + pub fn set_end(&mut self, bp_node: JSRef, bp_offset: u32) { + // Steps 1-3 handled in Range caller. + let start_node = self.start.node().root(); + let start_offset = self.start.offset; + match bp_position(bp_node, bp_offset, start_node.r(), start_offset) { + None | Some(Ordering::Less) => { + // Step 4-1. + self.start.set(bp_node, bp_offset); + }, + _ => {}, + }; + // Step 4-2. + self.end.set(bp_node, bp_offset); + } + + // https://dom.spec.whatwg.org/#dom-range-collapsetostart + fn collapse(&mut self, to_start: bool) { + if to_start { + let start_node = self.start.node().root(); + self.end.set(start_node.r(), self.start.offset); + } else { + let end_node = self.end.node().root(); + self.start.set(end_node.r(), self.end.offset); + } + } + + // https://dom.spec.whatwg.org/#dom-range-selectnodenode + fn select_node(&mut self, node: JSRef) -> ErrorResult { + // Steps 1, 2. + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + // Step 3. + let index = node.index(); + // Step 4. + self.start.set(parent.r(), index); + // Step 5. + self.end.set(parent.r(), index + 1); + Ok(()) + } + + // https://dom.spec.whatwg.org/#dom-range-selectnodecontentsnode + fn select_node_contents(&mut self, node: JSRef) -> ErrorResult { + if node.is_doctype() { + // Step 1. + return Err(Error::InvalidNodeType); + } + // Step 2. + let length = node.len(); + // Step 3. + self.start.set(node, 0); + // Step 4. + self.end.set(node, length); + Ok(()) + } + + // https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset + fn compare_point(&self, node: JSRef, offset: u32) -> Fallible { + let start = &self.start; + let start_node = start.node().root(); + let start_offset = start.offset; + let start_node_root = start_node.r().inclusive_ancestors().last().unwrap().root(); + let node_root = node.inclusive_ancestors().last().unwrap().root(); + if start_node_root.r() != node_root.r() { + // Step 1. + return Err(Error::WrongDocument); + } + if node.is_doctype() { + // Step 2. + return Err(Error::InvalidNodeType); + } + if offset > node.len() { + // Step 3. + return Err(Error::IndexSize); + } + if let Ordering::Less = bp_position(node, offset, start_node.r(), start_offset).unwrap() { + // Step 4. + return Ok(Ordering::Less); + } + let end = &self.end; + let end_node = end.node().root(); + let end_offset = end.offset; + if let Ordering::Greater = bp_position(node, offset, end_node.r(), end_offset).unwrap() { + // Step 5. + return Ok(Ordering::Greater); + } + // Step 6. + Ok(Ordering::Equal) + } +} + +#[jstraceable] +#[must_root] +#[privatize] +pub struct BoundaryPoint { + node: MutHeap>, + offset: u32, +} + +impl BoundaryPoint { + fn new(node: JSRef, offset: u32) -> BoundaryPoint { + debug_assert!(!node.is_doctype()); + debug_assert!(offset <= node.len()); + BoundaryPoint { + node: MutHeap::new(JS::from_rooted(node)), + offset: offset, + } + } + + pub fn node(&self) -> Temporary { + Temporary::from_rooted(self.node.get()) + } + + pub fn offset(&self) -> u32 { + self.offset + } + + fn set(&mut self, node: JSRef, offset: u32) { + debug_assert!(!node.is_doctype()); + debug_assert!(offset <= node.len()); + self.node.set(JS::from_rooted(node)); + self.offset = offset; + } +} + +#[allow(unrooted_must_root)] +impl PartialOrd for BoundaryPoint { + fn partial_cmp(&self, other: &Self) -> Option { + bp_position(self.node().root().r(), self.offset, + other.node().root().r(), other.offset) + } +} + +#[allow(unrooted_must_root)] +impl PartialEq for BoundaryPoint { + fn eq(&self, other: &Self) -> bool { + self.node().root().r() == other.node().root().r() && + self.offset == other.offset + } +} + +// https://dom.spec.whatwg.org/#concept-range-bp-position +fn bp_position(a_node: JSRef, a_offset: u32, + b_node: JSRef, b_offset: u32) + -> Option { + if a_node == b_node { + // Step 1. + return Some(a_offset.cmp(&b_offset)); + } + let position = b_node.CompareDocumentPosition(a_node); + if position & NodeConstants::DOCUMENT_POSITION_DISCONNECTED != 0 { + // No order is defined for nodes not in the same tree. + None + } else if position & NodeConstants::DOCUMENT_POSITION_FOLLOWING != 0 { + // Step 2. + match bp_position(b_node, b_offset, a_node, a_offset).unwrap() { + Ordering::Less => Some(Ordering::Greater), + Ordering::Greater => Some(Ordering::Less), + Ordering::Equal => unreachable!(), + } + } else if position & NodeConstants::DOCUMENT_POSITION_CONTAINS != 0 { + // Step 3-1, 3-2. + let b_ancestors = b_node.inclusive_ancestors(); + let ref child = b_ancestors.map(|child| child.root()).find(|child| { + child.r().parent_node().unwrap().root().r() == a_node + }).unwrap(); + // Step 3-3. + if child.r().index() < a_offset { + Some(Ordering::Greater) + } else { + // Step 4. + Some(Ordering::Less) + } + } else { + // Step 4. + Some(Ordering::Less) + } +} diff --git a/servo/components/script/dom/webidls/Range.webidl b/servo/components/script/dom/webidls/Range.webidl index 5ef37a47ecb0..10ed2afb6d32 100644 --- a/servo/components/script/dom/webidls/Range.webidl +++ b/servo/components/script/dom/webidls/Range.webidl @@ -9,76 +9,71 @@ * http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#extensions-to-the-range-interface */ -[Constructor] +[Constructor /*, Exposed=Window */] interface Range { - // [Throws] - // readonly attribute Node startContainer; - // [Throws] - // readonly attribute unsigned long startOffset; - // [Throws] - // readonly attribute Node endContainer; - // [Throws] - // readonly attribute unsigned long endOffset; - // readonly attribute boolean collapsed; - // [Throws] - // readonly attribute Node commonAncestorContainer; + readonly attribute Node startContainer; + readonly attribute unsigned long startOffset; + readonly attribute Node endContainer; + readonly attribute unsigned long endOffset; + readonly attribute boolean collapsed; + readonly attribute Node commonAncestorContainer; - // [Throws] - // void setStart(Node refNode, unsigned long offset); - // [Throws] - // void setEnd(Node refNode, unsigned long offset); - // [Throws] - // void setStartBefore(Node refNode); - // [Throws] - // void setStartAfter(Node refNode); - // [Throws] - // void setEndBefore(Node refNode); - // [Throws] - // void setEndAfter(Node refNode); - // void collapse(optional boolean toStart = false); - // [Throws] - // void selectNode(Node refNode); - // [Throws] - // void selectNodeContents(Node refNode); + [Throws] + void setStart(Node refNode, unsigned long offset); + [Throws] + void setEnd(Node refNode, unsigned long offset); + [Throws] + void setStartBefore(Node refNode); + [Throws] + void setStartAfter(Node refNode); + [Throws] + void setEndBefore(Node refNode); + [Throws] + void setEndAfter(Node refNode); + void collapse(optional boolean toStart = false); + [Throws] + void selectNode(Node refNode); + [Throws] + void selectNodeContents(Node refNode); - // const unsigned short START_TO_START = 0; - // const unsigned short START_TO_END = 1; - // const unsigned short END_TO_END = 2; - // const unsigned short END_TO_START = 3; - // [Throws] - // short compareBoundaryPoints(unsigned short how, Range sourceRange); + const unsigned short START_TO_START = 0; + const unsigned short START_TO_END = 1; + const unsigned short END_TO_END = 2; + const unsigned short END_TO_START = 3; + [Throws] + short compareBoundaryPoints(unsigned short how, Range sourceRange); // [Throws] // void deleteContents(); - // [Throws] + // [NewObject, Throws] // DocumentFragment extractContents(); - // [Throws] + // [NewObject, Throws] // DocumentFragment cloneContents(); // [Throws] // void insertNode(Node node); // [Throws] // void surroundContents(Node newParent); - // Range cloneRange(); + [NewObject] + Range cloneRange(); void detach(); - // [Throws] - // boolean isPointInRange(Node node, unsigned long offset); - // [Throws] - // short comparePoint(Node node, unsigned long offset); + [Throws] + boolean isPointInRange(Node node, unsigned long offset); + [Throws] + short comparePoint(Node node, unsigned long offset); - // [Throws] - // boolean intersectsNode(Node node); + boolean intersectsNode(Node node); // stringifier; }; -// https://domparsing.spec.whatwg.org/#dom-range-createcontextualfragment +// https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#extensions-to-the-range-interface partial interface Range { - // [Throws] + // [NewObject, Throws] // DocumentFragment createContextualFragment(DOMString fragment); };// -//// http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#extensions-to-the-range-interface +// http://dev.w3.org/csswg/cssom-view/#extensions-to-the-range-interface partial interface Range { // DOMRectList? getClientRects(); // DOMRect getBoundingClientRect();