# Source: view/NodeView.js

    /**
     * A (not yet documentd) collection of low-level utitity functions
     * related to <em>node views</em>, client-side <em>views</em> of
     * server-side XML <em>nodes</em>. Node views
     * are contained in the {@link DocumentView}.
     * <p>A node view is represented on the client side by an HTML element
     * (mainly <code>div</code>; possibly having descendant HTML elements)
     * having the following internal structure/attributes:
     * <ul>
     * <li>Text view:
     *
     *     <pre>&lt;span data-t="" id=UID contenteditable&gt;TEXT&lt;span&gt;</pre>
     *
     *     <p>No generated content.
     *
     *  <li>Comment view:
     *
     *      <pre>data-wc=UID
     *   [ + data-bc=UID ]
     *   + data-c="" id=UID contenteditable
     *        + TEXT
     *   [ + data-ac=UID ]</pre>
     *
     *      <p><strong>But</strong> when styled:
     *
     *      <pre>data-c="" id=UID contenteditable
     *     + TEXT</pre>
     *
     *      <pre>data-rc="" id=UID
     *     + REPLACED_CONTENT</pre>
     *
     *  <li>Processing-instruction view:
     *
     *      <pre>data-wp=UID
     *   [ + data-bc=UID ]
     *   + data-p="" id=UID contenteditable
     *        + TEXT
     *   [ + data-ap=UID ]</pre>
     *
     *      BUT when styled:
     *
     *      <pre>data-p="" id=UID contenteditable
     *     + TEXT</pre>
     *
     *      <pre>data-rp="" id=UID
     *     + REPLACED_CONTENT</pre>
     *
     *  <li>Element view:
     *
     *      <pre>data-e="" id=UID
     *   [ + data-be=UID ]
     *       + CHILDREN
     *   [ + data-ae=UID ]</pre>
     *
     *      <p><strong>Also</strong> when styled:
     *
     *      <pre>data-re="" id=UID
     *   [ + data-be=UID ]
     *       + REPLACED_CONTENT
     *   [ + data-ae=UID ]</pre>
     *
     *      <pre>data-we=UID
     *   [ + data-be=UID ]
     *   + data-e="" id=UID
     *      + CHILDREN
     *   [ + data-ae=UID ]</pre>
     *
     *      <pre>data-we=UID
     *   [ + data-be=UID ]
     *   + data-re="" id=UID
     *      + REPLACED_CONTENT
     *   [ + data-ae=UID ]</pre>
     *
     *   <li>Table view specificities: see description in
     *   <code>com.xmlmind.xmleditsrv.webview.TableRendering.java</code>.
     *
     *   <li>The root view must have an "<code>xxe-re</code>" class if editable
     *   and "<code>xxe-ro</code>" otherwise.
     *
     *   <li>Collapsers must have a <code>collapsed</code> attribute which is
     *   added/removed dynamically.
     *
     *       <p>In the case of the tree view,
     *       the collapser is <code>&lt;xxe-collapser&gt;</code>.
     *       More expectations in <code>TreeCollapser.js</code>.
     *
     *       <p>In the case of the styled view, the collapser is
     *       <code>&lt;xxe-collapser2&gt;</code>.
     *       See <code>StyledCollapser.js</code>.
     *
     *       <p><code>&lt;xxe-collapser2&gt;</code> for a collapsible
     *       styled element view may be found not only
     *       in the content before/content after of this
     *       element view, but also inside its <strong>descendant</strong>
     *       element views (e.g. content before in the caption of
     *       a collapsible table or the title of a collapsible section).
     *
     *       <p>A collapsible styled element has a
     *       <code>data-collapsible="collapsed;notCollapsibleHead;notCollapsibleFoot"</code>
     *       attribute.
     * </ul>
     */
    export class NodeView {
        // -----------------------------------------------------------------------
        // Access by uid
        // -----------------------------------------------------------------------

        static content(root, uid, reportError=true) {
            let content = root.getElementById(uid);
            if (content === null && reportError) {
                console.error(`NodeView.content: INTERNAL ERROR: \
    cannot find node view "${uid}"`);
            }

            return content;
        }

        static textualContent(root, uid, reportError=true) {
            let content = NodeView.content(root, uid, reportError);
            if (content !== null &&
                (content.hasAttribute("data-t") ||
                 content.hasAttribute("data-c") ||
                 content.hasAttribute("data-p"))) {
                return content;
            }

            return null;
        }

        static view(root, uid, reportError=true) {
            let content = NodeView.content(root, uid, reportError);
            if (content !== null) {
                return NodeView.contentToView(content, uid);
            }

            return null;
        }

        static contentToView(content, uid) {
            assertOrError(NodeView.isContent(content));

            let parent = content.parentElement;
            if (parent !== null &&
                (parent.getAttribute("data-we") === uid ||
                 parent.getAttribute("data-wp") === uid ||
                 parent.getAttribute("data-wc") === uid)) {
                content = parent;
            }
            // Otherwise the view is equal to its content.

            return content;
        }

        // -----------------------------------------------------------------------
        // Categorization of view parts
        // -----------------------------------------------------------------------

        static isContent(node) {
            return (node !== null &&
                    node.nodeType === Node.ELEMENT_NODE &&
                    node.id && /*accelerator*/
                    (node.hasAttribute("data-t") ||
                     node.hasAttribute("data-e") ||
                     node.hasAttribute("data-c") ||
                     node.hasAttribute("data-p") ||
                     node.hasAttribute("data-re") ||
                     node.hasAttribute("data-rc") ||
                     node.hasAttribute("data-rp")));
        }

        static isTextualContent(node) {
            return (node !== null &&
                    node.nodeType === Node.ELEMENT_NODE &&
                    node.id && /*accelerator*/
                    (node.hasAttribute("data-t") ||
                     node.hasAttribute("data-c") ||
                     node.hasAttribute("data-p")));
        }

        static isReplacedContent(node) {
            return (node !== null &&
                    node.nodeType === Node.ELEMENT_NODE &&
                    node.id && /*accelerator*/
                    (node.hasAttribute("data-re") ||
                     node.hasAttribute("data-rc") ||
                     node.hasAttribute("data-rp")));
        }

        static isContentBefore(node, uid) {
            return (node !== null &&
                    node.nodeType === Node.ELEMENT_NODE &&
                    (node.getAttribute("data-be") === uid ||
                     node.getAttribute("data-bp") === uid ||
                     node.getAttribute("data-bc") === uid));
        }

        static isContentAfter(node, uid) {
            return (node !== null &&
                    node.nodeType === Node.ELEMENT_NODE &&
                    (node.getAttribute("data-ae") === uid ||
                     node.getAttribute("data-ap") === uid ||
                     node.getAttribute("data-ac") === uid));
        }

        static isContentWrapper(node) {
            return (node !== null &&
                    node.nodeType === Node.ELEMENT_NODE &&
                    (node.hasAttribute("data-we") ||
                     node.hasAttribute("data-wp") ||
                     node.hasAttribute("data-wc")));
        }

        static isGeneratedContent(node) {
            return (node !== null &&
                    node.nodeType === Node.ELEMENT_NODE &&
                    (node.hasAttribute("data-be") ||
                     node.hasAttribute("data-bp") ||
                     node.hasAttribute("data-bc") ||
                     node.hasAttribute("data-ae") ||
                     node.hasAttribute("data-ap") ||
                     node.hasAttribute("data-ac") ||
                     node.hasAttribute("data-re") ||
                     node.hasAttribute("data-rp") ||
                     node.hasAttribute("data-rc")));
        }

        static isActualContent(node) {
            return (node !== null &&
                    node.nodeType === Node.ELEMENT_NODE &&
                    node.id && /*accelerator*/
                    (node.hasAttribute("data-t") ||
                     node.hasAttribute("data-e") ||
                     node.hasAttribute("data-p") ||
                     node.hasAttribute("data-c")));
        }

        // -----------------------------------------------------------------------
        // View functions
        // -----------------------------------------------------------------------

        static getUID(view) {
            if (view !== null &&
                view.nodeType === Node.ELEMENT_NODE) {
                // Do not assume that it's a content just because it has an ID.
                if (view.id && NodeView.isContent(view)) {
                    // Here we assume that specified view is also the content.
                    return view.id;
                } else {
                    // A wrapper, when it exists, is always the view.
                    for (const attrName of ["data-we", "data-wp", "data-wc"]) {
                        let uid = view.getAttribute(attrName);
                        if (uid !== null) {
                            return uid;
                        }
                    }
                }
            }

            return null;
        }

        static getContent(view) {
            if (view !== null &&
                view.nodeType === Node.ELEMENT_NODE) {
                if (view.id && NodeView.isContent(view)) {
                    return view;
                } else {
                    for (const attrName of ["data-we", "data-wp", "data-wc"]) {
                        let uid = view.getAttribute(attrName);
                        if (uid !== null) {
                            let child = view.firstChild;
                            while (child !== null) {
                                if (child.id === uid) {
                                    return child;
                                }

                                child = child.nextSibling;
                            }
                        }
                    }
                }
            }

            return null;
        }

        static getTextualContent(view) {
            let content = NodeView.getContent(view);
            if (content !== null &&
                (content.hasAttribute("data-t") ||
                 content.hasAttribute("data-c") ||
                 content.hasAttribute("data-p"))) {
                return content;
            }

            return null;
        }

        static getReplacedContent(view) {
            let content = NodeView.getContent(view);
            if (content !== null &&
                (content.hasAttribute("data-re") ||
                 content.hasAttribute("data-rc") ||
                 content.hasAttribute("data-rp"))) {
                return content;
            }

            return null;
        }

        static isView(node) {
            if (node !== null &&
                node.nodeType === Node.ELEMENT_NODE) {
                if (NodeView.isContentWrapper(node)) {
                    // A wrapper, when it exists, is always the view.
                    return true;
                } else {
                    let uid = node.id;
                    if (uid && NodeView.isContent(node)) {
                        // Content. Is it also the view?
                        return (NodeView.contentToView(node, uid) === node);
                    }
                }
            }

            return false;
        }

        static uidIfElementView(node) {
            if (node !== null &&
                node.nodeType === Node.ELEMENT_NODE) {
                let uid = node.getAttribute("data-we");
                if (uid !== null) {
                    // An element wrapper, when it exists, is always the element
                    // view.
                    return uid;
                }

                uid = node.id;
                if (uid &&
                    (node.hasAttribute("data-e") || node.hasAttribute("data-re"))) {
                    // Element content.
                    let parent = node.parentElement;
                    if (parent === null ||
                        parent.getAttribute("data-we") !== uid) {
                        // No wrapper, hence the element content is also the
                        // element view.
                        return uid;
                    }
                }
            }

            return null;
        }

        static getNextViewSibling(view) {
            assertOrError(NodeView.isView(view));

            let node = view.nextSibling;
            while (node !== null) {
                if (NodeView.isView(node)) {
                    return node;
                }

                node = node.nextSibling;
            }

            return null;
        }

        static getPreviousViewSibling(view) {
            assertOrError(NodeView.isView(view));

            let node = view.previousSibling;
            while (node !== null) {
                if (NodeView.isView(node)) {
                    return node;
                }

                node = node.previousSibling;
            }

            return null;
        }

        static getParentView(view) {
            assertOrError(NodeView.isView(view));

            return NodeView.lookupView(view.parentElement);
        }

        // -----------------------------------------------------------------------
        // Lookup (HTML ancestors) functions
        // -----------------------------------------------------------------------

        // ----------
        // lookupView
        // ----------

        static lookupView(node) {
            let elem = node;
            if (node !== null &&
                node.nodeType !== Node.ELEMENT_NODE) {
                elem = node.parentElement;
            }

            while (elem !== null) {
                let uid = elem.id;
                if (uid) {
                    if (NodeView.isContent(elem)) {
                        // Content. Is it also the view?
                        return NodeView.contentToView(elem, uid);
                    }
                } else {
                    if (NodeView.isContentWrapper(elem)) {
                        // A wrapper, when it exists, is always the view.
                        return elem;
                    }
                }

                elem = elem.parentElement;
            }

            return null;
        }

        // --------------
        // lookupViewPart
        // --------------

        static lookupViewPart(node) {
            while (node !== null) {
                if (node.nodeType === Node.ELEMENT_NODE &&
                    (NodeView.isContentWrapper(node) ||
                     NodeView.isGeneratedContent(node) ||
                     NodeView.isActualContent(node))) {
                    return node;
                }

                node = node.parentElement;
            }

            return null;
        }

        // --------------------
        // lookupTextualContent
        // --------------------

        static lookupTextualContent(node) {
            while (node !== null) {
                if (NodeView.isTextualContent(node)) {
                    return node;
                }

                node = node.parentElement;
            }

            return null;
        }

        // -----------------------------------------------------------------------
        // Find (inside an HTML node tree) functions
        // -----------------------------------------------------------------------

        // ------------------
        // findTextualContent
        // ------------------

        static findTextualContent(tree) {
            if (NodeView.isTextualContent(tree)) {
                return tree;
            }

            let child = tree.firstChild;
            while (child !== null) {
                if (child.nodeType === Node.ELEMENT_NODE &&
                    !NodeView.isGeneratedContent(child)) {
                    let found = NodeView.findTextualContent(child);
                    if (found !== null) {
                        return found;
                    }
                }

                child = child.nextSibling;
            }

            return null;
        }

        // -------------------------
        // pickNearestTextualContent
        // -------------------------

        static pickNearestTextualContent(tree, clientX, clientY) {
            let found = [null, Number.MAX_SAFE_INTEGER];
            NodeView.doPickNearestTextualContent(tree, clientX, clientY, found);
            return (found[0] === null)? null : found[0];
        }

        static doPickNearestTextualContent(tree, clientX, clientY, found) {
            if (NodeView.isTextualContent(tree)) {
                const rects = tree.getClientRects();
                const rectCount = rects.length;
                for (let i = 0; i < rectCount; ++i) {
                    const rect = rects[i];

                    if (rect !== null &&
                        rect.height > 0 && // rect.width=0 OK.
                        clientY >= rect.top && clientY < rect.bottom) {
                        let distance = 0;
                        if (clientX < rect.left) {
                            distance = rect.left - clientX;
                        } else if (clientX >= rect.right) {
                            distance = clientX - rect.right;
                        }

                        if (distance < found[1]) {
                            found[0] = tree;
                            found[1] = distance;
                        }
                    }
                }
            }

            let child = tree.firstChild;
            while (child !== null) {
                if (child.nodeType === Node.ELEMENT_NODE &&
                    !NodeView.isGeneratedContent(child)) {
                    NodeView.doPickNearestTextualContent(child, clientX, clientY,
                                                         found);
                }

                child = child.nextSibling;
            }
        }

        // ---------------------
        // collectTextualContent
        // ---------------------

        static collectTextualContent(view1, offset1, view2, offset2, collected) {
            collected.length = 0;

            if (view1 === view2) {
                let textContent1 = NodeView.getTextualContent(view1);
                if (textContent1 !== null) {
                    collected.push(textContent1);
                }
                return (offset1 > offset2);
            }

            // ---

            let reversed = false;
            if (view1.compareDocumentPosition(view2) &
                    Node.DOCUMENT_POSITION_PRECEDING) {
                // view2 before view1.
                reversed = true;
            }
            if (reversed) {
                let tmp = view1; view1 = view2; view2 = tmp;
                // offset1, offset2 not used below.
            }

            let start = NodeView.getTextualContent(view1);
            if (start === null) {
                // For example a TextNode's view having replaced content.
                start = view1;
            }
            let end = NodeView.getTextualContent(view2);
            if (end === null) {
                end = view2;
            }

            let range = new Range();
            range.setStart(start, 0);
            range.setEnd(end, 0);
            NodeView.doCollectTextualContent(range.commonAncestorContainer,
                                             start, end, [false], collected);

            return reversed;
        }

        static doCollectTextualContent(tree, start, end, collecting, collected) {
            if (collecting[0]) {
                if (NodeView.isTextualContent(tree)) {
                    collected.push(tree);
                }

                if (tree === end) {
                    return true; // Done.
                }
            } else {
                if (tree === start) {
                    collecting[0] = true;

                    if (NodeView.isTextualContent(tree)) {
                        collected.push(tree);
                    }

                    if (tree === end) {
                        return true; // Done.
                    }
                }
            }

            if (!NodeView.isGeneratedContent(tree)) {
                let child = tree.firstChild;
                while (child !== null) {
                    if (child.nodeType === Node.ELEMENT_NODE &&
                        NodeView.doCollectTextualContent(child, start, end,
                                                         collecting, collected)) {
                        return true;
                    }

                    child = child.nextSibling;
                }
            }

            return false;
        }

        // -----------------------------------------------------------------------
        // A text node view (having no replaced content; contenteditable=true)
        // is supposed to contain a single HTML text node.
        //
        // This is not always the case. Why?
        //
        // 1) Contenteditable tend to "damage" its text node.
        //    Ideally, you should find a single text node. In practice
        //    we may end up with none, several or even inserted elements
        //    like <br />.
        //
        // 2) CaretPlaceholder.js inserts <a class="xxe-caret"/> caret placeholder
        //    when the document view looses the keyboard focus.
        //
        // 3) TextHighlight.js renders the text selection by inserting
        //    <mark class="xxe-text-sel">SELECTED TEXT</mark>.
        // -----------------------------------------------------------------------

        // --------------------
        // normalizeTextContent
        // --------------------

        static normalizeTextualContent(textContent) {
            assertOrError(NodeView.isTextualContent(textContent));

            let child = textContent.firstChild;
            if (child === null) {
                child = document.createTextNode("");
                textContent.appendChild(child);
                // Done.
                return;
            }

            if (child.nextSibling === null &&
                child.nodeType === Node.TEXT_NODE) {
                // Nothing to do.
                return;
            }

            let needNormalize = 0;
            while (child !== null) {
                let next = child.nextSibling;

                switch (child.nodeType) {
                case Node.TEXT_NODE:
                    ++needNormalize;
                    break;
                case Node.ELEMENT_NODE:
                    {
                        let inserted = null;

                        let node = child.firstChild;
                        if (node !== null) {
                            if (node.nextSibling === null &&
                                node.nodeType === Node.TEXT_NODE) {
                                // Contains a single text node. Use it as is.
                                child.removeChild(node);
                                inserted = node;
                            } else {
                                let text = child.textContent;
                                if (text !== null && text.length > 0) {
                                    // Contains some text. Create new text node.
                                    inserted = document.createTextNode(text);
                                }
                            }
                        }

                        if (inserted !== null) {
                            textContent.insertBefore(inserted, /*before*/ child);
                            ++needNormalize;
                        }
                        textContent.removeChild(child);
                    }
                    break;
                }

                child = next;
            }

            if (needNormalize > 1) {
                textContent.normalize();
            }
        }

        // --------------------------
        // textualContentToCharOffset
        // --------------------------

        static textualContentToCharOffset(textContent, offset) {
            assertOrError(NodeView.isTextualContent(textContent));

            let result = [null, -1];

            let node = textContent.firstChild;
            if (node === null) {
                node = document.createTextNode("");
                textContent.appendChild(node);
                result[0] = node;
                result[1] = 0;
            } else {
                result[1] = 0;
                if (!NodeView.findCharOffset(textContent, offset, result)) {
                    if (result[0] !== null && result[1] === offset) {
                        // Offset is just after result[0], the last text node
                        // of textContent.

                        // Convert to relative offset.
                        result[1] = result[0].length;
                    } else {
                        // Not found.
                        result[0] = null;
                        result[1] = -1;
                    }
                }
            }

            return result;
        }

        static findCharOffset(tree, offset, result) {
            let node = tree.firstChild;
            while (node !== null) {
                switch (node.nodeType) {
                case Node.TEXT_NODE:
                    {
                        result[0] = node;
                        let start = result[1];
                        result[1] += node.length;

                        if (offset < result[1]) {
                            // Convert to relative offset.
                            result[1] = offset - start;
                            return true;
                        }
                    }
                    break;
                case Node.ELEMENT_NODE:
                    if (NodeView.findCharOffset(node, offset, result)) {
                        return true;
                    }
                    break;
                }

                node = node.nextSibling;
            }

            return false;
        }

        // --------------------------
        // charToTextualContentOffset
        // --------------------------

        static charToTextualContentOffset(pos) {
            let done = false;

            const node = pos[0];
            const offset = pos[1];
            pos[0] = null;
            pos[1] = -1;

            let textContent = NodeView.lookupTextualContent(node);
            if (textContent !== null) {
                let child = textContent.firstChild;
                if (child === null) {
                    // Empty text node view.
                    pos[0] = textContent;
                    pos[1] = 0;
                    done = true;
                } else {
                    if (child.nextSibling === null &&
                        child.nodeType === Node.TEXT_NODE) {
                        // textContent just contains a single text node.
                        pos[0] = textContent;
                        pos[1] = offset;
                        done = true;
                    } else {
                        let startOffset = [0];
                        if (NodeView.findNodeStartOffset(textContent, node,
                                                         startOffset)) {
                            pos[0] = textContent;
                            pos[1] = startOffset[0] + offset;
                            done = true;
                        }
                    }
                }
            }

            return done;
        }

        static findNodeStartOffset(tree, searchedNode, result) {
            let node = tree.firstChild;
            while (node !== null) {
                if (node === searchedNode) {
                    return true;
                }

                switch (node.nodeType) {
                case Node.TEXT_NODE:
                    result[0] += node.length;
                    break;
                case Node.ELEMENT_NODE:
                    if (NodeView.findNodeStartOffset(node, searchedNode, result)) {
                        return true;
                    }
                    break;
                }

                node = node.nextSibling;
            }

            return false;
        }

        // -----------------------------------------------------------------------
        // Expand/collapse views
        // -----------------------------------------------------------------------

        // ----------------
        // expandViewBranch
        // ----------------

        static expandViewBranch(node, docView) {
            let elem = node;
            if (node !== null &&
                node.nodeType !== Node.ELEMENT_NODE) {
                elem = node.parentElement;
            }

            while (elem !== null && elem !== docView) {
                // <xxe-collapser> are found only inside the content before or
                // content after of the element view.

                let uid = elem.id;
                if (uid && NodeView.isContent(elem)) {
                    // Replaced or actual content.

                    NodeView.expandCollapsers(elem, uid);
                } else {
                    // Wrapper?

                    uid = null;
                    for (const attrName of ["data-we", "data-wp", "data-wc"]) {
                        uid = elem.getAttribute(attrName);
                        if (uid !== null) {
                            NodeView.expandCollapsers(elem, uid);
                            break;
                        }
                    }
                }

                // <xxe-collapser2> are found inside the content before or
                // content after of the element view AND ITS DESCENDANT VIEWS.

                if (uid && elem.hasAttribute("data-collapsible")) {
                    // A collapsible styled element view.
                    NodeView.expandCollapsers2(elem, uid);
                }

                elem = elem.parentElement;
            }
        }

        static expandCollapsers(elem, uid) {
            let child = elem.firstChild;
            while (child !== null) {
                if (child.nodeType === Node.ELEMENT_NODE &&
                    (NodeView.isContentBefore(child, uid) ||
                     NodeView.isContentAfter(child, uid))) {
                    NodeView.doExpandCollapsers(child);
                }

                child = child.nextSibling;
            }
        }

        static doExpandCollapsers(elem) {
            let child = elem.firstChild;
            while (child !== null) {
                if (child.nodeType === Node.ELEMENT_NODE) {
                    if ("xxe-collapser" === child.localName) {
                        if (child.collapsed) {
                            child.collapsed = false;
                        }
                    } else {
                        NodeView.doExpandCollapsers(child);
                    }
                }

                child = child.nextSibling;
            }
        }

        static expandCollapsers2(elem, uid) {
            let collapsers = elem.querySelectorAll(`xxe-collapser2[for="${uid}"]`);
            const count = collapsers.length;
            if (count > 0) {
                for (let i = 0; i < count; ++i) {
                    let collapser = collapsers[i];
                    if (collapser.collapsed) {
                        collapser.collapsed = false;
                    }
                }
            }
        }

        // -----------------
        // lookupVisibleView
        // -----------------

        static lookupVisibleView(node, docView) {
            let elem = node;
            if (node !== null &&
                node.nodeType !== Node.ELEMENT_NODE) {
                elem = node.parentElement;
            }

            let visibleView = null;
            while (elem !== null && elem !== docView) {
                let view = null;

                let uid = elem.id;
                if (uid) {
                    if (NodeView.isContent(elem)) {
                        // Content. Is it also the view?
                        view = NodeView.contentToView(elem, uid);
                    }
                } else {
                    if (NodeView.isContentWrapper(elem)) {
                        // A wrapper, when it exists, is always the view.
                        view = elem;
                    }
                }

                if (view !== null) {
                    let display =
                        window.getComputedStyle(view).getPropertyValue("display");
                    if (display) {
                        if (display === "none") {
                            // May be an ancestor view will be visible?
                            visibleView = null;
                        } else {
                            if (visibleView === null) {
                                // Remember "deepest" visible view.
                                visibleView = view;
                            }
                        }
                    }
                }

                elem = elem.parentElement;
            }

            return visibleView;
        }
    }