As suggested, you can use NodeIterator to walk inside range.commonAncestorContainer.
Here's a snippet:
var _iterator = document.createNodeIterator(
    range.commonAncestorContainer,
    NodeFilter.SHOW_ALL, // pre-filter
    {
        // custom filter
        acceptNode: function (node) {
            return NodeFilter.FILTER_ACCEPT;
        }
    }
);
var _nodes = [];
while (_iterator.nextNode()) {
    if (_nodes.length === 0 && _iterator.referenceNode !== range.startContainer) continue;
    _nodes.push(_iterator.referenceNode);
    if (_iterator.referenceNode === range.endContainer) break;
}
You should use NodeFilter.SHOW_ALL because your range can contain multiple nodeTypes. If you know what you are selecting, you can check this reference to properly choose NodeFilter.
Edit: I also want to point out document.createTreeWalker().
The key difference is that document.createTreeWalker() allow your acceptNode filter to return both NodeFilter.FILTER_REJECT and NodeFilter.FILTER_SKIP with real differences.
Quote from NodeFilter docs:
FILTER_REJECT:
Value to be returned by the NodeFilter.acceptNode() method when a node should be rejected. For TreeWalker, child nodes are also rejected. For NodeIterator, this flag is synonymous with FILTER_SKIP.
Ps: the NodeFilter.acceptNode() documentation for NodeFilter.FILTER_REJECT is incorrect.