81d7cff6cb6665feb772b1339b6298273a3c49be
max
  Thu Aug 6 08:24:38 2026 -0700
hgBlat new results page: table redesign, rename modal, share link, and an XSS fix

Iterates on the new hgBlat "table" results page (rendered by hg/js/hgBlat.js from
JSON emitted by hgBlat.c) per feedback on #37893:

- Columns: "Open in Genome Browser" (position link + new-tab icon), "Show"
(base alignment), Query coverage moved before Locus, score-scaled bar in the
Score column, Identity as plain %, comma-formatted Span. Locus is plain text,
clipped with a CSS ellipsis (full value in title).
- Tooltips on every action link and every column header (via the existing
convertTitleTagsToMouseovers mechanism).
- "Rename BLAT Track": a real modal dialog (replaces the old inline toggle form),
reusing the existing hgc buildBigPsl call via a new window.blatRenameCt() helper;
it no longer depends on a generic page-global. New cfg fields canRename /
trackName / trackDescription drive it.
- "Share a link": trash-backed stable URL toggle, with a share-nodes icon.
- Security: cgiEncode the query name in the htcUserAli detailsUrl (its sibling
already did), and htmlEncode every URL before it goes into an href in hgBlat.js,
so a crafted query/sequence name can't break out of the attribute (XSS).
- Shared htmlEncode() moved into hg/js/utils.js for reuse instead of a per-file
escaper.
- hg.conf: blatNewPageBanner (invite banner, default off), blatOldTracks
(keep/hide/delete previous BLAT tracks at creation) documented in ex.hg.conf;
hgc.c tags BLAT tracks with blatResult=on and clears prior ones per blatOldTracks.

refs #37893

diff --git src/hg/js/utils.js src/hg/js/utils.js
index 5668209cd37..72bbd6f4408 100644
--- src/hg/js/utils.js
+++ src/hg/js/utils.js
@@ -4305,30 +4305,37 @@
     /* Actually hides the tooltip text */
     ele.classList.remove("isShown");
     ele.style.opacity = "0";
     ele.style.visibility = "hidden";
 }
 
 function titleTagToMouseover(mapEl) {
     /* for a given area tag, extract the title text into a div that can be positioned
     * like a standard tooltip mouseover next to the item */
     if (mapEl.dataset.tooltip)
         addMouseover(mapEl, mapEl.dataset.tooltip);
     else
         addMouseover(mapEl, mapEl.title);
 }
 
+function htmlEncode(s) {
+    /* HTML-escape a value (&, <, >, ", ') so it is safe to insert as text or into an attribute
+     * value in a string of HTML.  Shared helper: prefer this over rolling a per-file escaper.
+     * Uses the browser's own text->markup conversion via a detached element (jQuery required). */
+    return $('<div>').text(s === null || s === undefined ? '' : String(s)).html();
+}
+
 function convertTitleTagsToMouseovers() {
     /* make all the title tags in the document have mouseovers */
     document.querySelectorAll("[title],[data-tooltip]").forEach(function(a, i) {
         if (a.id !== "" && (a.id === "hotkeyHelp" || a.id.endsWith("Dialog") || a.id.endsWith("Popup"))) {
             // these divs are populated by ui-dialog, they should not have tooltips
             return;
         }
         if (a.title !== undefined &&
                 (a.title.startsWith("click & drag to scroll") || a.title.startsWith("drag select or click to zoom")))
             a.title = "";
         else if ((a.title !== undefined && a.title.length > 0) || a.getAttribute("data-tooltip") !== null) {
             if (a.title && a.title.startsWith("Click to alter the display density")) {
                 // these tooltips have a longer delay:
                 a.setAttribute("tooltipDelay", "delayed");
             }