docs: function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
                  var workingRange = textRange.duplicate();
                  workingRange.collapse(isStart);
                  var containerElement = workingRange.parentElement();
  
                  // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
                  // check for that
                  if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
                      containerElement = wholeRangeContainerElement;
                  }
  
  
                  // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
                  // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
                  if (!containerElement.canHaveHTML) {
                      var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
                      return {
                          boundaryPosition: pos,
                          nodeInfo: {
                              nodeIndex: pos.offset,
                              containerElement: pos.node
                          }
                      };
                  }
  
                  var workingNode = dom.getDocument(containerElement).createElement("span");
  
                  // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
                  // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
                  if (workingNode.parentNode) {
                      workingNode.parentNode.removeChild(workingNode);
                  }
  
                  var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
                  var previousNode, nextNode, boundaryPosition, boundaryNode;
                  var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
                  var childNodeCount = containerElement.childNodes.length;
                  var end = childNodeCount;
  
                  // Check end first. Code within the loop assumes that the endth child node of the container is definitely
                  // after the range boundary.
                  var nodeIndex = end;
  
                  while (true) {
                      if (nodeIndex == childNodeCount) {
                          containerElement.appendChild(workingNode);
                      } else {
                          containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
                      }
                      workingRange.moveToElementText(workingNode);
                      comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
                      if (comparison == 0 || start == end) {
                          break;
                      } else if (comparison == -1) {
                          if (end == start + 1) {
                              // We know the endth child node is after the range boundary, so we must be done.
                              break;
                          } else {
                              start = nodeIndex;
                          }
                      } else {
                          end = (end == start + 1) ? start : nodeIndex;
                      }
                      nodeIndex = Math.floor((start + end) / 2);
                      containerElement.removeChild(workingNode);
                  }
  
  
                  // We've now reached or gone past the boundary of the text range we're interested in
                  // so have identified the node we want
                  boundaryNode = workingNode.nextSibling;
  
                  if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {
                      // This is a character data node (text, comment, cdata). The working range is collapsed at the start of
                      // the node containing the text range's boundary, so we move the end of the working range to the
                      // boundary point and measure the length of its text to get the boundary's offset within the node.
                      workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
  
                      var offset;
  
                      if (/[\r\n]/.test(boundaryNode.data)) {
                          /*
                          For the particular case of a boundary within a text node containing rendered line breaks (within a
                          <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in
                          IE. The facts:
                          
                          - Each line break is represented as \r in the text node's data/nodeValue properties
                          - Each line break is represented as \r\n in the TextRange's 'text' property
                          - The 'text' property of the TextRange does not contain trailing line breaks
                          
                          To get round the problem presented by the final fact above, we can use the fact that TextRange's
                          moveStart() and moveEnd() methods return the actual number of characters moved, which is not
                          necessarily the same as the number of characters it was instructed to move. The simplest approach is
                          to use this to store the characters moved when moving both the start and end of the range to the
                          start of the document body and subtracting the start offset from the end offset (the
                          "move-negative-gazillion" method). However, this is extremely slow when the document is large and
                          the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to
                          the end of the document) has the same problem.
                          
                          Another approach that works is to use moveStart() to move the start boundary of the range up to the
                          end boundary one character at a time and incrementing a counter with the value returned by the
                          moveStart() call. However, the check for whether the start boundary has reached the end boundary is
                          expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected
                          by the location of the range within the document).
                          
                          The approach used below is a hybrid of the two methods above. It uses the fact that a string
                          containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot
                          be longer than the text of the TextRange, so the start of the range is moved that length initially
                          and then a character at a time to make up for any trailing line breaks not contained in the 'text'
                          property. This has good performance in most situations compared to the previous two methods.
                          */
                          var tempRange = workingRange.duplicate();
                          var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
  
                          offset = tempRange.moveStart("character", rangeLength);
                          while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
                              offset++;
                              tempRange.moveStart("character", 1);
                          }
                      } else {
                          offset = workingRange.text.length;
                      }
                      boundaryPosition = new DomPosition(boundaryNode, offset);
                  } else {
  
                      // If the boundary immediately follows a character data node and this is the end boundary, we should favour
                      // a position within that, and likewise for a start boundary preceding a character data node
                      previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
                      nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
                      if (nextNode && isCharacterDataNode(nextNode)) {
                          boundaryPosition = new DomPosition(nextNode, 0);
                      } else if (previousNode && isCharacterDataNode(previousNode)) {
                          boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
                      } else {
                          boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
                      }
                  }
  
                  // Clean up
                  workingNode.parentNode.removeChild(workingNode);
  
                  return {
                      boundaryPosition: boundaryPosition,
                      nodeInfo: {
                          nodeIndex: nodeIndex,
                          containerElement: containerElement
                      }
                  };
              }