Merge pull request #4522 from deepak1556/async_execute_javascript_patch

webContents: provide responses for executeJavscript method
This commit is contained in:
Cheng Zhao 2016-02-26 21:17:47 +08:00
commit ebfc127628
10 changed files with 137 additions and 16 deletions

View file

@ -58,7 +58,6 @@ let PDFPageSize = {
// Following methods are mapped to webFrame.
const webFrameMethods = [
'executeJavaScript',
'insertText',
'setZoomFactor',
'setZoomLevel',
@ -106,14 +105,26 @@ let wrapWebContents = function(webContents) {
};
}
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);
});
};
// Make sure webContents.executeJavaScript would run the code only when the
// webContents has been loaded.
const executeJavaScript = webContents.executeJavaScript;
webContents.executeJavaScript = function(code, hasUserGesture) {
webContents.executeJavaScript = function(code, hasUserGesture, callback) {
let requestId = getNextId();
if (typeof hasUserGesture === "function") {
callback = hasUserGesture;
hasUserGesture = false;
}
if (this.getURL() && !this.isLoading())
return executeJavaScript.call(this, code, hasUserGesture);
return asyncWebFrameMethods.call(this, requestId, "executeJavaScript", callback, code, hasUserGesture);
else
return this.once('did-finish-load', executeJavaScript.bind(this, code, hasUserGesture));
return this.once('did-finish-load', asyncWebFrameMethods.bind(this, requestId, "executeJavaScript", callback, code, hasUserGesture));
};
// Dispatch IPC messages to the ipc module.

View file

@ -354,11 +354,17 @@ ipcMain.on('ATOM_BROWSER_GUEST_WEB_CONTENTS', function(event, guestInstanceId) {
}
});
ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function(event, guestInstanceId, method, ...args) {
ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function(event, requestId, guestInstanceId, method, ...args) {
try {
let guestViewManager = require('./guest-view-manager');
let guest = guestViewManager.getGuest(guestInstanceId);
return guest[method].apply(guest, args);
if (requestId) {
const responseCallback = function(result) {
event.sender.send(`ATOM_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, result);
};
args.push(responseCallback);
}
guest[method].apply(guest, args);
} catch (error) {
return event.returnValue = exceptionToMeta(error);
}

View file

@ -15,7 +15,7 @@
#include "native_mate/object_template_builder.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
#include "third_party/WebKit/public/web/WebScriptExecutionCallback.h"
#include "third_party/WebKit/public/web/WebScriptSource.h"
#include "third_party/WebKit/public/web/WebSecurityPolicy.h"
#include "third_party/WebKit/public/web/WebView.h"
@ -26,6 +26,34 @@ namespace atom {
namespace api {
namespace {
class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
public:
using CompletionCallback =
base::Callback<void(
const v8::Local<v8::Value>& result)>;
explicit ScriptExecutionCallback(const CompletionCallback& callback)
: callback_(callback) {}
~ScriptExecutionCallback() {}
void completed(
const blink::WebVector<v8::Local<v8::Value>>& result) override {
if (!callback_.is_null() && !result.isEmpty() && !result[0].IsEmpty())
// Right now only single results per frame is supported.
callback_.Run(result[0]);
delete this;
}
private:
CompletionCallback callback_;
DISALLOW_COPY_AND_ASSIGN(ScriptExecutionCallback);
};
} // namespace
WebFrame::WebFrame()
: web_frame_(blink::WebLocalFrame::frameForCurrentContext()) {
}
@ -124,9 +152,14 @@ void WebFrame::ExecuteJavaScript(const base::string16& code,
mate::Arguments* args) {
bool has_user_gesture = false;
args->GetNext(&has_user_gesture);
scoped_ptr<blink::WebScopedUserGesture> gesture(
has_user_gesture ? new blink::WebScopedUserGesture : nullptr);
web_frame_->executeScriptAndReturnValue(blink::WebScriptSource(code));
ScriptExecutionCallback::CompletionCallback completion_callback;
args->GetNext(&completion_callback);
scoped_ptr<blink::WebScriptExecutionCallback> callback(
new ScriptExecutionCallback(completion_callback));
web_frame_->requestExecuteScriptAndReturnValue(
blink::WebScriptSource(code),
has_user_gesture,
callback.release());
}
mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder(

View file

@ -36,6 +36,14 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, m
electron.webFrame[method].apply(electron.webFrame, args);
});
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);
};
args.push(responseCallback);
electron.webFrame[method].apply(electron.webFrame, args);
});
// Process command line arguments.
var nodeIntegration = 'false';
var preloadScript = null;

View file

@ -391,7 +391,6 @@ var registerWebViewElement = function() {
'printToPDF',
];
nonblockMethods = [
'executeJavaScript',
'insertCSS',
'insertText',
'send',
@ -422,7 +421,7 @@ var registerWebViewElement = function() {
var args, internal;
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
internal = v8Util.getHiddenValue(this, 'internal');
return ipcRenderer.send.apply(ipcRenderer, ['ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', internal.guestInstanceId, m].concat(slice.call(args)));
return ipcRenderer.send.apply(ipcRenderer, ['ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', null, internal.guestInstanceId, m].concat(slice.call(args)));
};
};
for (j = 0, len1 = nonblockMethods.length; j < len1; j++) {
@ -430,6 +429,20 @@ var registerWebViewElement = function() {
proto[m] = createNonBlockHandler(m);
}
proto.executeJavaScript = function(code, hasUserGesture, callback) {
var internal = v8Util.getHiddenValue(this, 'internal');
if (typeof hasUserGesture === "function") {
callback = hasUserGesture;
hasUserGesture = false;
}
let requestId = getNextId();
ipcRenderer.send('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', requestId, internal.guestInstanceId, "executeJavaScript", code, hasUserGesture);
ipcRenderer.once(`ATOM_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, function(event, result) {
if (callback)
callback(result);
});
};
// WebContents associated with this webview.
proto.getWebContents = function() {
var internal = v8Util.getHiddenValue(this, 'internal');

View file

@ -425,10 +425,12 @@ Returns a `String` representing the user agent for this web page.
Injects CSS into the current web page.
### `webContents.executeJavaScript(code[, userGesture])`
### `webContents.executeJavaScript(code[, userGesture, callback])`
* `code` String
* `userGesture` Boolean (optional)
* `callback` Function (optional) - Called after script has been executed.
* `result`
Evaluates `code` in page.

View file

@ -279,10 +279,12 @@ Returns a `String` representing the user agent for guest page.
Injects CSS into the guest page.
### `<webview>.executeJavaScript(code, userGesture)`
### `<webview>.executeJavaScript(code, userGesture, callback)`
* `code` String
* `userGesture` Boolean - Default `false`.
* `callback` Function (optional) - Called after script has been executed.
* `result`
Evaluates `code` in page. If `userGesture` is set, it will create the user
gesture context in the page. HTML APIs like `requestFullScreen`, which require

View file

@ -10,6 +10,7 @@ const screen = require('electron').screen;
const app = remote.require('electron').app;
const ipcMain = remote.require('electron').ipcMain;
const ipcRenderer = require('electron').ipcRenderer;
const BrowserWindow = remote.require('electron').BrowserWindow;
const isCI = remote.getGlobal('isCi');
@ -690,4 +691,22 @@ describe('browser-window module', function() {
assert.equal(fs.existsSync(serializedPath), false);
});
});
describe('window.webContents.executeJavaScript', function() {
var expected = 'hello, world!';
var code = '(() => \"' + expected + '\")()';
it('doesnt throw when no calback is provided', function() {
const result = ipcRenderer.sendSync('executeJavaScript', code, false);
assert.equal(result, 'success');
});
it('returns result when calback is provided', function(done) {
ipcRenderer.send('executeJavaScript', code, true);
ipcRenderer.once('executeJavaScript-response', function(event, result) {
assert.equal(result, expected);
done();
});
});
});
});

View file

@ -138,4 +138,15 @@ app.on('ready', function() {
});
event.returnValue = "done";
});
ipcMain.on('executeJavaScript', function(event, code, hasCallback) {
if (hasCallback) {
window.webContents.executeJavaScript(code, (result) => {
window.webContents.send('executeJavaScript-response', result);
});
} else {
window.webContents.executeJavaScript(code);
event.returnValue = "success";
}
});
});

