function distance()

in src/js/other-websites/fathom.js [725:812]


    function distance(fnodeA,
                             fnodeB,
                             {differentDepthCost = 2,
                              differentTagCost = 2,
                              sameTagCost = 1,
                              strideCost = 1,
                              additionalCost = (fnodeA, fnodeB) => 0} = {}) {
        // I was thinking of something that adds little cost for siblings. Up
        // should probably be more expensive than down (see middle example in the
        // Nokia paper).

        // TODO: Test and tune default costs. They're off the cuff at the moment.

        if (fnodeA === fnodeB) {
            return 0;
        }

        const elementA = isDomElement(fnodeA) ? fnodeA : fnodeA.element;
        const elementB = isDomElement(fnodeB) ? fnodeB : fnodeB.element;

        // Stacks that go from the common ancestor all the way to A and B:
        const aAncestors = [elementA];
        const bAncestors = [elementB];

        let aAncestor = elementA;
        let bAncestor = elementB;

        // Ascend to common parent, stacking them up for later reference:
        while (!aAncestor.contains(elementB)) {  // Note: an element does contain() itself.
            aAncestor = aAncestor.parentNode;
            aAncestors.push(aAncestor); //aAncestors = [a, b]. aAncestor = b // if a is outer: no loop here; aAncestors = [a]. aAncestor = a.
        }

        // In compareDocumentPosition()'s opinion, inside implies after. Basically,
        // before and after pertain to opening tags.
        const comparison = elementA.compareDocumentPosition(elementB);

        // If either contains the other, abort. We'd either return a misleading
        // number or else walk upward right out of the document while trying to
        // make the ancestor stack.
        if (comparison & (elementA.DOCUMENT_POSITION_CONTAINS | elementA.DOCUMENT_POSITION_CONTAINED_BY)) {
            return Number.MAX_VALUE;
        }
        // Make an ancestor stack for the right node too so we can walk
        // efficiently down to it:
        do {
            bAncestor = bAncestor.parentNode;  // Assumes we've early-returned above if A === B. This walks upward from the outer node and up out of the tree. It STARTS OUT with aAncestor === bAncestor!
            bAncestors.push(bAncestor);
        } while (bAncestor !== aAncestor);

        // Figure out which node is left and which is right, so we can follow
        // sibling links in the appropriate directions when looking for stride
        // nodes:
        let left = aAncestors;
        let right = bAncestors;
        let cost = 0;
        if (comparison & elementA.DOCUMENT_POSITION_FOLLOWING) {
            // A is before, so it could contain the other node. What did I mean to do if one contained the other?
            left = aAncestors;
            right = bAncestors;
        } else if (comparison & elementA.DOCUMENT_POSITION_PRECEDING) {
            // A is after, so it might be contained by the other node.
            left = bAncestors;
            right = aAncestors;
        }

        // Descend to both nodes in parallel, discounting the traversal
        // cost iff the nodes we hit look similar, implying the nodes dwell
        // within similar structures.
        while (left.length || right.length) {
            const l = left.pop();
            const r = right.pop();
            if (l === undefined || r === undefined) {
                // Punishment for being at different depths: same as ordinary
                // dissimilarity punishment for now
                cost += differentDepthCost;
            } else {
                // TODO: Consider similarity of classList.
                cost += l.tagName === r.tagName ? sameTagCost : differentTagCost;
            }
            // Optimization: strides might be a good dimension to eliminate.
            if (strideCost !== 0) {
                cost += numStrides(l, r) * strideCost;
            }
        }

        return cost + additionalCost(fnodeA, fnodeB);
    }