Skip SSE/polling watchers in iframed Galaxy instances

Scratchbook windows (WinBox iframes) load the same analysis route
(``/datasets/X/display``) as the main page, so each one boots a full
Galaxy Vue app. ``historyStore.startWatchingHistory()`` was called
unconditionally in ``App.vue::setup()``, meaning every iframe opened
its own EventSource to ``/api/events/stream``. With 3 long-lived SSE
streams from the main page plus one per iframe, two open dataset
windows is enough to saturate the HTTP/1.1 6-connections-per-origin
budget and hang the tab — ``test_scratchbook_window_persistence`` hung
indefinitely on every CI run of Playwright shard 1.

Treat any frame where ``window.top !== window.self`` as embedded, in
addition to the existing ``?embed=true`` route-query check. That
suppresses history SSE in iframes (and also the existing
``startWatchingEntryPoints`` / ``startWatchingNotifications`` calls,
which were already gated on ``!embedded``), leaving the per-origin
connection budget untouched at 3 used / 3 free so iframes can still
make regular API calls without queueing behind SSE sockets.

Verified locally: test passes in 43s with SSE flags on; previously hung
past the 5-minute timeout.
This commit is contained in:
mvdbeek
2026-04-28 17:20:21 +02:00
parent 35c6077d7e
commit 6463c24e1b
+23 -3
View File
@@ -48,7 +48,7 @@
</template>
<script>
import { storeToRefs } from "pinia";
import { ref, watch } from "vue";
import { computed, ref, watch } from "vue";
import { useRoute } from "vue-router/composables";
import { getGalaxyInstance } from "@/app";
@@ -105,9 +105,29 @@ export default {
const uploadModal = ref(null);
setGlobalUploadModal(uploadModal);
const embedded = useRouteQueryBool("embed");
// Treat any iframe context as embedded: scratchbook pops dataset
// displays into ``WinBox`` iframes that hit the same routes without
// an ``embed`` query param, and each one would otherwise open its own
// SSE + polling traffic, quickly saturating the HTTP/1.1 per-origin
// connection pool (e.g. ``test_scratchbook_window_persistence`` hangs
// indefinitely after two windows are open).
const inIframe = (() => {
if (typeof window === "undefined") {
return false;
}
try {
return window.top !== window.self;
} catch {
// Cross-origin access throws — that's definitely an iframe.
return true;
}
})();
const embeddedQuery = useRouteQueryBool("embed");
const embedded = computed(() => embeddedQuery.value || inIframe);
const historyStore = useHistoryStore();
historyStore.startWatchingHistory();
if (!embedded.value) {
historyStore.startWatchingHistory();
}
watch(
() => embedded.value,