View file

@ -194,6 +194,7 @@ describe('<webview> tag', function() {
document.body.appendChild(webview);
});
});
describe('partition attribute', function() {
it('inserts no node symbols when not set', function(done) {
webview.addEventListener('console-message', function(e) {
@ -356,6 +357,7 @@ describe('<webview> tag', function() {
document.body.appendChild(webview);
});
});
describe('did-navigate-in-page event', function() {
it('emits when an anchor link is clicked', function(done) {
var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page.html');
@ -556,7 +558,7 @@ describe('<webview> tag', function() {
done();
};
var listener2 = function() {
var jsScript = 'document.getElementsByTagName("video")[0].webkitRequestFullScreen()';
var jsScript = "document.querySelector('video').webkitRequestFullscreen()";
webview.executeJavaScript(jsScript, true);
webview.removeEventListener('did-finish-load', listener2);
};
@ -565,6 +567,20 @@ describe('<webview> tag', function() {
webview.src = "file://" + fixtures + "/pages/fullscreen.html";
document.body.appendChild(webview);
});
it('can return the result of the executed script', function(done) {
var listener = function() {
var jsScript = "'4'+2";
webview.executeJavaScript(jsScript, false, function(result) {
assert.equal(result, '42');
done();
});
webview.removeEventListener('did-finish-load', listener);
};
webview.addEventListener('did-finish-load', listener);
webview.src = "about:blank";
document.body.appendChild(webview);
});
});
describe('sendInputEvent', function() {