d5580b384522b44b2d374446d33304a1adf2e13a
chmalee
  Mon Apr 20 14:05:06 2026 -0700
hgTracks: wrap setInHistory calls in try/catch, refs #37367

diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index 37669810613..4731835736a 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -4460,31 +4460,39 @@
                 imageV2.markAsDirtyPage(); // vis of cfg change
                 imageV2.drawHighlights();
                 if (typeof showMouseovers !== 'undefined' && showMouseovers) {
                     convertTitleTagsToMouseovers();
                 }
                 return;
             }
         }
         
         imageV2.loadRemoteTracks();
         makeItemsByDrag.load();
         imageV2.loadSuggestBox();
         imageV2.drawHighlights();
 
         if (imageV2.backSupport) {
+            // try/catch: browser extensions that proxy history.pushState can throw
+            // "Permission denied to access property apply" under Firefox cross-origin
+            // security. Swallow it so zoom/drag still complete; only the URL-bar
+            // position update is lost.
+            try {
                 imageV2.setInHistory(false);    // Set this new position into History stack
+            } catch (e) {
+                console.warn("setInHistory failed, continuing:", e);
+            }
         } else {
             imageV2.markAsDirtyPage();
         }
         if (typeof showMouseovers !== 'undefined' && showMouseovers) {
             convertTitleTagsToMouseovers();
         }
         if(typeof window.igvBrowser !== "undefined") {
            window.igvBrowser.search(genomePos.get());
         }
     },
 
     updateImgForId: function (html, id, fullImageReload, newJsonRec)
     {   // update row in imgTbl for given id.
         // return true if we successfully pull slice for id and update it in imgTrack.
         var newTr = $(html).find("tr[id='tr_" + id + "']");
@@ -5187,31 +5195,36 @@
         // Have vis box changes update cart through ajax.  This helps keep page/cart in sync.
         vis.initForAjax();
 
         // We reach here from these possible paths:
         // A) Forward: Full page retrieval: hgTracks is first navigated to (or chrom change)
         // B) Back-button past a full retrieval (B in: ->A,->b,->c(full page),->d,<-c,<-B(again))
         //    B1) Dirty page: at least one non-position change (e.g. 1 track vis changed in b)
         //    B2) Clean page: only position changes from A->b->| 
         var curPos = encodeURIComponent(genomePos.get().replace(/,/g,''));
         var curDbPos = hgTracks.lastDbPos;
         var cachedPos = imageV2.history.getState().data.position;
         var cachedDbPos = imageV2.history.getState().data.lastDbPos;
         // A) Forward: Full page retrieval: hgTracks is first navigated to (or chrom change)
         if (!cachedDbPos) { // Not a back-button operation
             // set the current position into history outright (will replace). No img update needed
+            // try/catch: see comment on the other setInHistory call site.
+            try {
                 imageV2.setInHistory(true);
+            } catch (e) {
+                console.warn("setInHistory failed, continuing:", e);
+            }
         } else { // B) Back-button past a full retrieval
             genomePos.set(decodeURIComponent(cachedPos));
             // B1) Dirty page: at least one non-position change 
             if (imageV2.isDirtyPage()) {
                 imageV2.markAsCleanPage();
                 // Only forcing a full page refresh if chrom changes
                 var cachedChrom = decodeURIComponent(cachedPos).split(':')[0];
                 var curChrom    = decodeURIComponent(   curPos).split(':')[0];
                 if (cachedChrom === curChrom) {
                     imageV2.navigateInPlace("db="+getDb()+"&"+cachedDbPos, null, false);
                 } else {
                     imageV2.fullReload();
                 }
             } else {
                 // B2) Clean page: only position changes from a->b