feat(webkit): implement file chooser interception (frontend) (#98)

This commit is contained in:
Pavel Feldman 2019-11-27 13:50:24 -08:00 committed by Dmitry Gozman
parent 494347e009
commit cf9c4d153a
4 changed files with 62 additions and 10 deletions

View File

@ -9,7 +9,7 @@
"playwright": {
"chromium_revision": "719491",
"firefox_revision": "1004",
"webkit_revision": "1001"
"webkit_revision": "1002"
},
"scripts": {
"unit": "node test/test.js",

View File

@ -183,7 +183,9 @@ export class ElementHandle extends js.JSHandle<ElementHandle> {
async setInputFiles(...files: (string|input.FilePayload)[]) {
const multiple = await this.evaluate((element: HTMLInputElement) => !!element.multiple);
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
await this.evaluate(input.setFileInputFunction, await input.loadFiles(files));
const filePayloads = await input.loadFiles(files);
const objectId = this._remoteObject.objectId;
await this._client.send('DOM.setInputFiles', { objectId, files: filePayloads });
}
async focus() {

View File

@ -60,6 +60,7 @@ export class Page extends EventEmitter {
private _disconnectPromise: Promise<Error> | undefined;
private _sessionListeners: RegisteredListener[] = [];
private _emulatedMediaType: string | undefined;
private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>();
static async create(session: TargetSession, target: Target, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue): Promise<Page> {
const page = new Page(session, target, screenshotTaskQueue);
@ -97,6 +98,7 @@ export class Page extends EventEmitter {
this._frameManager.initialize(),
this._session.send('Console.enable'),
this._session.send('Dialog.enable'),
this._session.send('Page.setInterceptFileChooserDialog', { enabled: true }),
]);
}
@ -108,7 +110,8 @@ export class Page extends EventEmitter {
helper.addEventListener(this._session, 'Page.loadEventFired', event => this.emit(Events.Page.Load)),
helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)),
helper.addEventListener(this._session, 'Page.domContentEventFired', event => this.emit(Events.Page.DOMContentLoaded)),
helper.addEventListener(this._session, 'Dialog.javascriptDialogOpening', event => this._onDialog(event))
helper.addEventListener(this._session, 'Dialog.javascriptDialogOpening', event => this._onDialog(event)),
helper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event))
];
}
@ -438,6 +441,32 @@ export class Page extends EventEmitter {
return this._closed;
}
async waitForFileChooser(options: { timeout?: number; } = {}): Promise<FileChooser> {
const {
timeout = this._timeoutSettings.timeout(),
} = options;
let callback;
const promise = new Promise<FileChooser>(x => callback = x);
this._fileChooserInterceptors.add(callback);
return helper.waitWithTimeout<FileChooser>(promise, 'waiting for file chooser', timeout).catch(e => {
this._fileChooserInterceptors.delete(callback);
throw e;
});
}
async _onFileChooserOpened(event: {frameId: Protocol.Network.FrameId, element: Protocol.Runtime.RemoteObject}) {
if (!this._fileChooserInterceptors.size)
return;
const context = await this._frameManager.frame(event.frameId)._utilityContext();
const handle = createJSHandle(context, event.element) as ElementHandle;
const interceptors = Array.from(this._fileChooserInterceptors);
this._fileChooserInterceptors.clear();
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
const fileChooser = new FileChooser(handle, multiple);
for (const interceptor of interceptors)
interceptor.call(null, fileChooser);
}
get mouse(): input.Mouse {
return this._mouse;
}
@ -557,7 +586,28 @@ export class ConsoleMessage {
}
}
type MediaFeature = {
name: string,
value: string
export class FileChooser {
private _element: ElementHandle;
private _multiple: boolean;
private _handled = false;
constructor(element: ElementHandle, multiple: boolean) {
this._element = element;
this._multiple = multiple;
}
isMultiple(): boolean {
return this._multiple;
}
async accept(filePaths: string[]): Promise<any> {
assert(!this._handled, 'Cannot accept FileChooser which is already handled!');
this._handled = true;
await this._element.setInputFiles(...filePaths);
}
async cancel(): Promise<any> {
assert(!this._handled, 'Cannot cancel FileChooser which is already handled!');
this._handled = true;
}
}

View File

@ -38,7 +38,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
});
});
describe.skip(WEBKIT)('Page.waitForFileChooser', function() {
describe('Page.waitForFileChooser', function() {
it('should work when file input is attached to DOM', async({page, server}) => {
await page.setContent(`<input type=file>`);
const [chooser] = await Promise.all([
@ -97,7 +97,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
});
});
describe.skip(WEBKIT)('FileChooser.accept', function() {
describe('FileChooser.accept', function() {
it('should accept single file', async({page, server}) => {
await page.setContent(`<input type=file oninput='javascript:console.timeStamp()'>`);
const [chooser] = await Promise.all([
@ -161,7 +161,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
});
});
describe.skip(WEBKIT)('FileChooser.cancel', function() {
describe('FileChooser.cancel', function() {
it('should cancel dialog', async({page, server}) => {
// Consider file chooser canceled if we can summon another one.
// There's no reliable way in WebPlatform to see that FileChooser was
@ -191,7 +191,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
});
});
describe.skip(WEBKIT)('FileChooser.isMultiple', () => {
describe('FileChooser.isMultiple', () => {
it('should work for single file pick', async({page, server}) => {
await page.setContent(`<input type=file>`);
const [chooser] = await Promise.all([