Skip to content

Commit f63ea3f

Browse files
authored
feat(downloads): expose suggested filename (#2062)
1 parent 84f966c commit f63ea3f

File tree

7 files changed

+48
-9
lines changed

7 files changed

+48
-9
lines changed

docs/api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3050,6 +3050,7 @@ const path = await download.path();
30503050
- [download.delete()](#downloaddelete)
30513051
- [download.failure()](#downloadfailure)
30523052
- [download.path()](#downloadpath)
3053+
- [download.suggestedFilename()](#downloadsuggestedfilename)
30533054
- [download.url()](#downloadurl)
30543055
<!-- GEN:stop -->
30553056

@@ -3073,6 +3074,11 @@ Returns download error if any.
30733074

30743075
Returns path to the downloaded file in case of successful download.
30753076

3077+
#### download.suggestedFilename()
3078+
- returns: <[string]>
3079+
3080+
Returns suggested filename for this download. It is typically computed by the browser from the [`Content-Disposition`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) response header or the `download` attribute. See the spec on [whatwg](https://html.spec.whatwg.org/#downloading-resources). Different browsers can use different logic for computing it.
3081+
30763082
#### download.url()
30773083
- returns: <[string]>
30783084

src/browser.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,18 @@ export abstract class BrowserBase extends EventEmitter implements Browser, Inner
5353
return page;
5454
}
5555

56-
_downloadCreated(page: Page, uuid: string, url: string) {
57-
const download = new Download(page, this._downloadsPath, uuid, url);
56+
_downloadCreated(page: Page, uuid: string, url: string, suggestedFilename?: string) {
57+
const download = new Download(page, this._downloadsPath, uuid, url, suggestedFilename);
5858
this._downloads.set(uuid, download);
5959
}
6060

61+
_downloadFilenameSuggested(uuid: string, suggestedFilename: string) {
62+
const download = this._downloads.get(uuid);
63+
if (!download)
64+
return;
65+
download._filenameSuggested(suggestedFilename);
66+
}
67+
6168
_downloadFinished(uuid: string, error?: string) {
6269
const download = this._downloads.get(uuid);
6370
if (!download)

src/chromium/crPage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ class FrameSession {
670670
}
671671
if (!originPage)
672672
return;
673-
this._crPage._browserContext._browser._downloadCreated(originPage, payload.guid, payload.url);
673+
this._crPage._browserContext._browser._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
674674
}
675675

676676
_onDownloadProgress(payload: Protocol.Page.downloadProgressPayload) {

src/download.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import * as util from 'util';
2020
import { Page } from './page';
2121
import { Events } from './events';
2222
import { Readable } from 'stream';
23+
import { assert } from './helper';
2324

2425
export class Download {
2526
private _downloadsPath: string;
@@ -31,23 +32,36 @@ export class Download {
3132
private _failure: string | null = null;
3233
private _deleted = false;
3334
private _url: string;
35+
private _suggestedFilename: string | undefined;
3436

35-
constructor(page: Page, downloadsPath: string, uuid: string, url: string) {
37+
constructor(page: Page, downloadsPath: string, uuid: string, url: string, suggestedFilename?: string) {
3638
this._page = page;
3739
this._downloadsPath = downloadsPath;
3840
this._uuid = uuid;
3941
this._url = url;
42+
this._suggestedFilename = suggestedFilename;
4043
this._finishedCallback = () => {};
4144
this._finishedPromise = new Promise(f => this._finishedCallback = f);
4245
page._browserContext._downloads.add(this);
4346
this._acceptDownloads = !!this._page._browserContext._options.acceptDownloads;
47+
if (suggestedFilename !== undefined)
48+
this._page.emit(Events.Page.Download, this);
49+
}
50+
51+
_filenameSuggested(suggestedFilename: string) {
52+
assert(this._suggestedFilename === undefined);
53+
this._suggestedFilename = suggestedFilename;
4454
this._page.emit(Events.Page.Download, this);
4555
}
4656

4757
url(): string {
4858
return this._url;
4959
}
5060

61+
suggestedFilename(): string {
62+
return this._suggestedFilename!;
63+
}
64+
5165
async path(): Promise<string | null> {
5266
if (!this._acceptDownloads)
5367
throw new Error('Pass { acceptDownloads: true } when you are creating your browser context.');

src/firefox/ffBrowser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class FFBrowser extends BrowserBase {
130130
}
131131
if (!originPage)
132132
return;
133-
this._downloadCreated(originPage, payload.uuid, payload.url);
133+
this._downloadCreated(originPage, payload.uuid, payload.url, payload.suggestedFileName);
134134
}
135135

136136
_onDownloadFinished(payload: Protocol.Browser.downloadFinishedPayload) {

src/webkit/wkBrowser.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export class WKBrowser extends BrowserBase {
5656
helper.addEventListener(this._browserSession, 'Playwright.pageProxyDestroyed', this._onPageProxyDestroyed.bind(this)),
5757
helper.addEventListener(this._browserSession, 'Playwright.provisionalLoadFailed', event => this._onProvisionalLoadFailed(event)),
5858
helper.addEventListener(this._browserSession, 'Playwright.downloadCreated', this._onDownloadCreated.bind(this)),
59+
helper.addEventListener(this._browserSession, 'Playwright.downloadFilenameSuggested', this._onDownloadFilenameSuggested.bind(this)),
5960
helper.addEventListener(this._browserSession, 'Playwright.downloadFinished', this._onDownloadFinished.bind(this)),
6061
helper.addEventListener(this._browserSession, kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this)),
6162
];
@@ -111,6 +112,10 @@ export class WKBrowser extends BrowserBase {
111112
this._downloadCreated(originPage, payload.uuid, payload.url);
112113
}
113114

115+
_onDownloadFilenameSuggested(payload: Protocol.Playwright.downloadFilenameSuggestedPayload) {
116+
this._downloadFilenameSuggested(payload.uuid, payload.suggestedFilename);
117+
}
118+
114119
_onDownloadFinished(payload: Protocol.Playwright.downloadFinishedPayload) {
115120
this._downloadFinished(payload.uuid, payload.error);
116121
}

test/download.spec.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,22 @@ describe('Download', function() {
2525
res.setHeader('Content-Disposition', 'attachment');
2626
res.end(`Hello world`);
2727
});
28+
state.server.setRoute('/downloadWithFilename', (req, res) => {
29+
res.setHeader('Content-Type', 'application/octet-stream');
30+
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
31+
res.end(`Hello world`);
32+
});
2833
});
2934

3035
it('should report downloads with acceptDownloads: false', async({page, server}) => {
31-
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
36+
await page.setContent(`<a href="${server.PREFIX}/downloadWithFilename">download</a>`);
3237
const [ download ] = await Promise.all([
3338
page.waitForEvent('download'),
3439
page.click('a')
3540
]);
3641
let error;
37-
expect(download.url()).toBe(`${server.PREFIX}/download`);
42+
expect(download.url()).toBe(`${server.PREFIX}/downloadWithFilename`);
43+
expect(download.suggestedFilename()).toBe(`file.txt`);
3844
await download.path().catch(e => error = e);
3945
expect(await download.failure()).toContain('acceptDownloads');
4046
expect(error.message).toContain('acceptDownloads: true');
@@ -51,8 +57,8 @@ describe('Download', function() {
5157
expect(fs.readFileSync(path).toString()).toBe('Hello world');
5258
await page.close();
5359
});
54-
it.fail(WEBKIT)('should report non-navigation downloads', async({browser, server}) => {
55-
// Our WebKit embedder does not download in this case, although Safari does.
60+
it.fail(WEBKIT && MAC)('should report non-navigation downloads', async({browser, server}) => {
61+
// Mac WebKit embedder does not download in this case, although Safari does.
5662
server.setRoute('/download', (req, res) => {
5763
res.setHeader('Content-Type', 'application/octet-stream');
5864
res.end(`Hello world`);
@@ -65,6 +71,7 @@ describe('Download', function() {
6571
page.waitForEvent('download'),
6672
page.click('a')
6773
]);
74+
expect(download.suggestedFilename()).toBe(`file.txt`);
6875
const path = await download.path();
6976
expect(fs.existsSync(path)).toBeTruthy();
7077
expect(fs.readFileSync(path).toString()).toBe('Hello world');

0 commit comments

Comments
 (0)