All files / src/web debug-dom.js

96.29% Statements 52/54
83.33% Branches 10/12
100% Functions 12/12
100% Lines 47/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112            532x       34892x       47x 47x 47x 47x 47x 47x       7x       9x         5x 5x 5x                               533x 533x 533x 533x 533x   533x 533x 533x 533x 5x 5x   5x 5x 5x 5x 5x                 1001x       538x 538x 8586x 8586x 8586x                         1029x 1029x 1029x 135x 135x 135x 134x 134x 134x 124x 124x 124x      
import { hexword } from "../hex.js";
 
// The DOM pieces the debugger's views share.
 
/** `$1234`, or `$1234-$1240` for a range of more than one address. */
export function addressRange({ start, end }) {
    return start === end ? `$${hexword(start)}` : `$${hexword(start)}-$${hexword(end)}`;
}
 
export function labelHtml(addr) {
    return '<span class="addr">' + hexword(addr) + "</span>";
}
 
export function externalLink(url, text) {
    const link = document.createElement("a");
    link.href = url;
    link.target = "_blank";
    link.rel = "noopener";
    link.textContent = text;
    return link;
}
 
/** In px, a shortfall that layout rounding can leave, and that still counts as fitting. */
const RoundingSlack = 0.5;
 
/** How many rows of `rowHeight` fit in `height`, at least one. */
export function rowsThatFit(height, rowHeight) {
    return Math.max(1, Math.floor((height + RoundingSlack) / rowHeight));
}
 
/** The most columns, a power of two and at least `fewest`, of `columnWidth` each that fit beside `fixed` in `width`. */
export function powerOfTwoColumnsThatFit(width, fixed, columnWidth, fewest) {
    let count = fewest;
    while (fixed + 2 * count * columnWidth <= width + RoundingSlack) count *= 2;
    return count;
}
 
/**
 * The rows of a view's container, cloned from its template, as many as fit the container's height. The stylesheet
 * sizes the container from `--rows`, set here to `initial`, and not from the rows in it, so that their number never
 * changes the panel's size: the panel being stretched or squeezed changes their number. `refit` redraws them after
 * it has changed.
 */
export class FittedRows {
    /**
     * @param {HTMLElement} container
     * @param {number} initial
     * @param {() => void} refit
     */
    constructor(container, initial, refit) {
        this._container = container;
        this._initial = initial;
        this._template = container.querySelector(".template");
        this._template.remove();
        this._template.classList.remove("template");
        /** @type {HTMLElement[]} */
        this.rows = [];
        container.style.setProperty("--rows", `${initial}`);
        this._setCount(initial);
        new ResizeObserver(([entry]) => {
            const height = entry.contentRect.height;
            const rowHeight = this.rows[0].getBoundingClientRect().height;
            // Both are zero while the panel is hidden.
            Iif (height === 0 || rowHeight === 0) return;
            const count = rowsThatFit(height, rowHeight);
            Iif (count === this.rows.length) return;
            this._setCount(count);
            refit();
        }).observe(container);
    }
 
    /**
     * The row a view puts what it centres on in: the middle of as many as it starts with, kept there as more fit
     * below, so that stretching the panel downwards moves nothing already shown.
     */
    get anchor() {
        return Math.floor(Math.min(this._initial, this.rows.length) / 2);
    }
 
    _setCount(count) {
        while (this.rows.length > count) this.rows.pop().remove();
        while (this.rows.length < count) {
            const row = this._template.cloneNode(true);
            this._container.append(row);
            this.rows.push(row);
        }
    }
}
 
/**
 * Has a form's one text box hand `go` what its text resolves to, marking the box instead when it resolves to nothing.
 * @template Target
 * @param {HTMLFormElement} form
 * @param {(text: string) => Target | undefined} resolve
 * @param {(target: Target) => void} go
 */
export function bindGotoForm(form, resolve, go) {
    const addr = form.querySelector("input");
    addr.addEventListener("input", () => addr.classList.remove("is-invalid"));
    form.addEventListener("submit", (e) => {
        e.preventDefault();
        const text = addr.value.trim();
        if (!text) return;
        const target = resolve(text);
        addr.classList.toggle("is-invalid", target === undefined);
        if (target === undefined) return;
        go(target);
        addr.value = "";
        addr.blur();
    });
}