5a249cd50f592a3b7598110eec792cfc942fab3f
max
Wed Sep 9 08:52:06 2026 -0700
Address the v504 code review
hgSession: an anonymous share name that arrives with the request is saved only
when it is not already in the table. Every anonymous link sits under the one
reserved user "l", so a name already there stays as it is and the caller is told
so. The top-right Share dialog is unaffected, since it passes a name it has
just reserved and such a name does not exist yet. Snapshot names are now left
out of both My Sessions listings, which is what their "__" prefix has claimed
all along.
Share dialog: "Create link & copy" reports the copy instead of promising it.
copyToClipboard says whether the text reached the clipboard, the dialog passes
that on when a browser refuses, and it tries the asynchronous clipboard API
before giving up. The preview is built with the same encoding the server uses,
so a name holding a hyphen or a slash previews as the link that really gets
made. Cancelling out of the name editor no longer copies a second time, and a
reply with no link in it says so rather than showing "undefined".
hgBlat: a second click on the share button while the first request is still out
no longer mints a second snapshot session, and a box dismissed during the wait
stays closed.
Also: a snapshot moves a cart value into durable storage only when it is a
trash path, the way sessionData's own callers check; sqlAddressMatch keeps to
its own documented precondition when handed an empty address; alphaGenomeToWig
compares its output with its input rather than with itself, rejects a position
that is not all digits and skips an empty score; and hgc's default iframe width
reaches the browser as one percent sign.
refs #38294
diff --git src/hg/js/hgBlat.js src/hg/js/hgBlat.js
index 178b986220d..dd80fbc5c6f 100644
--- src/hg/js/hgBlat.js
+++ src/hg/js/hgBlat.js
@@ -268,30 +268,40 @@
function blatApplyTooltips() {
$('#blatTable thead th').each(function() {
var tip = BLAT_HEADER_TIPS[$(this).text().trim()];
if (tip) { $(this).attr('title', tip); }
});
if (typeof convertTitleTagsToMouseovers === 'function') {
convertTitleTagsToMouseovers();
}
}
// ---- share a link --------------------------------------------------------
// The snapshot link we created for this page view, cached so re-opening the box doesn't make another.
var blatShareCachedUrl = null;
+// True while the create request is on its way. The cached URL only exists once the reply is in, so
+// without this a second click during the wait mints a second snapshot session for the same results.
+var blatShareBusy = false;
+
+// Is the share box on screen? A reply that arrives after the user has clicked the button again to
+// dismiss the box must not reopen it.
+function blatShareBoxOpen(box) {
+ return box.style.display === 'flex';
+}
+
// Render the share box. url set -> show the link + Copy; url null -> "Creating link…"; msg (url null)
// -> show an error.
function blatShowShareBox(box, url, msg) {
box.style.display = 'flex';
if (msg) {
box.innerHTML = '' +
htmlEncode(msg) + '';
return;
}
if (!url) {
box.innerHTML = 'Creating link…';
return;
}
box.innerHTML =
'Anyone with this shareable link can open ' +
@@ -315,48 +325,58 @@
function blatShareLink() {
// Create (or reveal) a durable share link. It is backed by a lightweight "snapshot" session that
// stores only db + the results bigPsl - not the whole cart - under a server-generated unique name
// (see lib/snapshotSession.c). hgBlat's ?u=&s= reopen (doShareReopen) rebuilds the results table
// from that bigPsl. The token generation, uniqueness and cleanup are shared with hgc and the
// top-right "Share a link".
var box = document.getElementById('gbShareBox');
if (!box) { return; }
if (box.style.display === 'flex') { box.style.display = 'none'; return; } // toggle off
// Already viewing a shared session link: the current URL is itself the shareable link.
if (/[?&]s=/.test(window.location.search)) { blatShowShareBox(box, window.location.href); return; }
// Already created one this page view: reuse it rather than creating another session.
if (blatShareCachedUrl) { blatShowShareBox(box, blatShareCachedUrl); return; }
+ // One is already being created: show it working again instead of asking for a second one.
+ if (blatShareBusy) { blatShowShareBox(box, null); return; }
var cfg = hgBlatData.config;
+ blatShareBusy = true;
blatShowShareBox(box, null); // "Creating link…"
var body = 'hgsid=' + encodeURIComponent(cfg.hgsid || '') +
'&hgS_doSaveSessionJson=1&hgS_shareAnon=1&hgS_snapshotType=blat';
fetch('../cgi-bin/hgSession', {method: 'POST', credentials: 'same-origin',
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: body})
.then(function(r) { return r.json(); })
.then(function(data) {
+ blatShareBusy = false;
if (!data || !data.name) {
+ if (blatShareBoxOpen(box))
blatShowShareBox(box, null, (data && data.error) || 'Could not create the link.');
return;
}
blatShareCachedUrl = window.location.origin + '/cgi-bin/hgBlat?u=l&s=' +
encodeURIComponent(data.name);
+ // Keep a box the user dismissed while waiting closed; the link is cached for the
+ // next click on the button.
+ if (blatShareBoxOpen(box))
blatShowShareBox(box, blatShareCachedUrl);
})
.catch(function() {
+ blatShareBusy = false;
+ if (blatShareBoxOpen(box))
blatShowShareBox(box, null, 'Could not reach the server. Please try again.');
});
}
// ---- Rename BLAT track (modal) -------------------------------------------
// The results custom track is built (and renamed) by hgBlat.c's inline code, which exposes a small
// window.blatRenameCt(name, description) helper (it POSTs to hgc's buildBigPsl and rebuilds the
// track). We reuse that helper (no new endpoint), just swapping its old inline toggle-form UI for a
// proper modal dialog. The current name/description come from cfg (hgBlat.c), not a global, so this
// does not depend on any generic page-global.
function blatRenameModalHtml(cfg) {
// hgSession link is relative (same /cgi-bin/), carrying db + hgsid so the session page opens in
// this assembly and cart.
var sessionUrl = `hgSession?db=${encodeURIComponent(cfg.db)}&hgsid=${encodeURIComponent(cfg.hgsid)}`;