2eecd47992bf6fcfee294854359117d81167459a
max
  Sat Sep 5 15:51:03 2026 -0700
hgc: show the trackDb iframeUrl frame with the item details, not above them

The iframe from the iframeUrl setting was written out before anything else on
the details page, which is unlike every other part of a details page. It now
appears just below the "View DNA for this feature" line, with the rest of the
details.

Rather than move each of the printIframe() calls, which all happen before the
position is known, printIframe() now only builds the html and a new
printPendingIframe() writes it out from the position-printing routines. That
covers bigBed, bigPsl and the generic click handler alike. The lorax details
page prints it immediately, since there the iframe is the page itself.

Also, a track with an iframeUrl but no url setting never showed its iframe at
all, refs #37595

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index 0cf8c8eda36..fab157dbb55 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -752,51 +752,53 @@
 printBand(chrom, start, end, FALSE);
 printf("<B>Genomic Size:</B> %d<BR>\n", end - start);
 if (strand != NULL && differentString(strand,".") && isNotEmpty(strand))
     printf("<B>Strand:</B> %s<BR>\n", strand);
 else
     strand = "?";
 if (featDna && end > start)
     {
     char *tbl = cgiUsualString("table", cgiString("g"));
     strand = cgiEncode(strand);
     printf("<A HREF=\"%s&db=%s&o=%d&g=getDna&i=%s&c=%s&l=%d&r=%d&strand=%s&table=%s\">"
 	   "View DNA for this feature</A>  (%s/%s)<BR>\n",  hgcPathAndSettings(),
 	   database, start, (item != NULL ? cgiEncode(item) : ""),
 	   cgiEncode(chrom), start, end, strand, tbl, trackHubSkipHubName(database), trackHubSkipHubName(hGenome(database)));
     }
+printPendingIframe();
 }
 
 void printPosOnScaffold(char *chrom, int start, int end, char *strand)
 /* Print position lines referenced to scaffold.  'strand' argument may be null. */
 {
     char *scaffoldName;
     int scaffoldStart, scaffoldEnd;
 
     if (!hScaffoldPos(database, chrom, start, end, &scaffoldName, &scaffoldStart, &scaffoldEnd))
         {
         printPosOnChrom(chrom, start,end,strand, FALSE, NULL);
         return;
         }
     printf("<B>Scaffold:</B> %s<BR>\n", scaffoldName);
     printf("<B>Begin in Scaffold:</B> %d<BR>\n", scaffoldStart+1);
     printf("<B>End in Scaffold:</B> %d<BR>\n", scaffoldEnd);
     printf("<B>Genomic Size:</B> %d<BR>\n", scaffoldEnd - scaffoldStart);
     if (strand != NULL)
 	printf("<B>Strand:</B> %s<BR>\n", strand);
     else
 	strand = "?";
+    printPendingIframe();
 }
 
 void printPos(char *chrom, int start, int end, char *strand, boolean featDna,
 	      char *item)
 /* Print position lines.  'strand' argument may be null. */
 {
 if (sameWord(organism, "Fugu"))
     /* Fugu is the only chrUn-based scaffold assembly, so it
      * has non-general code here.  Later scaffold assemblies
      * treat scaffolds as chroms.*/
     printPosOnScaffold(chrom, start, end, strand);
 else
     printPosOnChrom(chrom, start, end, strand, featDna, item);
 }
 
@@ -922,74 +924,91 @@
     }
 return id;
 }
 
 char *getUrlSetting(struct trackDb *tdb, char* urlSetting)
 /* get the "url" setting for the current track */
 {
 char *url;
 if (sameWord(urlSetting, "url"))
     url = tdb->url;
 else
     url = trackDbSetting(tdb, urlSetting);
 return url;
 }
 
+static struct dyString *pendingIframe = NULL;  // see printIframe() below
+
 void printIframe(struct trackDb *tdb, char *itemName)
