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/hgc/hgc.c src/hg/hgc/hgc.c index e8ae42ba93d..1f822180985 100644 --- src/hg/hgc/hgc.c +++ src/hg/hgc/hgc.c @@ -9073,94 +9073,110 @@ (ident >= 90) ? "#b45309" : "#b1301f"; /* Offer "Share a link" only when a durable bigPsl custom track backs these results; without it there * is nothing for a shared session to rebuild the alignment from. */ char *shareBb = blatFindPinnedBigPsl(cart); boolean canShare = (shareBb != NULL); freeMem(shareBb); /* Colors imported from the BLAT Redesign (slide 3): grey page, steel-blue section-header bars, navy * links with maroon hover, slate text. The

/
the shared alignment code emits are hidden; its *

section headings become the steel-blue bars. The page chrome is webStartGbNoBanner (a menubar * and
, no legacy section tables), so we draw our own gold title bar in plain HTML. */ printf("\n"); /* gold title bar, drawn directly (no framework subheadingBar, no JS): title on the left, then a * "Share a link" button (when a durable track backs the results) and a "Back to results" button. */ printf("
"); printf("BLAT Base Alignment: %s", blatAsmLabel(database)); printf(""); printf("" "\xe2\x80\xb9 Back to results", cartSessionId(cart)); if (canShare) printf("Share a link"); printf("
\n"); /* one white panel laid out as two grid columns: a full-height "jump to" sidebar on the left, and on * the right an "Alignment Summary" header, the summary line, and the base-by-base alignment inlined * so the whole page scrolls */ printf("
\n"); printf("
\n"); printf("

Alignment Summary

\n"); /* comma-format the coordinates and base counts, matching the new Table view (readable at the * hundreds-of-millions scale of genomic coordinates, and the convention elsewhere in the browser) */ char tStartC[32], tEndC[32], matchC[32], qSizeC[32]; sprintLongWithCommas(tStartC, psl->tStart + 1); sprintLongWithCommas(tEndC, psl->tEnd); sprintLongWithCommas(matchC, psl->match + psl->repMatch); sprintLongWithCommas(qSizeC, psl->qSize); -printf("

%s aligned to %s:%s-%s, " - "%.1f%% identity, " - "%s of %s bases matched, strand %s.

\n", - qName, chrom, tStartC, tEndC, idColor, ident, - matchC, qSizeC, psl->strand); +/* key-value strip (Query / Position / Identity / Matches / Strand), styled like hgBlat's summary + * strip so the two pages read as one design. */ +printf("
" + "
Query%s
" + "
" + "
Position%s:%s-%s
" + "
" + "
Identity" + "%.1f%%
" + "
" + "
Matches%s of %s
" + "
" + "
Strand%s
" + "
\n", + qName, chrom, tStartC, tEndC, idColor, ident, matchC, qSizeC, psl->strand); if (isNotEmpty(aliasStr)) printf("

Genome sequence %s is also known as: %s.

\n", chrom, aliasStr); /* The shared library returns the number of alignment blocks it actually shows. The DNA path merges * blocks separated by gaps <= 8 bases, so this can be fewer than psl->blockCount; use it (not * psl->blockCount) so the sidebar's "Block N" links match the #1..#N anchors that were emitted. */ int blockCount; if (qType == gftRna || qType == gftDna) blockCount = showPartialDnaAlignment(psl, oSeq, stdout, cdsS, cdsE, FALSE); else blockCount = showGfAlignment(psl, oSeq, stdout, qType, qStart, qEnd, qName); printf("
\n"); /* #blatAlnContent */ /* Sidebar, emitted after the alignment so blockCount is known; CSS grid puts it back in column 1. * The inner div is position:sticky so the links stay in view as the long alignment scrolls. */ printf("
\n"); @@ -27384,40 +27400,71 @@ * Bring up the bigPsl detail page with all the alignments. */ { char *trackName = cartString(cart, "trackName"); char *trackDescription = cartString(cart, "trackDescription"); char *pslName, *faName, *qName; parseSs(fileNames, &pslName, &faName, &qName); struct tempName bigBedTn; trashDirDateFile(&bigBedTn, "hgBlat", "bp", ".bb"); char *bigBedFile = bigBedTn.forCgi; makeBigPsl(pslName, faName, database, bigBedFile); char* host = getenv("HTTP_HOST"); boolean isProt = cgiOptionalString("isProt") != NULL; -char *customTextTemplate = "track type=bigPsl indelDoubleInsert=on indelQueryInsert=on pslFile=%s visibility=pack showAll=on htmlUrl=http://%s/goldenPath/help/hgUserPsl.html %s bigDataUrl=%s name=\"%s\" description=\"%s\" colorByStrand=\"0,0,0 0,0,150\" mouseOver=\"${oChromStart}-${oChromEnd} of ${oChromSize} bp, strand ${oStrand}\"\n"; +// blatResult=on tags this as a BLAT results track so previous ones can be found (see blatOldTracks). +char *customTextTemplate = "track type=bigPsl blatResult=on indelDoubleInsert=on indelQueryInsert=on pslFile=%s visibility=pack showAll=on htmlUrl=http://%s/goldenPath/help/hgUserPsl.html %s bigDataUrl=%s name=\"%s\" description=\"%s\" colorByStrand=\"0,0,0 0,0,150\" mouseOver=\"${oChromStart}-${oChromEnd} of ${oChromSize} bp, strand ${oStrand}\"\n"; char *extraForMismatch = "indelPolyA=on showDiffBasesAllScales=. baseColorUseSequence=lfExtra baseColorDefault=diffBases"; if (isProt) extraForMismatch = ""; char buffer[4096]; safef(buffer, sizeof buffer, customTextTemplate, bigBedTn.forCgi, host, extraForMismatch, bigBedTn.forCgi, trackName, trackDescription); struct customTrack *ctList = getCtList(); struct customTrack *newCts = customFactoryParse(database, buffer, FALSE, NULL, NULL); + +/* Optionally clear PREVIOUS BLAT result tracks (those tagged blatResult=on) so the user is not + * confused about which results are current. hg.conf "blatOldTracks": + * keep (default) - do nothing, every search's track stays as-is + * hide - leave earlier BLAT tracks in the session but set them to hide + * delete - remove earlier BLAT tracks from the session (their trash files age out) + * Only BLAT-tagged tracks are touched; the track just made is left alone. */ +char *oldTracks = cfgOptionDefault("blatOldTracks", "keep"); +if (differentString(oldTracks, "keep")) + { + struct customTrack *ct, *next, *keptList = NULL; + for (ct = ctList; ct != NULL; ct = next) + { + next = ct->next; + if (ct->tdb != NULL && sameOk(trackDbSetting(ct->tdb, "blatResult"), "on")) + { + if (sameString(oldTracks, "hide")) + { + cartSetString(cart, ct->tdb->track, "hide"); + slAddHead(&keptList, ct); /* keep it in the session, just hidden */ + } + /* "delete": drop it from the list so customTracksSaveCart writes it out */ + } + else + slAddHead(&keptList, ct); + } + slReverse(&keptList); + ctList = keptList; + } + theCtList = customTrackAddToList(ctList, newCts, NULL, FALSE); customTracksSaveCart(database, cart, theCtList); /* Pin this bigPsl file in the cart so hgBlat's Table view can reopen exactly these results from a * shared session (see doShareReopen in hgBlat.c) - unambiguously, even when the cart holds several * BLAT custom tracks from earlier searches. */ cartSetString(cart, "blatLastBigBed", bigBedFile); cartSetString(cart, "i", "PrintAllSequences"); hgCustom(newCts->tdb->track, NULL); if (sameOk(cartOptionalString(cart, "autoRearr"), "1")) { char snakeVar[256];