vendor/assets/javascripts/rangy-position.js in rangy-rails-1.3alpha.780.0 vs vendor/assets/javascripts/rangy-position.js in rangy-rails-1.3alpha.804.0

- old
+ new

@@ -1,15 +1,524 @@ -/** - * Position module for Rangy. - * Extensions to Range and Selection objects to provide access to pixel positions relative to the viewport or document. - * - * Part of Rangy, a cross-browser JavaScript range and selection library - * http://code.google.com/p/rangy/ - * - * Depends on Rangy core. - * - * Copyright 2013, Tim Down - * Licensed under the MIT license. - * Version: 1.3alpha.780M - * Build date: 17 May 2013 - */ -rangy.createModule("Position",["WrappedSelection"],function(a,b){function j(a){var b=0,d=0;if(typeof a.pageXOffset==c&&typeof a.pageYOffset==c)b=a.pageXOffset,d=a.pageYOffset;else{var e=a.document,f=e.documentElement,h=e.compatMode,i=typeof h=="string"&&h.indexOf("CSS")>=0&&f?f:g.getBody(e);if(i&&typeof i.scrollLeft==c&&typeof i.scrollTop==c)try{b=i.scrollLeft,d=i.scrollTop}catch(j){}}return{x:b,y:d}}function k(a,b){b=b.toLowerCase();while(a){if(a.nodeType==1&&a.tagName.toLowerCase()==b)return a;a=a.parentNode}return null}function l(a,b,c,d){this.top=a,this.right=b,this.bottom=c,this.left=d,this.width=b-d,this.height=c-a}function m(a,b,c){return new l(a.top+c,a.right+b,a.bottom+c,a.left+b)}function n(a,b){var d=0,e=0,f=b.documentElement,h=g.getBody(b),i=f.clientWidth===0&&typeof h.clientTop==c?h:f,j=i.clientLeft,k=i.clientTop;return j&&(d=-j),k&&(e=-k),m(a,d,e)}function o(a){var b=[],c=[],d=[],e=[];for(var f=0,g=a.length,h;f<g;++f)h=a[f],h&&(b.push(h.top),c.push(h.bottom),d.push(h.left),e.push(h.right));return new l(Math.min.apply(Math,b),Math.max.apply(Math,e),Math.max.apply(Math,c),Math.min.apply(Math,d))}function p(b,c,d){var e=g.getBody(b).createTextRange();e.moveToPoint(c,d);var f=new a.WrappedTextRange(e);return new i(f.startContainer,f.startOffset)}function q(a,b,c){var d=a.caretPositionFromPoint(b,c);return new i(d.offsetNode,d.offset)}function r(a,b,c){var d=a.caretRangeFromPoint(b,c);return new i(d.startContainer,d.startOffset)}function s(a){var b=(a.nativeRange||a).getClientRects();return b.length>0?b[b.length-1]:null}function t(a,b,c){return console.log("pointIsInOrAboveRect",a,b,Math.floor(c.top),Math.floor(c.right),Math.floor(c.bottom),Math.floor(c.left)),b<c.bottom&&a>=c.left&&a<=c.right}function u(b,c,d,e){var f=b.elementFromPoint(c,d);console.log("elementFromPoint is ",f);var h=a.createRange(b);h.selectNodeContents(f),h.collapse(!0);var j=f.firstChild,k,l,m;if(!j)j=f.parentNode,k=g.getNodeIndex(f),e||++k;else{a:while(j){console.log(j);if(j.nodeType==3)for(k=0,m=j.length;k<=m;++k){h.setEnd(j,k),l=s(h);if(l&&t(c,d,l)){l.right-c>c-l.left&&--k;break a}}else{h.setEndAfter(j),l=s(h);if(l&&t(c,d,l)){k=g.getNodeIndex(j),j=f.parentNode,e||++k;break}}j=j.nextSibling}j||(j=f,k=f.childNodes.length)}return new i(j,k)}function v(c){if(a.features.implementsTextRange)return p;if(typeof c.caretPositionFromPoint!=d)return q;if(typeof c.caretRangeFromPoint!=d)return r;if(typeof c.elementFromPoint!=d&&A)return u;throw b.createError("createCaretPositionFromPointGetter(): Browser does not provide a recognised method to create a selection from pixel coordinates")}function w(c,d,e,f,h){h=g.getContentDocument(h,b,"createRangeFromPoints");var i=v(h),j=i(h,c,d,!1),k=i(h,e,f,!0);console.log(j.node,j.offset,k.node,k.offset);var l=a.createRange(h);return l.setStartAndEnd(j.node,j.offset,k.node,k.offset),l}function x(a,b,c,d,e){var f,g,h,i,j=d<b||b==d&&c<a;j?(f=c,g=d,h=a,i=b):(f=a,g=b,h=c,i=d);var k=rangy.getSelection(e),l=w(f,g,h,i,e);return k.setSingleRange(l),k}function K(a,b){return a.compareBoundaryPoints(b.START_TO_START,b)}function L(a){return function(){var b="getBounding"+(a?"Document":"Client")+"Rect",c=[];for(var d=0,e=null,f;d<this.rangeCount;++d)c.push(this.getRangeAt(d)[b]());return o(c)}}function M(a,b){return function(){if(this.rangeCount==0)return null;var c=b?"Document":"Client",d=this.getAllRanges();return d.length>1&&d.sort(K),a?d[0]["getStart"+c+"Pos"]():d[d.length-1]["getEnd"+c+"Pos"]()}}var c="number",d="undefined",e=a.WrappedRange,f=a.WrappedTextRange,g=a.dom,h=a.util,i=g.DomPosition,y=document.createElement("span"),z=h.isHostMethod(y,"getBoundingClientRect");y=null;var A=!1,B=!1;if(a.features.implementsDomRange){var C=a.createNativeRange();A=h.isHostMethod(C,"getClientRects"),B=h.isHostMethod(C,"getBoundingClientRect"),C.detach()}h.extend(a.features,{rangeSupportsGetBoundingClientRect:B,rangeSupportsGetClientRects:A,elementSupportsGetBoundingClientRect:z});var D=function(a){return function(){var b=this.cloneRange();b.collapse(a);var c=b.getBoundingClientRect();return{x:c[a?"left":"right"],y:c[a?"top":"bottom"]}}},E=a.rangePrototype;if(a.features.implementsTextRange&&z)E.getBoundingClientRect=function(){var a=f.rangeToTextRange(this),b=this.getNodes([1],function(a){return/^t[dh]$/i.test(a.tagName)}),c,d=[];if(b.length>0){var e=k(this.startContainer,"table");for(var h=0,i,j,l,m,p;i=b[h];++h){l=k(i,"table");if(!e||l!=e)m=this.cloneRange(),e&&m.setStartAfter(e),m.setEndBefore(l),d.push(f.rangeToTextRange(m).getBoundingClientRect());this.containsNode(i)?d.push(i.getBoundingClientRect()):(j=a.duplicate(),j.moveToElementText(i),j.compareEndPoints("StartToStart",a)==-1?j.setEndPoint("StartToStart",a):j.compareEndPoints("EndToEnd",a)==1&&j.setEndPoint("EndToEnd",a),d.push(j.getBoundingClientRect())),e=l}var q=k(this.endContainer,"table");!q&&e&&(m=this.cloneRange(),m.setStartAfter(e),d.push(f.rangeToTextRange(m).getBoundingClientRect())),c=o(d)}else c=a.getBoundingClientRect();return n(c,g.getDocument(this.startContainer))};else if(a.features.implementsDomRange){var F=function(a){return a instanceof e?a:new e(a)};if(B){E.getBoundingClientRect=function(){var a=F(this).nativeRange,b=a.getBoundingClientRect()||a.getClientRects()[0];return n(b,g.getDocument(this.startContainer))};if(A){var G=function(a,b){var c=a.childNodes};D=function(a){return function(){var c,d=F(this).nativeRange,e=d.getClientRects();if(e.length==0&&z){!a,console.log(d,d.getClientRects(),d.getBoundingClientRect());if(this.collapsed&&this.startContainer.nodeType==1&&this.startOffset<this.startContainer.childNodes.length){var f=this.startContainer.childNodes[this.startOffset];f.getClientRects&&console.log(f,f.getClientRects(),this.startContainer.getClientRects())}}if(e.length>0)return a?(c=e[0],{x:c.left,y:c.top}):(c=e[e.length-1],{x:c.right,y:c.bottom});throw b.createError("Cannot get position for range "+this.inspect())}}}}else{var H=z?function(a){return n(a.getBoundingClientRect(),g.getDocument(a))}:function(a){var b=0,c=0,d=a,e=a.offsetWidth,f=a.offsetHeight;while(d)b+=d.offsetLeft,c+=d.offsetTop,d=d.offsetParent;return n(new l(c,b+e,c+f,b),g.getDocument(a))},I=function(a){var b;a.splitBoundaries();var c=document.createElement("span");if(a.collapsed)a.insertNode(c),b=H(c),c.parentNode.removeChild(c);else{var d=a.cloneRange();d.collapse(!0),d.insertNode(c);var e=H(c);c.parentNode.removeChild(c),d.collapseToPoint(a.endContainer,a.endOffset),d.insertNode(c);var f=H(c);c.parentNode.removeChild(c);var g=[e,f],h=a.getNodes([1],function(b){return a.containsNode(b)});for(var i=0,j=h.length;i<j;++i)g.push(H(h[i]));b=o(g)}return a.normalizeBoundaries(),b};E.getBoundingClientRect=function(a){return I(F(a))}}function J(a){return function(){var b=this["get"+(a?"Start":"End")+"ClientPos"](),c=j(g.getWindow(this.startContainer));return{x:b.x+c.x,y:b.y+c.y}}}}h.extend(E,{getBoundingDocumentRect:function(){var a=j(g.getWindow(this.startContainer));return m(this.getBoundingClientRect(),a.x,a.y)},getStartClientPos:D(!0),getEndClientPos:D(!1),getStartDocumentPos:J(!0),getEndDocumentPos:J(!1)}),h.extend(a.selectionPrototype,{getBoundingClientRect:L(!1),getBoundingDocumentRect:L(!0),getStartClientPos:M(!0,!1),getEndClientPos:M(!1,!1),getStartDocumentPos:M(!0,!0),getEndDocumentPos:M(!1,!0)}),a.positionFromPoint=function(a,c,d){return d=g.getContentDocument(d,b,"positionFromPoint"),v(d)(d,a,c)},a.createRangeFromPoints=w,a.moveSelectionToPoints=x}) \ No newline at end of file +rangy.createModule("Position", ["WrappedSelection"], function(api, module) { + //var log = log4javascript.getLogger("rangy.position"); + + var NUMBER = "number", UNDEF = "undefined"; + var WrappedRange = api.WrappedRange; + var WrappedTextRange = api.WrappedTextRange; + var dom = api.dom, util = api.util, DomPosition = dom.DomPosition; + + // Feature detection + + //var caretPositionFromPointSupported = (typeof document.caretPositionFromPoint != UNDEF); + + // Since Rangy can deal with multiple documents which could be in different modes, we have to do the checks every + // time, unless we cache a getScrollPosition function in each document. This would necessarily pollute the + // document's global namespace, which I'm choosing to view as a greater evil than a slight performance hit. + function getScrollPosition(win) { + var x = 0, y = 0; + if (typeof win.pageXOffset == NUMBER && typeof win.pageYOffset == NUMBER) { + x = win.pageXOffset; + y = win.pageYOffset; + } else { + var doc = win.document; + var docEl = doc.documentElement; + var compatMode = doc.compatMode; + var scrollEl = (typeof compatMode == "string" && compatMode.indexOf("CSS") >= 0 && docEl) + ? docEl : dom.getBody(doc); + + if (scrollEl && typeof scrollEl.scrollLeft == NUMBER && typeof scrollEl.scrollTop == NUMBER) { + try { + x = scrollEl.scrollLeft; + y = scrollEl.scrollTop; + } catch (ex) {} + } + } + return { x: x, y: y }; + } + + function getAncestorElement(node, tagName) { + tagName = tagName.toLowerCase(); + while (node) { + if (node.nodeType == 1 && node.tagName.toLowerCase() == tagName) { + return node; + } + node = node.parentNode; + } + return null; + } + + function Rect(top, right, bottom, left) { + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + this.width = right - left; + this.height = bottom - top; + } + + function createRelativeRect(rect, dx, dy) { + return new Rect(rect.top + dy, rect.right + dx, rect.bottom + dy, rect.left + dx); + } + + function adjustClientRect(rect, doc) { + // Older IEs have an issue with a two pixel margin on the body element + var dx = 0, dy = 0; + var docEl = doc.documentElement, body = dom.getBody(doc); + var container = (docEl.clientWidth === 0 && typeof body.clientTop == NUMBER) ? body : docEl; + var clientLeft = container.clientLeft, clientTop = container.clientTop; + if (clientLeft) { + dx = -clientLeft; + } + if (clientTop) { + dy = -clientTop; + } + return createRelativeRect(rect, dx, dy); + } + + function mergeRects(rects) { + var tops = [], bottoms = [], lefts = [], rights = []; + for (var i = 0, len = rects.length, rect; i < len; ++i) { + rect = rects[i]; + if (rect) { + tops.push(rect.top); + bottoms.push(rect.bottom); + lefts.push(rect.left); + rights.push(rect.right); + } + } + return new Rect( + Math.min.apply(Math, tops), + Math.max.apply(Math, rights), + Math.max.apply(Math, bottoms), + Math.min.apply(Math, lefts) + ); + } + + function getTextRangePosition(doc, x, y) { + var textRange = dom.getBody(doc).createTextRange(); + textRange.moveToPoint(x, y); + var range = new api.WrappedTextRange(textRange); + return new DomPosition(range.startContainer, range.startOffset); + } + + function caretPositionFromPoint(doc, x, y) { + var pos = doc.caretPositionFromPoint(x, y); + return new DomPosition(pos.offsetNode, pos.offset); + } + + function caretRangeFromPoint(doc, x, y) { + var range = doc.caretRangeFromPoint(x, y); + return new DomPosition(range.startContainer, range.startOffset); + } + + function getLastRangeRect(range) { + var rects = (range.nativeRange || range).getClientRects(); + return (rects.length > 0) ? rects[rects.length - 1] : null; + } + + function pointIsInOrAboveRect(x, y, rect) { + console.log("pointIsInOrAboveRect", x, y, Math.floor(rect.top), Math.floor(rect.right), Math.floor(rect.bottom), Math.floor(rect.left)) + return y < rect.bottom && x >= rect.left && x <= rect.right; + } + + function positionFromPoint(doc, x, y, favourPrecedingPosition) { + var el = doc.elementFromPoint(x, y); + + console.log("elementFromPoint is ", el); + + var range = api.createRange(doc); + range.selectNodeContents(el); + range.collapse(true); + + var node = el.firstChild, offset, rect, textLen; + + if (!node) { + node = el.parentNode; + offset = dom.getNodeIndex(el); + if (!favourPrecedingPosition) { + ++offset; + } + } else { + // Search through the text node children of el + main: while (node) { + console.log(node); + if (node.nodeType == 3) { + // Go through the text node character by character + for (offset = 0, textLen = node.length; offset <= textLen; ++offset) { + range.setEnd(node, offset); + rect = getLastRangeRect(range); + if (rect && pointIsInOrAboveRect(x, y, rect)) { + // We've gone past the point. Now we check which side (left or right) of the character the point is nearer to + if (rect.right - x > x - rect.left) { + --offset; + } + break main; + } + } + } else { + // Handle elements + range.setEndAfter(node); + rect = getLastRangeRect(range); + if (rect && pointIsInOrAboveRect(x, y, rect)) { + offset = dom.getNodeIndex(node); + node = el.parentNode; + if (!favourPrecedingPosition) { + ++offset; + } + break; + } + } + + node = node.nextSibling; + } + if (!node) { + node = el; + offset = el.childNodes.length; + } + } + + return new DomPosition(node, offset); + } + + function createCaretPositionFromPointGetter(doc) { + if (api.features.implementsTextRange) { + return getTextRangePosition; + } else if (typeof doc.caretPositionFromPoint != UNDEF) { + return caretPositionFromPoint; + } else if (typeof doc.caretRangeFromPoint != UNDEF) { + return caretRangeFromPoint; + } else if (typeof doc.elementFromPoint != UNDEF && rangeSupportsGetClientRects) { + return positionFromPoint; + } else { + throw module.createError("createCaretPositionFromPointGetter(): Browser does not provide a recognised method to create a selection from pixel coordinates"); + } + } + + function createRangeFromPoints(startX, startY, endX, endY, doc) { + doc = dom.getContentDocument(doc, module, "createRangeFromPoints"); + var positionFinder = createCaretPositionFromPointGetter(doc); + var startPos = positionFinder(doc, startX, startY, false); + var endPos = positionFinder(doc, endX, endY, true); + console.log(startPos.node, startPos.offset, endPos.node, endPos.offset); + var range = api.createRange(doc); + range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset); + return range; + } + + function moveSelectionToPoints(anchorX, anchorY, focusX, focusY, doc) { + var startX, startY, endX, endY; + + // Detect backward selection for coordinates and flip start and end coordinates if necessary + var backward = focusY < anchorY || (anchorY == focusY && focusX < anchorX); + + if (backward) { + startX = focusX; + startY = focusY; + endX = anchorX; + endY = anchorY; + } else { + startX = anchorX; + startY = anchorY; + endX = focusX; + endY = focusY; + } + + var sel = rangy.getSelection(doc); + var range = createRangeFromPoints(startX, startY, endX, endY, doc); + sel.setSingleRange(range); + return sel; + } + + // Test that <span> elements support getBoundingClientRect + var span = document.createElement("span"); + var elementSupportsGetBoundingClientRect = util.isHostMethod(span, "getBoundingClientRect"); + span = null; + + // Test for getBoundingClientRect support in Range + var rangeSupportsGetClientRects = false, rangeSupportsGetBoundingClientRect = false; + if (api.features.implementsDomRange) { + var testRange = api.createNativeRange(); + rangeSupportsGetClientRects = util.isHostMethod(testRange, "getClientRects"); + rangeSupportsGetBoundingClientRect = util.isHostMethod(testRange, "getBoundingClientRect"); + testRange.detach(); + } + + util.extend(api.features, { + rangeSupportsGetBoundingClientRect: rangeSupportsGetBoundingClientRect, + rangeSupportsGetClientRects: rangeSupportsGetClientRects, + elementSupportsGetBoundingClientRect: elementSupportsGetBoundingClientRect + }); + + var createClientBoundaryPosGetter = function(isStart) { + return function() { + var boundaryRange = this.cloneRange(); + boundaryRange.collapse(isStart); + var rect = boundaryRange.getBoundingClientRect(); + return { + x: rect[isStart ? "left" : "right"], + y: rect[isStart ? "top" : "bottom"] + }; + }; + }; + + var rangeProto = api.rangePrototype; + + if (api.features.implementsTextRange && elementSupportsGetBoundingClientRect) { + rangeProto.getBoundingClientRect = function() { + // We need a TextRange + var textRange = WrappedTextRange.rangeToTextRange(this); + + // Work around table problems (table cell bounding rects seem not to count if TextRange spans cells) + var cells = this.getNodes([1], function(el) { + return /^t[dh]$/i.test(el.tagName); + }); + + // Merge rects for each cell selected by the range into overall rect + var rect, rects = []; + if (cells.length > 0) { + var lastTable = getAncestorElement(this.startContainer, "table"); + + for (var i = 0, cell, tempTextRange, table, subRange, subRect; cell = cells[i]; ++i) { + // Handle non-table sections of the range + table = getAncestorElement(cell, "table"); + if (!lastTable || table != lastTable) { + // There is a section of the range prior to the current table, or lying between tables. + // Merge in its rect + subRange = this.cloneRange(); + if (lastTable) { + subRange.setStartAfter(lastTable); + } + subRange.setEndBefore(table); + rects.push(WrappedTextRange.rangeToTextRange(subRange).getBoundingClientRect()); + } + + if (this.containsNode(cell)) { + rects.push(cell.getBoundingClientRect()); + } else { + tempTextRange = textRange.duplicate(); + tempTextRange.moveToElementText(cell); + if (tempTextRange.compareEndPoints("StartToStart", textRange) == -1) { + tempTextRange.setEndPoint("StartToStart", textRange); + } else if (tempTextRange.compareEndPoints("EndToEnd", textRange) == 1) { + tempTextRange.setEndPoint("EndToEnd", textRange); + } + rects.push(tempTextRange.getBoundingClientRect()); + } + lastTable = table; + } + + // Merge in the rect for any content lying after the final table + var endTable = getAncestorElement(this.endContainer, "table"); + if (!endTable && lastTable) { + subRange = this.cloneRange(); + subRange.setStartAfter(lastTable); + rects.push(WrappedTextRange.rangeToTextRange(subRange).getBoundingClientRect()); + } + rect = mergeRects(rects); + } else { + rect = textRange.getBoundingClientRect(); + } + + return adjustClientRect(rect, dom.getDocument(this.startContainer)); + }; + } else if (api.features.implementsDomRange) { + var createWrappedRange = function(range) { + return (range instanceof WrappedRange) ? range : new WrappedRange(range); + }; + + if (rangeSupportsGetBoundingClientRect) { + rangeProto.getBoundingClientRect = function() { + var nativeRange = createWrappedRange(this).nativeRange; + // Test for WebKit getBoundingClientRect bug (https://bugs.webkit.org/show_bug.cgi?id=65324) + var rect = nativeRange.getBoundingClientRect() || nativeRange.getClientRects()[0]; + return adjustClientRect(rect, dom.getDocument(this.startContainer)); + }; + + if (rangeSupportsGetClientRects) { + var getElementRectsForPosition = function(node, offset) { + var children = node.childNodes; + //if (offset < children.length) + }; + + createClientBoundaryPosGetter = function(isStart) { + return function() { + var rect, nativeRange = createWrappedRange(this).nativeRange; + var rects = nativeRange.getClientRects(); + + if (rects.length == 0 && elementSupportsGetBoundingClientRect) { + if (isStart) { + + + } + + console.log(nativeRange, nativeRange.getClientRects(), nativeRange.getBoundingClientRect()); + if (this.collapsed + && this.startContainer.nodeType == 1 + && this.startOffset < this.startContainer.childNodes.length) { + var n = this.startContainer.childNodes[this.startOffset]; + if (n.getClientRects) { + console.log(n, n.getClientRects(), this.startContainer.getClientRects()) + } + + } + } + + if (rects.length > 0) { + if (isStart) { + rect = rects[0]; + return { x: rect.left, y: rect.top }; + } else { + rect = rects[rects.length - 1]; + return { x: rect.right, y: rect.bottom }; + } + } else { + throw module.createError("Cannot get position for range " + this.inspect()); + } + }; + } + } + } else { + var getElementBoundingClientRect = elementSupportsGetBoundingClientRect ? + function(el) { + return adjustClientRect(el.getBoundingClientRect(), dom.getDocument(el)); + } : + + // This implementation is very naive. There are many browser quirks that make it extremely + // difficult to get accurate element coordinates in all situations + function(el) { + var x = 0, y = 0, offsetEl = el, width = el.offsetWidth, height = el.offsetHeight; + while (offsetEl) { + x += offsetEl.offsetLeft; + y += offsetEl.offsetTop; + offsetEl = offsetEl.offsetParent; + } + + return adjustClientRect(new Rect(y, x + width, y + height, x), dom.getDocument(el)); + }; + + var getRectFromBoundaries = function(range) { + var rect; + range.splitBoundaries(); + var span = document.createElement("span"); + + if (range.collapsed) { + range.insertNode(span); + rect = getElementBoundingClientRect(span); + span.parentNode.removeChild(span); + } else { + // TODO: This isn't right. I'm not sure it can be made right sensibly. Consider what to do. + // This doesn't consider all the line boxes it needs to consider. + var workingRange = range.cloneRange(); + + // Get the start rectangle + workingRange.collapse(true); + workingRange.insertNode(span); + var startRect = getElementBoundingClientRect(span); + span.parentNode.removeChild(span); + + // Get the end rectangle + workingRange.collapseToPoint(range.endContainer, range.endOffset); + workingRange.insertNode(span); + var endRect = getElementBoundingClientRect(span); + span.parentNode.removeChild(span); + + // Merge the start and end rects + var rects = [startRect, endRect]; + + // Merge in rectangles for all elements in the range + var elements = range.getNodes([1], function(el) { + return range.containsNode(el); + }); + + for (var i = 0, len = elements.length; i < len; ++i) { + rects.push(getElementBoundingClientRect(elements[i])); + } + rect = mergeRects(rects) + } + + // Clean up + range.normalizeBoundaries(); + return rect; + }; + + rangeProto.getBoundingClientRect = function(range) { + return getRectFromBoundaries(createWrappedRange(range)); + }; + } + + function createDocumentBoundaryPosGetter(isStart) { + return function() { + var pos = this["get" + (isStart ? "Start" : "End") + "ClientPos"](); + var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) ); + return { x: pos.x + scrollPos.x, y: pos.y + scrollPos.y }; + }; + } + } + + util.extend(rangeProto, { + getBoundingDocumentRect: function() { + var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) ); + return createRelativeRect(this.getBoundingClientRect(), scrollPos.x, scrollPos.y); + }, + + getStartClientPos: createClientBoundaryPosGetter(true), + getEndClientPos: createClientBoundaryPosGetter(false), + + getStartDocumentPos: createDocumentBoundaryPosGetter(true), + getEndDocumentPos: createDocumentBoundaryPosGetter(false) + }); + + // Add Selection methods + function compareRanges(r1, r2) { + return r1.compareBoundaryPoints(r2.START_TO_START, r2); + } + + function createSelectionRectGetter(isDocument) { + return function() { + var rangeMethodName = "getBounding" + (isDocument ? "Document" : "Client") + "Rect"; + var rects = []; + for (var i = 0, rect = null, rangeRect; i < this.rangeCount; ++i) { + rects.push(this.getRangeAt(i)[rangeMethodName]()); + } + return mergeRects(rects); + }; + } + + function createSelectionBoundaryPosGetter(isStart, isDocument) { + return function() { + if (this.rangeCount == 0) { + return null; + } + + var posType = isDocument ? "Document" : "Client"; + + var ranges = this.getAllRanges(); + if (ranges.length > 1) { + // Order the ranges by position within the DOM + ranges.sort(compareRanges); + } + + return isStart ? + ranges[0]["getStart" + posType + "Pos"]() : + ranges[ranges.length - 1]["getEnd" + posType + "Pos"](); + }; + } + + util.extend(api.selectionPrototype, { + getBoundingClientRect: createSelectionRectGetter(false), + getBoundingDocumentRect: createSelectionRectGetter(true), + + getStartClientPos: createSelectionBoundaryPosGetter(true, false), + getEndClientPos: createSelectionBoundaryPosGetter(false, false), + + getStartDocumentPos: createSelectionBoundaryPosGetter(true, true), + getEndDocumentPos: createSelectionBoundaryPosGetter(false, true) + }); + + api.positionFromPoint = function(x, y, doc) { + doc = dom.getContentDocument(doc, module, "positionFromPoint"); + return createCaretPositionFromPointGetter(doc)(doc, x, y); + }; + + api.createRangeFromPoints = createRangeFromPoints; + api.moveSelectionToPoints = moveSelectionToPoints; +});