diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d3262947b..e9fd7554d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -140,6 +140,8 @@ jobs: node test-packet-filter-time.js node test-issue-121-clear-filters-selection.js node test-clear-filters.js + node test-issue-147-packets-url-detail-params.js + node test-packet-path-map.js node test-confidence-indicator.js node test-1659-analytics-warmup.js node test-issue-120-distance-building.js @@ -503,6 +505,7 @@ jobs: BASE_URL=http://localhost:13581 node test-charts-fluid-1058-e2e.js 2>&1 | tee -a e2e-output.txt BASE_URL=http://localhost:13581 node test-slideover-1056-e2e.js 2>&1 | tee -a e2e-output.txt BASE_URL=http://localhost:13581 node test-issue-1692-packets-init-parallel-e2e.js 2>&1 | tee -a e2e-output.txt + BASE_URL=http://localhost:13581 node test-issue-147-packets-url-obs-e2e.js 2>&1 | tee -a e2e-output.txt BASE_URL=http://localhost:13581 node test-slideover-1168-munger-e2e.js 2>&1 | tee -a e2e-output.txt BASE_URL=http://localhost:13581 node test-logo-pulse-1173-e2e.js 2>&1 | tee -a e2e-output.txt BASE_URL=http://localhost:13581 node test-issue-1122-packets-filter-ux-e2e.js 2>&1 | tee -a e2e-output.txt diff --git a/public/packet-path-map.js b/public/packet-path-map.js index 952ed72c5..d864822f0 100644 --- a/public/packet-path-map.js +++ b/public/packet-path-map.js @@ -76,18 +76,47 @@ var activeMap = null; + // Registered in the capture phase while the modal is open: Escape closes + // only this top layer. Stopping it keeps the layers below (the packets + // detail pane, a SlideOver), whose Escape handlers listen on document in + // the bubble phase, open until the next Escape (#167). function onKeydown(e) { - if (e.key === 'Escape') close(); + if (e.key === 'Escape' && document.getElementById('packetPathModal')) { + e.stopPropagation(); + close(); + } } - function close() { + // A #/packets/?…&viewPath=1 URL describes this modal as open (#147). + // Once it closes, drop that one param (the others stay verbatim) so a + // refresh or a copied address-bar link does not reopen it. This writes the + // hash next to packets.js's updatePacketsUrl(), which adds ?viewPath=1 while + // the modal is open on the packet of the #/packets/ subpath. + function dropViewPathParam() { + if (typeof history === 'undefined' || !history.replaceState) return; + var h = String(location.hash || ''); + var q = h.indexOf('?'); + if (q < 0 || h.indexOf('#/packets/') !== 0) return; + var params = h.slice(q + 1).split('&'); + var kept = params.filter(function (p) { return p !== 'viewPath=1'; }); + if (kept.length === params.length) return; + history.replaceState(null, '', h.slice(0, q) + (kept.length ? '?' + kept.join('&') : '')); + } + + // Removes the modal; true when one was open. + function removeModal() { var overlay = document.getElementById('packetPathModal'); if (overlay) overlay.remove(); if (activeMap) { try { activeMap.remove(); } catch (e) { /* already gone */ } activeMap = null; } - document.removeEventListener('keydown', onKeydown); + document.removeEventListener('keydown', onKeydown, true); + return !!overlay; + } + + function close() { + if (removeModal()) dropViewPathParam(); } // A short prefix marking a node's role in tooltips -- purely a label, @@ -137,11 +166,15 @@ } async function open(hash) { - close(); // in case one's already open + // In case one's already open. Replacing it is not a close, so a + // ?viewPath=1 in the URL stays (#167). + removeModal(); var overlay = document.createElement('div'); overlay.id = 'packetPathModal'; overlay.className = 'modal-overlay'; + // The packet shown, for packets.js's ?viewPath=1 (#167). + overlay.dataset.hash = hash; overlay.innerHTML = '