From 124fbdbd7427dda8512be557e387a70eac38bd94 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sun, 9 Oct 2016 11:51:46 +1100 Subject: [PATCH 1/5] Allow web frame methods to return async promises --- lib/renderer/init.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/renderer/init.js b/lib/renderer/init.js index 2356a81dda2..de5bf35509a 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -39,7 +39,13 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_SYNC_WEB_FRAME_METHOD', (eve electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => { const responseCallback = function (result) { - event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, result) + Promise.resolve(result) + .then((resolvedResult) => { + event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, resolvedResult) + }) + .catch((resolvedError) => { + console.error(`An async web frame method (${method}) returned a promise that threw an error: `, resolvedError) + }) } args.push(responseCallback) electron.webFrame[method].apply(electron.webFrame, args) From 75b010ce6314c56ec083adfa1a9835e3cfa348ea Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 11 Oct 2016 10:40:05 +1100 Subject: [PATCH 2/5] Add sync executeJavaScript test --- lib/renderer/init.js | 2 +- spec/api-browser-window-spec.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/renderer/init.js b/lib/renderer/init.js index de5bf35509a..50250e16926 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -48,7 +48,7 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev }) } args.push(responseCallback) - electron.webFrame[method].apply(electron.webFrame, args) + electron.webFrame[method](...args) }) // Process command line arguments. diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index e268af969d9..0ee01e9244b 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1463,6 +1463,7 @@ describe('browser-window module', function () { describe('window.webContents.executeJavaScript', function () { var expected = 'hello, world!' var code = '(() => "' + expected + '")()' + var asyncCode = '(() => new Promise(r => setTimeout(() => r("' + expected + '"), 500)))()' it('doesnt throw when no calback is provided', function () { const result = ipcRenderer.sendSync('executeJavaScript', code, false) @@ -1477,6 +1478,14 @@ describe('browser-window module', function () { }) }) + it('returns result if the code returns an asyncronous promise', function (done) { + ipcRenderer.send('executeJavaScript', asyncCode, true) + ipcRenderer.once('executeJavaScript-response', function (event, result) { + assert.equal(result, expected) + done() + }) + }) + it('works after page load and during subframe load', function (done) { w.webContents.once('did-finish-load', function () { // initiate a sub-frame load, then try and execute script during it From 857e1da6a3a48a4fa4b9ba4c30b99f80b94bdbc8 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 11 Oct 2016 16:47:09 +1100 Subject: [PATCH 3/5] Make executeJavaScript return a Promise so that caught errors can be sent to the caller --- docs/api/web-contents.md | 14 ++++++++++++++ lib/browser/api/web-contents.js | 20 ++++++++++++++------ lib/renderer/init.js | 2 +- spec/api-browser-window-spec.js | 26 ++++++++++++++++++++++++++ spec/static/main.js | 4 ++++ 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 96e3eee7df4..367b6b474a8 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -620,12 +620,26 @@ Injects CSS into the current web page. * `callback` Function (optional) - Called after script has been executed. * `result` Any +Returns `Promise` - A promise that resolves with the result of the executed code +or is rejected if the result of the code is a rejected promise + Evaluates `code` in page. In the browser window some HTML APIs like `requestFullScreen` can only be invoked by a gesture from the user. Setting `userGesture` to `true` will remove this limitation. +If the result of the executed code is a promise the callback result will be the +resolved value of the promise. We recommend that you use the returned Promise +to handle code that results in a Promise. + +```js +contents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true) + .then((result) => { + console.log(result) // Will be the JSON object from the fetch call + }) +``` + #### `contents.setAudioMuted(muted)` * `muted` Boolean diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 560b323d619..196f90b29bf 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -110,9 +110,15 @@ const webFrameMethodsWithResult = [ ] const asyncWebFrameMethods = function (requestId, method, callback, ...args) { - this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) - ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, result) { - if (callback) callback(result) + return new Promise((resolve, reject) => { + this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) + ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, result) { + if (callback) callback(result) + resolve(result) + }) + ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_ERROR_${requestId}`, (event, error) => { + reject(error) + }) }) } @@ -146,10 +152,12 @@ WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callba hasUserGesture = false } if (this.getURL() && !this.isLoadingMainFrame()) { - asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture) + return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture) } else { - this.once('did-finish-load', () => { - asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture) + return new Promise((resolve, reject) => { + this.once('did-finish-load', () => { + asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture).then(resolve).catch(reject) + }) }) } } diff --git a/lib/renderer/init.js b/lib/renderer/init.js index 50250e16926..202c124d69f 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -44,7 +44,7 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, resolvedResult) }) .catch((resolvedError) => { - console.error(`An async web frame method (${method}) returned a promise that threw an error: `, resolvedError) + event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_ERROR_${requestId}`, resolvedError) }) } args.push(responseCallback) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 0ee01e9244b..0f5e168dd34 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1462,8 +1462,10 @@ describe('browser-window module', function () { describe('window.webContents.executeJavaScript', function () { var expected = 'hello, world!' + var expectedErrorMsg = 'woops!' var code = '(() => "' + expected + '")()' var asyncCode = '(() => new Promise(r => setTimeout(() => r("' + expected + '"), 500)))()' + var badAsyncCode = '(() => new Promise((r, e) => setTimeout(() => e("' + expectedErrorMsg + '"), 500)))()' it('doesnt throw when no calback is provided', function () { const result = ipcRenderer.sendSync('executeJavaScript', code, false) @@ -1486,6 +1488,30 @@ describe('browser-window module', function () { }) }) + it('resolves the returned promise with the result', function (done) { + ipcRenderer.send('executeJavaScript', code, true) + ipcRenderer.once('executeJavaScript-promise-response', function (event, result) { + assert.equal(result, expected) + done() + }) + }) + + it('resolves the returned promise with the result if the code returns an asyncronous promise', function (done) { + ipcRenderer.send('executeJavaScript', asyncCode, true) + ipcRenderer.once('executeJavaScript-promise-response', function (event, result) { + assert.equal(result, expected) + done() + }) + }) + + it('rejects the returned promise if an async error is thrown', function (done) { + ipcRenderer.send('executeJavaScript', badAsyncCode, true) + ipcRenderer.once('executeJavaScript-promise-error', function (event, error) { + assert.equal(error, expectedErrorMsg) + done() + }) + }) + it('works after page load and during subframe load', function (done) { w.webContents.once('did-finish-load', function () { // initiate a sub-frame load, then try and execute script during it diff --git a/spec/static/main.js b/spec/static/main.js index 58d8a1970a9..ffc13e39a8e 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -173,6 +173,10 @@ app.on('ready', function () { if (hasCallback) { window.webContents.executeJavaScript(code, (result) => { window.webContents.send('executeJavaScript-response', result) + }).then((result) => { + window.webContents.send('executeJavaScript-promise-response', result) + }).catch((err) => { + window.webContents.send('executeJavaScript-promise-error', err) }) } else { window.webContents.executeJavaScript(code) From 9f18a6e65c23f6440fc83c87ea2965d903bbaf6e Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 24 Oct 2016 18:19:45 +1100 Subject: [PATCH 4/5] Use ES6 template strings, fix docs and remove _ERROR_ IPC event --- docs/api/web-contents.md | 2 +- lib/browser/api/web-contents.js | 10 ++++------ lib/renderer/init.js | 4 ++-- spec/api-browser-window-spec.js | 6 +++--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 367b6b474a8..4d868081360 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -621,7 +621,7 @@ Injects CSS into the current web page. * `result` Any Returns `Promise` - A promise that resolves with the result of the executed code -or is rejected if the result of the code is a rejected promise +or is rejected if the result of the code is a rejected promise. Evaluates `code` in page. diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 196f90b29bf..695ec8a4092 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -112,12 +112,10 @@ const webFrameMethodsWithResult = [ const asyncWebFrameMethods = function (requestId, method, callback, ...args) { return new Promise((resolve, reject) => { this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) - ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, result) { - if (callback) callback(result) - resolve(result) - }) - ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_ERROR_${requestId}`, (event, error) => { - reject(error) + ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, error, result) { + if (callback && !error) callback(result) + if (error) return reject(error) + return resolve(result) }) }) } diff --git a/lib/renderer/init.js b/lib/renderer/init.js index 202c124d69f..05733f678a8 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -41,10 +41,10 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev const responseCallback = function (result) { Promise.resolve(result) .then((resolvedResult) => { - event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, resolvedResult) + event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, null, resolvedResult) }) .catch((resolvedError) => { - event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_ERROR_${requestId}`, resolvedError) + event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, resolvedError) }) } args.push(responseCallback) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 0f5e168dd34..9bfede278bc 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1463,9 +1463,9 @@ describe('browser-window module', function () { describe('window.webContents.executeJavaScript', function () { var expected = 'hello, world!' var expectedErrorMsg = 'woops!' - var code = '(() => "' + expected + '")()' - var asyncCode = '(() => new Promise(r => setTimeout(() => r("' + expected + '"), 500)))()' - var badAsyncCode = '(() => new Promise((r, e) => setTimeout(() => e("' + expectedErrorMsg + '"), 500)))()' + var code = `(() => "${expected}")()` + var asyncCode = `(() => new Promise(r => setTimeout(() => r("${expected}"), 500)))()` + var badAsyncCode = `(() => new Promise((r, e) => setTimeout(() => e("${expectedErrorMsg}"), 500)))()` it('doesnt throw when no calback is provided', function () { const result = ipcRenderer.sendSync('executeJavaScript', code, false) From 8e203592e25db081dc68db086938157d0847f169 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 3 Nov 2016 09:51:01 -0700 Subject: [PATCH 5/5] :art: Remove extra returns --- lib/browser/api/web-contents.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 695ec8a4092..d363bb051b8 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -113,9 +113,12 @@ const asyncWebFrameMethods = function (requestId, method, callback, ...args) { return new Promise((resolve, reject) => { this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, error, result) { - if (callback && !error) callback(result) - if (error) return reject(error) - return resolve(result) + if (error == null) { + if (callback != null) callback(result) + resolve(result) + } else { + reject(error) + } }) }) }