From fa71566015986fe65eb8760087286de993f283b8 Mon Sep 17 00:00:00 2001 From: Mathias Bynens Date: Tue, 26 Nov 2019 13:11:55 +0100 Subject: [PATCH 1/2] fix: prepare jsHandle.uploadFile for CDP Page.handleFileChooser removal https://chromium-review.googlesource.com/c/chromium/src/+/1935410 removes Page.handleFileChooser from the CDP. --- lib/JSHandle.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/JSHandle.js b/lib/JSHandle.js index a15be538380f6..28d4e20818656 100644 --- a/lib/JSHandle.js +++ b/lib/JSHandle.js @@ -14,9 +14,12 @@ * limitations under the License. */ -const {helper, assert, debugError} = require('./helper'); +const fs = require('fs'); const path = require('path'); +const {helper, assert, debugError} = require('./helper'); +const readFileAsync = helper.promisify(fs.readFile); + function createJSHandle(context, remoteObject) { const frame = context.frame(); if (remoteObject.subtype === 'node' && frame) { @@ -312,9 +315,21 @@ class ElementHandle extends JSHandle { * @param {!Array} filePaths */ async uploadFile(...filePaths) { - const files = filePaths.map(filePath => path.resolve(filePath)); - const objectId = this._remoteObject.objectId; - await this._client.send('DOM.setFileInputFiles', { objectId, files }); + const promises = filePaths.map(filePath => readFileAsync(filePath, 'utf8')); + const buffers = await Promise.all(promises); + const fileContents = buffers.map(buffer => buffer.toString()); + filePaths = filePaths.map(filePath => path.basename(filePath)); + await this.evaluateHandle((element, filePaths, fileContents) => { + const dt = new DataTransfer(); + for (let i = 0; i < filePaths.length; i++) { + const name = filePaths[i]; + const content = fileContents[i]; + const file = new File([content], name); + dt.items.add(file); + } + element.files = dt.files; + element.dispatchEvent(new Event('input', { 'bubbles': true })); + }, filePaths, fileContents); } async tap() { From e7dcfeb309f020be6277866251acbdf48fce1158 Mon Sep 17 00:00:00 2001 From: Mathias Bynens Date: Wed, 27 Nov 2019 09:52:54 +0100 Subject: [PATCH 2/2] fix: improve binary file support UTF-8-decoding the input file could fail for binary files, and so we now read the raw file buffer and base64-encode it. To base64-decode it within the page context, we use the Fetch API in combination with a data URL. This requires knowing the proper MIME type for the input file, which we now figure out using the new mime-types dependency. --- lib/JSHandle.js | 44 +++++++++++++++++++++++++++----------------- package.json | 2 ++ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/lib/JSHandle.js b/lib/JSHandle.js index 28d4e20818656..5465f5b158361 100644 --- a/lib/JSHandle.js +++ b/lib/JSHandle.js @@ -14,11 +14,7 @@ * limitations under the License. */ -const fs = require('fs'); -const path = require('path'); - const {helper, assert, debugError} = require('./helper'); -const readFileAsync = helper.promisify(fs.readFile); function createJSHandle(context, remoteObject) { const frame = context.frame(); @@ -305,8 +301,8 @@ class ElementHandle extends JSHandle { if (option.selected && !element.multiple) break; } - element.dispatchEvent(new Event('input', { 'bubbles': true })); - element.dispatchEvent(new Event('change', { 'bubbles': true })); + element.dispatchEvent(new Event('input', { bubbles: true })); + element.dispatchEvent(new Event('change', { bubbles: true })); return options.filter(option => option.selected).map(option => option.value); }, values); } @@ -315,21 +311,35 @@ class ElementHandle extends JSHandle { * @param {!Array} filePaths */ async uploadFile(...filePaths) { - const promises = filePaths.map(filePath => readFileAsync(filePath, 'utf8')); - const buffers = await Promise.all(promises); - const fileContents = buffers.map(buffer => buffer.toString()); - filePaths = filePaths.map(filePath => path.basename(filePath)); - await this.evaluateHandle((element, filePaths, fileContents) => { + // These imports are only needed for `uploadFile`, so keep them + // scoped here to avoid paying the cost unnecessarily. + const path = require('path'); + const mime = require('mime-types'); + const fs = require('fs'); + const readFileAsync = helper.promisify(fs.readFile); + + const promises = filePaths.map(filePath => readFileAsync(filePath)); + const files = []; + for (let i = 0; i < filePaths.length; i++) { + const buffer = await promises[i]; + const filePath = path.basename(filePaths[i]); + const file = { + name: filePath, + content: buffer.toString('base64'), + mimeType: mime.lookup(filePath), + }; + files.push(file); + } + await this.evaluateHandle(async(element, files) => { const dt = new DataTransfer(); - for (let i = 0; i < filePaths.length; i++) { - const name = filePaths[i]; - const content = fileContents[i]; - const file = new File([content], name); + for (const item of files) { + const response = await fetch(`data:${item.mimeType};base64,${item.content}`); + const file = new File([await response.blob()], item.name); dt.items.add(file); } element.files = dt.files; - element.dispatchEvent(new Event('input', { 'bubbles': true })); - }, filePaths, fileContents); + element.dispatchEvent(new Event('input', { bubbles: true })); + }, files); } async tap() { diff --git a/package.json b/package.json index c62c1157dec02..19be1754e6111 100644 --- a/package.json +++ b/package.json @@ -29,10 +29,12 @@ "author": "The Chromium Authors", "license": "Apache-2.0", "dependencies": { + "@types/mime-types": "^2.1.0", "debug": "^4.1.0", "extract-zip": "^1.6.6", "https-proxy-agent": "^3.0.0", "mime": "^2.0.3", + "mime-types": "^2.1.25", "progress": "^2.0.1", "proxy-from-env": "^1.0.0", "rimraf": "^2.6.1",