Skip to content

Commit a96491c

Browse files
authored
feat(downloads): subscribe to download events in Browser domain instead of Page (#6082)
1 parent e37c078 commit a96491c

File tree

3 files changed

+48
-32
lines changed

3 files changed

+48
-32
lines changed

src/server/chromium/crBrowser.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export class CRBrowser extends Browser {
8181
this._connection.on(ConnectionEvents.Disconnected, () => this._didClose());
8282
this._session.on('Target.attachedToTarget', this._onAttachedToTarget.bind(this));
8383
this._session.on('Target.detachedFromTarget', this._onDetachedFromTarget.bind(this));
84+
this._session.on('Browser.downloadWillBegin', this._onDownloadWillBegin.bind(this));
85+
this._session.on('Browser.downloadProgress', this._onDownloadProgress.bind(this));
8486
}
8587

8688
async newContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
@@ -188,6 +190,36 @@ export class CRBrowser extends Browser {
188190
}
189191
}
190192

193+
private _findOwningPage(frameId: string) {
194+
for (const crPage of this._crPages.values()) {
195+
const frame = crPage._page._frameManager.frame(frameId);
196+
if (frame)
197+
return crPage;
198+
}
199+
return null;
200+
}
201+
202+
_onDownloadWillBegin(payload: Protocol.Browser.downloadWillBeginPayload) {
203+
const page = this._findOwningPage(payload.frameId);
204+
assert(page, 'Download started in unknown page: ' + JSON.stringify(payload));
205+
page.willBeginDownload();
206+
207+
let originPage = page._initializedPage;
208+
// If it's a new window download, report it on the opener page.
209+
if (!originPage && page._opener)
210+
originPage = page._opener._initializedPage;
211+
if (!originPage)
212+
return;
213+
this._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
214+
}
215+
216+
_onDownloadProgress(payload: any) {
217+
if (payload.state === 'completed')
218+
this._downloadFinished(payload.guid, '');
219+
if (payload.state === 'canceled')
220+
this._downloadFinished(payload.guid, 'canceled');
221+
}
222+
191223
async _closePage(crPage: CRPage) {
192224
await this._session.send('Target.closeTarget', { targetId: crPage._targetId });
193225
}
@@ -283,7 +315,8 @@ export class CRBrowserContext extends BrowserContext {
283315
promises.push(this._browser._session.send('Browser.setDownloadBehavior', {
284316
behavior: this._options.acceptDownloads ? 'allowAndName' : 'deny',
285317
browserContextId: this._browserContextId,
286-
downloadPath: this._browser.options.downloadsPath
318+
downloadPath: this._browser.options.downloadsPath,
319+
eventsEnabled: true,
287320
}));
288321
}
289322
if (this._options.permissions)

src/server/chromium/crPage.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ export class CRPage implements PageDelegate {
149149
return this._sessionForFrame(frame);
150150
}
151151

