From 2b547bd44a8eca9bd2d0c7872f0a49906d808af7 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 17 Feb 2016 22:33:27 +0530 Subject: [PATCH] webContents: provide responses for executeJavscript method --- atom/browser/api/lib/ipc-main.js | 4 ++ atom/browser/api/lib/web-contents.js | 14 ++++++- .../native_mate_converters/blink_converter.h | 14 +++++++ atom/renderer/api/atom_api_web_frame.cc | 41 +++++++++++++++++-- atom/renderer/lib/init.js | 10 +++++ atom/renderer/lib/web-view/web-view.js | 17 +++++--- docs/api/web-contents.md | 4 +- docs/api/web-view-tag.md | 4 +- spec/webview-spec.js | 18 +++++++- 9 files changed, 113 insertions(+), 13 deletions(-) diff --git a/atom/browser/api/lib/ipc-main.js b/atom/browser/api/lib/ipc-main.js index e253e03eaabe..d6a043a92bfe 100644 --- a/atom/browser/api/lib/ipc-main.js +++ b/atom/browser/api/lib/ipc-main.js @@ -1,3 +1,7 @@ const EventEmitter = require('events').EventEmitter; module.exports = new EventEmitter; + +// Every webContents would add a listenter to the +// WEB_FRAME_RESPONSE event, so ignore the listenters warning. +module.exports.setMaxListeners(0); diff --git a/atom/browser/api/lib/web-contents.js b/atom/browser/api/lib/web-contents.js index e0c16999c72a..c2376af53a26 100644 --- a/atom/browser/api/lib/web-contents.js +++ b/atom/browser/api/lib/web-contents.js @@ -11,6 +11,7 @@ const debuggerBinding = process.atomBinding('debugger'); let slice = [].slice; let nextId = 0; +let responseCallback = {}; let getNextId = function() { return ++nextId; @@ -109,13 +110,24 @@ let wrapWebContents = function(webContents) { // 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) { + if (typeof hasUserGesture === "function") { + callback = hasUserGesture; + hasUserGesture = false; + } + if (callback !== null) + responseCallback["executeJavaScript"] = callback; if (this.getURL() && !this.isLoading()) return executeJavaScript.call(this, code, hasUserGesture); else return this.once('did-finish-load', executeJavaScript.bind(this, code, hasUserGesture)); }; + ipcMain.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_RESPONSE', function(event, method, result) { + if (responseCallback[method]) + responseCallback[method].apply(null, [result]); + }); + // Dispatch IPC messages to the ipc module. webContents.on('ipc-message', function(event, packed) { var args, channel; diff --git a/atom/common/native_mate_converters/blink_converter.h b/atom/common/native_mate_converters/blink_converter.h index 6a3601929214..f066ea29ccd6 100644 --- a/atom/common/native_mate_converters/blink_converter.h +++ b/atom/common/native_mate_converters/blink_converter.h @@ -6,6 +6,7 @@ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ #include "native_mate/converter.h" +#include "third_party/WebKit/public/platform/WebVector.h" namespace blink { class WebInputEvent; @@ -87,6 +88,19 @@ struct Converter { blink::WebFindOptions* out); }; +template +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + const blink::WebVector& val) { + v8::Local result( + MATE_ARRAY_NEW(isolate, static_cast(val.size()))); + for (size_t i = 0; i < val.size(); ++i) { + result->Set(static_cast(i), Converter::ToV8(isolate, val[i])); + } + return result; + } +}; + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index c72882886b91..06c9621b820a 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -8,6 +8,7 @@ #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" +#include "atom/common/native_mate_converters/blink_converter.h" #include "atom/renderer/api/atom_api_spell_check_client.h" #include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_view.h" @@ -15,7 +16,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 +27,33 @@ namespace atom { namespace api { +namespace { + +class ScriptExecutionCallback : public blink::WebScriptExecutionCallback { + public: + using CompletionCallback = + base::Callback>& result)>; + + explicit ScriptExecutionCallback(const CompletionCallback& callback) + : callback_(callback) {} + ~ScriptExecutionCallback() {} + + void completed( + const blink::WebVector>& result) override { + if (!callback_.is_null()) + callback_.Run(result); + 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 gesture( - has_user_gesture ? new blink::WebScopedUserGesture : nullptr); - web_frame_->executeScriptAndReturnValue(blink::WebScriptSource(code)); + ScriptExecutionCallback::CompletionCallback completion_callback; + args->GetNext(&completion_callback); + scoped_ptr callback( + new ScriptExecutionCallback(completion_callback)); + web_frame_->requestExecuteScriptAndReturnValue( + blink::WebScriptSource(code), + has_user_gesture, + callback.release()); } mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( diff --git a/atom/renderer/lib/init.js b/atom/renderer/lib/init.js index 340a1ef50536..ab03a9055ce9 100644 --- a/atom/renderer/lib/init.js +++ b/atom/renderer/lib/init.js @@ -32,7 +32,17 @@ v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter); const electron = require('electron'); // Call webFrame method. +const asyncWebFrameMethods = [ + 'executeJavaScript' +]; + electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => { + if (asyncWebFrameMethods.includes(method)) { + const responseCallback = function(result) { + event.sender.send('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_RESPONSE', method, result); + }; + args.push(responseCallback); + } electron.webFrame[method].apply(electron.webFrame, args); }); diff --git a/atom/renderer/lib/web-view/web-view.js b/atom/renderer/lib/web-view/web-view.js index 44fc10421621..892ecf56192c 100644 --- a/atom/renderer/lib/web-view/web-view.js +++ b/atom/renderer/lib/web-view/web-view.js @@ -307,7 +307,7 @@ var registerBrowserPluginElement = function() { // Registers custom element. var registerWebViewElement = function() { - var createBlockHandler, createNonBlockHandler, i, j, len, len1, m, methods, nonblockMethods, proto; + var createBlockHandler, createNonBlockHandler, i, j, len, len1, m, methods, nonblockMethods, webFrameMethods, proto; proto = Object.create(HTMLObjectElement.prototype); proto.createdCallback = function() { return new WebViewImpl(this); @@ -391,14 +391,16 @@ var registerWebViewElement = function() { 'printToPDF', ]; nonblockMethods = [ - 'executeJavaScript', 'insertCSS', - 'insertText', 'send', - 'sendInputEvent', + 'sendInputEvent' + ]; + webFrameMethods = [ + 'executeJavaScript', + 'insertText', 'setZoomFactor', 'setZoomLevel', - 'setZoomLevelLimits', + 'setZoomLevelLimits' ]; // Forward proto.foo* method calls to WebViewImpl.foo*. @@ -430,6 +432,11 @@ var registerWebViewElement = function() { proto[m] = createNonBlockHandler(m); } + // Forward proto.foo* webframe method calls to WebFrame.foo*. + for (let method of webFrameMethods) { + proto[method] = webFrame[method].bind(webFrame); + } + // WebContents associated with this webview. proto.getWebContents = function() { var internal = v8Util.getHiddenValue(this, 'internal'); diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index dde701d7f488..376860b17f90 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -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` Array Evaluates `code` in page. diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 4a0697ad843d..3bc4f0e9f647 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -279,10 +279,12 @@ Returns a `String` representing the user agent for guest page. Injects CSS into the guest page. -### `.executeJavaScript(code, userGesture)` +### `.executeJavaScript(code, userGesture, callback)` * `code` String * `userGesture` Boolean - Default `false`. +* `callback` Function (optional) - Called after script has been executed. + * `result` Array Evaluates `code` in page. If `userGesture` is set, it will create the user gesture context in the page. HTML APIs like `requestFullScreen`, which require diff --git a/spec/webview-spec.js b/spec/webview-spec.js index d73a177d0970..fef49de454f2 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -194,6 +194,7 @@ describe(' 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(' 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(' 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(' 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[0], '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() {