     else
       NodeFilter.FILTER_SKIP
 
+  getLocationRangeAtPoint: ([pageX, pageY]) ->
+    if document.caretPositionFromPoint
+      {offsetNode, offset} = document.caretPositionFromPoint(pageX, pageY)
+      domRange = document.createRange()
+      domRange.setStart(offsetNode, offset)
+
+    else if document.caretRangeFromPoint
+      domRange = document.caretRangeFromPoint(pageX, pageY)
+
+    else if document.body.createTextRange
+      range = document.body.createTextRange()
+      range.moveToPoint(pageX, pageY)
+      range.select()
+      return @updateCurrentLocationRange()
+
+    if domRange
+      @createLocationRangeFromDOMRange(domRange)
+
+  getPointAtEndOfSelection: ->
+    if range = @getDOMRange()
+      rects = range.getClientRects()
+      if rects.length > 0
+        rect = rects[rects.length - 1]
+
+        pageX = rect.right
+        pageY = rect.top + rect.height / 2
+
+        if @clientRectIsRelativeToBody()
+          pageX -= document.body.scrollLeft
+          pageY -= document.body.scrollTop
+
+        [pageX, pageY]
+
   # ClientRect position properties should be relative to the viewport,
   # but in some browsers (like mobile Safari), they're relative to the body.
   clientRectIsRelativeToBody: memoize ->

    selection = window.getSelection()
    selection.removeAllRanges()
    selection.addRange(range)

  # Private

  createLocationRangeFromDOMRange: (range) ->
    return unless range?

  constructor: (@element) ->
    @selectionObserver = new Trix.SelectionObserver
    @selectionObserver.delegate = this

  getLocationRange: ->
    @lockedLocationRange ? @currentLocationRange