152+
willBeginDownload() {
153+
this._mainFrameSession._willBeginDownload();
154+
}
155+
152156
async pageOrError(): Promise<Page | Error> {
153157
return this._pagePromise;
154158
}
@@ -415,8 +419,6 @@ class FrameSession {
415419
private _addBrowserListeners() {
416420
this._eventListeners.push(...[
417421
helper.addEventListener(this._client, 'Inspector.targetCrashed', event => this._onTargetCrashed()),
418-
helper.addEventListener(this._client, 'Page.downloadWillBegin', event => this._onDownloadWillBegin(event)),
419-
helper.addEventListener(this._client, 'Page.downloadProgress', event => this._onDownloadProgress(event)),
420422
helper.addEventListener(this._client, 'Page.screencastFrame', event => this._onScreencastFrame(event)),
421423
helper.addEventListener(this._client, 'Page.windowOpen', event => this._onWindowOpen(event)),
422424
]);
@@ -818,26 +820,13 @@ class FrameSession {
818820
await this._page._onFileChooserOpened(handle);
819821
}
820822

821-
_onDownloadWillBegin(payload: Protocol.Page.downloadWillBeginPayload) {
822-
let originPage = this._crPage._initializedPage;
823-
// If it's a new window download, report it on the opener page.
823+
_willBeginDownload() {
824+
const originPage = this._crPage._initializedPage;
824825
if (!originPage) {
825826
// Resume the page creation with an error. The page will automatically close right
826827
// after the download begins.
827828
this._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
828-
if (this._crPage._opener)
829-
originPage = this._crPage._opener._initializedPage;
830829
}
831-
if (!originPage)
832-
return;
833-
this._crPage._browserContext._browser._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
834-
}
835-
836-
_onDownloadProgress(payload: Protocol.Page.downloadProgressPayload) {
837-
if (payload.state === 'completed')
838-
this._crPage._browserContext._browser._downloadFinished(payload.guid, '');
839-
if (payload.state === 'canceled')
840-
this._crPage._browserContext._browser._downloadFinished(payload.guid, 'canceled');
841830
}
842831

843832
_onScreencastFrame(payload: Protocol.Page.screencastFramePayload) {

tests/download.spec.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { browserTest as it, expect } from './config/browserTest';
1818
import fs from 'fs';
1919
import path from 'path';
2020
import crypto from 'crypto';
21-
import { chromiumVersionLessThan } from './config/utils';
2221

2322
it.describe('download event', () => {
2423
it.beforeEach(async ({server}) => {
@@ -272,12 +271,7 @@ it.describe('download event', () => {
272271
await page.close();
273272
});
274273

275-
it('should report new window downloads', async ({browser, server, browserName, headless}) => {
276-
it.fixme(browserName === 'chromium' && !headless);
277-
278-
// TODO: - the test fails in headed Chromium as the popup page gets closed along
279-
// with the session before download completed event arrives.
280-
// - WebKit doesn't close the popup page
274+
it('should report new window downloads', async ({browser, server}) => {
281275
const page = await browser.newPage({ acceptDownloads: true });
282276
await page.setContent(`<a target=_blank href="${server.PREFIX}/download">download</a>`);
283277
const [ download ] = await Promise.all([
@@ -360,7 +354,7 @@ it.describe('download event', () => {
360354
expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy();
361355
});
362356

363-
it('should close the context without awaiting the failed download', async ({browser, server, httpsServer, browserName, browserVersion}, testInfo) => {
357+
it('should close the context without awaiting the failed download', async ({browser, server, httpsServer, browserName, headless}, testInfo) => {
364358
it.skip(browserName !== 'chromium', 'Only Chromium downloads on alt-click');
365359

366360
const page = await browser.newPage({ acceptDownloads: true });
@@ -378,13 +372,10 @@ it.describe('download event', () => {
378372
page.context().close(),
379373
]);
380374
expect(downloadPath).toBe(null);
381-
if (chromiumVersionLessThan(browserVersion, '91.0.4472.0'))
382-
expect(saveError.message).toContain('File deleted upon browser context closure.');
383-
else
384-
expect(saveError.message).toContain('File not found on disk. Check download.failure() for details.');
375+
expect(saveError.message).toContain('File not found on disk. Check download.failure() for details.');
385376
});
386377

387-
it('should close the context without awaiting the download', async ({browser, server, browserName, platform}, testInfo) => {
378+
it('should close the context without awaiting the download', async ({browser, server, browserName, platform, headless}, testInfo) => {
388379
it.skip(browserName === 'webkit' && platform === 'linux', 'WebKit on linux does not convert to the download immediately upon receiving headers');
389380

390381
server.setRoute('/downloadStall', (req, res) => {
@@ -408,7 +399,10 @@ it.describe('download event', () => {
408399
page.context().close(),
409400
]);
410401
expect(downloadPath).toBe(null);
411-
expect(saveError.message).toContain('File deleted upon browser context closure.');
402+
if (browserName === 'chromium' && headless)
403+
expect(saveError.message).toContain('.saveAs: canceled');
404+
else
405+
expect(saveError.message).toContain('File deleted upon browser context closure.');
412406
});
413407

414408
it('should throw if browser dies', async ({ server, browserType, browserName, browserOptions, platform}, testInfo) => {

0 commit comments

Comments
 (0)