Course file download through a blank new tab

Reproduces the Ivalua training-materials failure: the user clicks Download on ivalua.docebosaas.com, a new tab opens, and partway through it closes on its own. No file arrives. /safeview-fileserv/ is never contacted, so the file is never scanned -- the failure is before scanning, not during it.

What the application actually does

From ivalua.docebosaas.com_mac_native.har (the native capture) plus the TAB_open in the isolated captures, the click handler is the common "open blank now, fill it in later" pattern:

win = window.open('');                 // during the click, to beat the popup blocker
meta = await fetch('getCourseFile?file_id=1352&course_id=958');   // 356 ms
win.location = 'downloadCourseFile?course_id=958&file_id=1352&access_token=...';
      // -> 302 -> 302 -> lmsfiles.s3.eu-west-1.amazonaws.com/....zip
      //                  Content-Disposition: attachment

The two things that matter are that the new tab is opened blank and navigated later, and that its first and only navigation is the download. Every case below is a variation on one of those two properties.

Status: one theory tried and discarded

Read this before trusting anything below. The first explanation attempted here has been tested and is wrong.

That theory was a race: the surrogate addresses download messages to the download item's own WebContents, so they go out on the new tab's tab_id channel, and at that moment the new tab's thin client is still booting -- so registerNewDownload() never runs, onDownloadStatusUpdated() early-returns on its dl_prog guard, and /safeview-fileserv/ is never contacted. It fitted the field logs and it fitted the SGH precedent.

The delay sweep in case 3 refutes it. Every delay from 0 ms upward downloads correctly. If the fault were a race against the child's bootstrap, 0 ms would be the worst case and would have to fail. It does not, so the hand-off is reaching a live thin client and being handled. The two surrogate mechanisms involved -- queue_download_updates_, armed only by safeview_navigation_throttle.cc, and the SV-32430 opener retarget, gated on defer_initial_nav -- are real gaps in the code, but they are not what breaks this flow.

What is still untested, and why each is live

Those two are independent, so run them as a 2x2 rather than changing both at once. The tab lifetimes are the tell-tale: 5.7 / 6.2 / 6.1 s is a tight cluster, so whatever ends the tab is either a fixed timeout or a transfer of consistent length -- and only the size control can distinguish those.

What the captures pin down, and what they do not

ClaimEvidence
defer_initial_nav is false, so this is not SV-33388/SV-32430 ["TAB_open",6,"about:blank",false,"",false,false] on the opener's websocket; argument order per thin-client/tab.js onTabOpenRequest()
No popup modal, so no user-dwell delay (unlike the SGH case) allow-tab-open sent 196 ms after TAB_open, with prompted=false
The download was never announced on the opener's channel Full opcode census of both isolated HARs: no download_requested, no download_status_updated. Only a client->surrogate dl-resume at startup
The download is attributed to a tab that never committed a document surrogate-download-requested ... fsid=GssE1FVw-0, where the opener's own session is GssE1FVw-25
The child tab closed itself; the opener only reported it Opener sends tab_close 6 5.7 s after opening, from the win.closed poller at thin-client/tab.js:483-492
NOT established: whether the child tab's thin client ever attached at all The child tab's traffic is in its own DevTools session, which was not recorded. This is what the test below has to settle -- see "Capturing the child tab"

Before you run anything

  1. Web policy for this host is isolate. Not allow, not SSL exception.
  2. Downloads enabled in policy, and file scanning on -- the point is to see whether /safeview-fileserv/ is reached at all.
  3. Run case 4 first as a smoke test. If case 4 does not download a CSV, the environment is wrong and no other result below means anything.
  4. Nothing here has a host or a directory baked into it. Drop the four files -- this page, course_file_redirect.php, get_course_file.php, attachment_csv.php -- into any folder on any PHP-capable server and open this page from there. Every URL is built from this page's own location at runtime.
  5. The chain is same-origin unless you say otherwise. The real S3 hop is cross-site, but that needs a second host and cannot be derived, so it is opt-in below. Leave it blank to start; case 5 is what tells you whether cross-site matters at all.

Optional: second host for the cross-site hop

If you have a second server carrying the same files at the same directory path, put its origin here (scheme + host, e.g. https://other.example.com) and the last redirect will go there instead, matching the ivalua.docebosaas.com → *.amazonaws.com hop. The setting is remembered in this browser.

Transfer size and duration -- set this before anything else

The reported symptom is that the tab closes partway through the download. A file that arrives instantly has no partway, so a small attachment cannot express the failure at all and will pass every case here no matter what else is wrong. The field file is a course archive (file_type: "archive", a .zip), and the three captured tabs lived 5.7 s, 6.2 s and 6.1 s before closing -- so the defaults below aim at roughly that transfer time.

Size MB   spread over ms  

Set the duration to 0 to get the old instant-transfer behaviour back for comparison. That comparison is itself a result: instant passes but slow fails means the fault is in the teardown of a tab that is still transferring, not in how the tab was opened.

Capturing the child tab

The child tab closes itself, which is exactly why the field HARs are silent about it. Turn on DevTools → Settings → Preferences → Global → Auto-open DevTools for popups before clicking. The child gets its own DevTools window that survives long enough to read. What to look for, in order:

  1. Does the child request tc-kernel-min.js / thin-client-min.js and open a wss://.../r/websocket at all? If not, it never had a thin client and the message had nobody to reach.
  2. If it does, note the websocket's open timestamp and compare it with surrogate-download-requested in the surrogate log. The download landing first is the race.
  3. Either way, check whether anything is ever requested from /safeview-fileserv/. In the field, nothing was.

Cases

1. The reported flow (expected failure)

window.open('') during the click, a 356 ms metadata fetch, then set the blank tab's location to a 2-redirect chain ending on an attachment. This is the captured flow, hop for hop -- and fully cross-site too if you filled in a second host above.

Expected failure: a new tab opens, sits blank, then closes on its own. No file. No /safeview-fileserv/ request.

2. Same flow, no metadata delay

Identical, but the location is set in the same task as the window.open. Separates "the app waits on an XHR" from "the tab is opened blank".

If this fails too, the XHR gap is incidental and the blank-tab-plus-download shape is sufficient on its own.

3. Delay sweep -- the discriminator

The same flow at increasing metadata delays. A longer delay gives the child tab's thin client more time to finish booting before the surrogate starts the download.

This is the result that decides the diagnosis. A clean cutover -- short delays fail, long delays succeed -- proves it is a race against the child tab's thin-client bootstrap, and the crossover point is roughly how long that bootstrap takes. Failing at every delay means the child tab never gets a working thin client at all, and the fix has to be the opener retarget rather than any kind of waiting.

4. Direct window.open(url) -- control

No blank tab. The URL is passed to window.open directly, so TAB_open carries the real URL instead of about:blank and the client navigates the child tab itself.

Expected pass. Also the smoke test -- if this fails, stop and fix the environment.

5. Blank tab, same-origin, no redirects

Blank tab and a metadata delay as in case 1, but the location is set straight to the attachment on this origin. Isolates the redirect chain and the cross-site final hop from the new-tab timing.

If both fail the same way as case 1, cross-site and redirects are not part of the cause and the new-tab hand-off is the whole story.

6. Same tab -- control

No new tab at all; navigate this tab to the same chain.

Expected pass. The opener has a live thin client, so the download is announced to somebody who is listening.

Client-side trace

The child tab is watched with the same win.closed poll the thin client uses, so its lifetime is recorded here even though it leaves no HAR of its own. How long the child lived is the number to report -- in the field captures it was 5.7 s and 6.2 s.