feat(downloads): expose suggested filename (#2062)

This commit is contained in:
Pavel Feldman 2020-05-12 19:23:08 -07:00 коммит произвёл GitHub
Родитель 84f966c301
Коммит f63ea3ffd2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 48 добавлений и 9 удалений

Просмотреть файл

@ -3050,6 +3050,7 @@ const path = await download.path();
- [download.delete()](#downloaddelete)
- [download.failure()](#downloadfailure)
- [download.path()](#downloadpath)
- [download.suggestedFilename()](#downloadsuggestedfilename)
- [download.url()](#downloadurl)
<!-- GEN:stop -->
@ -3073,6 +3074,11 @@ Returns download error if any.
Returns path to the downloaded file in case of successful download.
#### download.suggestedFilename()
- returns: <[string]>
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.
#### download.url()
- returns: <[string]>

Просмотреть файл

@ -53,11 +53,18 @@ export abstract class BrowserBase extends EventEmitter implements Browser, Inner
return page;
}
_downloadCreated(page: Page, uuid: string, url: string) {
const download = new Download(page, this._downloadsPath, uuid, url);
_downloadCreated(page: Page, uuid: string, url: string, suggestedFilename?: string) {
const download = new Download(page, this._downloadsPath, uuid, url, suggestedFilename);
this._downloads.set(uuid, download);
}
_downloadFilenameSuggested(uuid: string, suggestedFilename: string) {
const download = this._downloads.get(uuid);
if (!download)
return;
download._filenameSuggested(suggestedFilename);
}
_downloadFinished(uuid: string, error?: string) {
const download = this._downloads.get(uuid);
if (!download)

Просмотреть файл

@ -670,7 +670,7 @@ class FrameSession {
}
if (!originPage)
return;
this._crPage._browserContext._browser._downloadCreated(originPage, payload.guid, payload.url);
this._crPage._browserContext._browser._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
}
_onDownloadProgress(payload: Protocol.Page.downloadProgressPayload) {

Просмотреть файл

@ -20,6 +20,7 @@ import * as util from 'util';
import { Page } from './page';
import { Events } from './events';
import { Readable } from 'stream';
import { assert } from './helper';
export class Download {
private _downloadsPath: string;
@ -31,16 +32,25 @@ export class Download {
private _failure: string | null = null;
private _deleted = false;
private _url: string;
private _suggestedFilename: string | undefined;
constructor(page: Page, downloadsPath: string, uuid: string, url: string) {
constructor(page: Page, downloadsPath: string, uuid: string, url: string, suggestedFilename?: string) {
this._page = page;
this._downloadsPath = downloadsPath;
this._uuid = uuid;
this._url = url;
this._suggestedFilename = suggestedFilename;
this._finishedCallback = () => {};
this._finishedPromise = new Promise(f => this._finishedCallback = f);
page._browserContext._downloads.add(this);
this._acceptDownloads = !!this._page._browserContext._options.acceptDownloads;
if (suggestedFilename !== undefined)
this._page.emit(Events.Page.Download, this);
}
_filenameSuggested(suggestedFilename: string) {
assert(this._suggestedFilename === undefined);
this._suggestedFilename = suggestedFilename;
this._page.emit(Events.Page.Download, this);
}
@ -48,6 +58,10 @@ export class Download {
return this._url;
}
suggestedFilename(): string {
return this._suggestedFilename!;
}
async path(): Promise<string | null> {
if (!this._acceptDownloads)
throw new Error('Pass { acceptDownloads: true } when you are creating your browser context.');

Просмотреть файл

@ -130,7 +130,7 @@ export class FFBrowser extends BrowserBase {
}
if (!originPage)
return;
this._downloadCreated(originPage, payload.uuid, payload.url);
this._downloadCreated(originPage, payload.uuid, payload.url, payload.suggestedFileName);
}
_onDownloadFinished(payload: Protocol.Browser.downloadFinishedPayload) {

Просмотреть файл

@ -56,6 +56,7 @@ export class WKBrowser extends BrowserBase {
helper.addEventListener(this._browserSession, 'Playwright.pageProxyDestroyed', this._onPageProxyDestroyed.bind(this)),
helper.addEventListener(this._browserSession, 'Playwright.provisionalLoadFailed', event => this._onProvisionalLoadFailed(event)),
helper.addEventListener(this._browserSession, 'Playwright.downloadCreated', this._onDownloadCreated.bind(this)),
helper.addEventListener(this._browserSession, 'Playwright.downloadFilenameSuggested', this._onDownloadFilenameSuggested.bind(this)),
helper.addEventListener(this._browserSession, 'Playwright.downloadFinished', this._onDownloadFinished.bind(this)),
helper.addEventListener(this._browserSession, kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this)),
];
@ -111,6 +112,10 @@ export class WKBrowser extends BrowserBase {
this._downloadCreated(originPage, payload.uuid, payload.url);
}
_onDownloadFilenameSuggested(payload: Protocol.Playwright.downloadFilenameSuggestedPayload) {
this._downloadFilenameSuggested(payload.uuid, payload.suggestedFilename);
}
_onDownloadFinished(payload: Protocol.Playwright.downloadFinishedPayload) {
this._downloadFinished(payload.uuid, payload.error);
}

Просмотреть файл

@ -25,16 +25,22 @@ describe('Download', function() {
res.setHeader('Content-Disposition', 'attachment');
res.end(`Hello world`);
});
state.server.setRoute('/downloadWithFilename', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
res.end(`Hello world`);
});
});
it('should report downloads with acceptDownloads: false', async({page, server}) => {
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
await page.setContent(`<a href="${server.PREFIX}/downloadWithFilename">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
let error;
expect(download.url()).toBe(`${server.PREFIX}/download`);
expect(download.url()).toBe(`${server.PREFIX}/downloadWithFilename`);
expect(download.suggestedFilename()).toBe(`file.txt`);
await download.path().catch(e => error = e);
expect(await download.failure()).toContain('acceptDownloads');
expect(error.message).toContain('acceptDownloads: true');
@ -51,8 +57,8 @@ describe('Download', function() {
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it.fail(WEBKIT)('should report non-navigation downloads', async({browser, server}) => {
// Our WebKit embedder does not download in this case, although Safari does.
it.fail(WEBKIT && MAC)('should report non-navigation downloads', async({browser, server}) => {
// Mac WebKit embedder does not download in this case, although Safari does.
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.end(`Hello world`);
@ -65,6 +71,7 @@ describe('Download', function() {
page.waitForEvent('download'),
page.click('a')
]);
expect(download.suggestedFilename()).toBe(`file.txt`);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world');