-/* print an iframe with the URL specified in trackDb (iframeUrl), can have 
+/* Prepare an iframe with the URL specified in trackDb (iframeUrl), can have 
  * the standard codes in it (like $$ for itemName, etc)
+ * The iframe is not written out here: it is held back and printed by
+ * printPendingIframe(), just after the position / "View DNA" block, so it shows
+ * up with the rest of the item details instead of above them. refs #37595
  */
 {
 char *url = getUrlSetting(tdb, "iframeUrl");
 if (url==NULL)
     return;
 char *eUrl = replaceInUrl(url, itemName, cart, database, seqName, winStart, winEnd, 
                                 tdb->track, FALSE, NULL);
 if (eUrl==NULL)
     return;
 
 char *iframeOptions = trackDbSettingOrDefault(tdb, "iframeOptions", "width='100%%' height='1024'");
 // Resizing requires the hgcDetails pages to include a bit of javascript.
 //
 // Explanation how this works and why the javascript is needed:
 // http://stackoverflow.com/questions/153152/resizing-an-iframe-based-on-content
 // In short:
 // - iframes have a fixed size in html, resizing can only be done in javascript
 // - the iframed page cannot call the resize() function in the hgc html directly, as they have
 //   been loaded from different webservers
 // - one way around it is that the iframed page includes a helper page on our server and 
 //   send their size to the helper page (pages can call functions of included pages)
 // - the helper page then sends the size back to hgc (pages on the same server can
 //   call each others' functions)
 //   width='%s' height='%s' src='%s' seamless scrolling='%s' frameborder='%s'
 
 // The nonce is required: our CSP puts a nonce in script-src, which makes
 // browsers ignore 'unsafe-inline', so an un-nonced inline script never runs.
 // The script stays here, ahead of the iframe, so resizeIframe is defined
 // before the iframed page loads and calls it.
-printf(" \
+dyStringFree(&pendingIframe);
+pendingIframe = dyStringCreate("<br> \
 <script nonce='%s'> \
 function resizeIframe(height) \
 { \
      document.getElementById('hgcIframe').height = parseInt(height)+10; \
 } \
 </script> \
  \
 <iframe id='hgcIframe' src='%s' %s></iframe> \
 <p>", getNonce(), eUrl, iframeOptions);
 }
 
+void printPendingIframe()
+/* Write out the iframe queued up by printIframe(), if there is one. Called from
+ * the position-printing routines so that the iframe lands under the "View DNA"
+ * line, with the other details, and not at the top of the page. */
+{
+if (pendingIframe == NULL)
+    return;
+fputs(pendingIframe->string, stdout);
+dyStringFree(&pendingIframe);
+}
+
 void printCustomUrlWithLabel(struct trackDb *tdb, char *itemName, char *itemLabel, 
                                 char *urlSetting, boolean encode, struct slPair *fields)
 /* Print custom URL specified in trackDb settings. */
 {
 char urlLabelSetting[32];
 
 // replace the $$ and other wildchards with the url given in tdb 
 char *url = getUrlSetting(tdb, urlSetting);
 //char* eUrl = constructUrl(tdb, url, itemName, encode);
 if (url==NULL || isEmpty(url))
     return;
 
 char *eUrl = replaceInUrl(url, itemName, cart, database, seqName, winStart, winEnd, tdb->track, 
                             encode, fields);
 if (eUrl==NULL)
@@ -3720,30 +3739,32 @@
     if (sameString(trackHubSkipHubName(tdb->track), "quickLiftChain"))
         tdb->html = hFileContentsOrWarning(hHelpFile(trackHubSkipHubName(tdb->track)));
     if (liftDb && isEmpty(tdb->html))
         tdb->html = getTrackHtml(liftDb, tdb->table);
     if (tdb->html != NULL && tdb->html[0] != 0)
         return tdb->html;
     }
 return NULL;
 }
 
 void printTrackHtml(struct trackDb *tdb)
 /* If there's some html associated with track print it out. Also print
  * last update time for data table and make a link
  * to the TB table schema page for this table. */
 {
+// safety net: a few detail pages never print a position, so flush the iframe here
+printPendingIframe();
 if (!isCustomTrack(tdb->track) && !isMyVariantsType(tdb->type))
     {
     printRelatedTracks(database, trackHash, tdb, cart);
     extraUiLinks(database, tdb, cart);
     printTrackUiLink(tdb);
     printOrigAssembly(tdb);
     printDataVersion(database, tdb);
     printUpdateTime(database, tdb, NULL);
     printDataRestrictionDate(tdb);
     }
 char *liftDb = cloneString(trackDbSetting(tdb, "quickLiftDb"));
 char *html = getHtmlFromSelfOrParent(tdb, liftDb);
 if (html != NULL && html[0] != 0)
     {
     htmlHorizontalLine();
@@ -5088,34 +5109,37 @@
         headerItem = NULL;
     else if ((  sameString(type, "narrowPeak")
              || sameString(type, "broadPeak")
              || sameString(type, "gappedPeak") )
          &&  headerItem
          &&  sameString(headerItem, ".") )
         headerItem = NULL;
     }
 /* Print header. */
 genericHeader(tdb, headerItem);
 
 if (differentString(type, "bigInteract") && differentString(type, "interact"))
     {
     // skip generic URL code as these may have multiple items returned for a click
     itemForUrl = getIdInUrl(tdb, item);
-    if (itemForUrl != NULL && trackDbSetting(tdb, "url") && differentString(type, "bigBed")
+    // the big* types print their own url and iframe, over in bigBedClick.c
+    if (itemForUrl != NULL && differentString(type, "bigBed")
             && differentString(type, "bigPsl") && differentString(type, "bigGenePred"))
         {
+        if (trackDbSetting(tdb, "url"))
             printCustomUrl(tdb, itemForUrl, item == itemForUrl);
+        // a track can have an iframeUrl without a url, so this is not under the test above
         printIframe(tdb, itemForUrl);
         }
     }
 if (plus != NULL)
     {
     fputs(plus, stdout);
     }
 if (container != NULL)
     {
     genericContainerClick(conn, container, tdb, item, itemForUrl);
     }
 else if (wordCount > 0)
     {
     type = words[0];
     if (sameString(type, "bed"))