webContents: provide responses for executeJavscript method
authorRobo <hop2deep@gmail.com>
Wed, 17 Feb 2016 17:03:27 +0000 (22:33 +0530)
committerRobo <hop2deep@gmail.com>
Thu, 25 Feb 2016 06:15:59 +0000 (11:45 +0530)
atom/browser/api/lib/ipc-main.js
atom/browser/api/lib/web-contents.js
atom/common/native_mate_converters/blink_converter.h
atom/renderer/api/atom_api_web_frame.cc
atom/renderer/lib/init.js
atom/renderer/lib/web-view/web-view.js
docs/api/web-contents.md
docs/api/web-view-tag.md
spec/webview-spec.js

index e253e03..d6a043a 100644 (file)
@@ -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);
index e0c1699..c2376af 100644 (file)
@@ -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;
index 6a36019..f066ea2 100644 (file)
@@ -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> {
                      blink::WebFindOptions* out);
 };
 
+template<typename T>
+struct Converter<blink::WebVector<T> > {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const blink::WebVector<T>& val) {
+    v8::Local<v8::Array> result(
+        MATE_ARRAY_NEW(isolate, static_cast<int>(val.size())));
+    for (size_t i = 0; i < val.size(); ++i) {
+      result->Set(static_cast<int>(i), Converter<T>::ToV8(isolate, val[i]));
+    }
+    return result;
+  }
+};
+
 }  // namespace mate
 
 #endif  // ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_
index c728828..06c9621 100644 (file)
@@ -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<void(
+          const blink::WebVector<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())
+      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<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(
index 340a1ef..ab03a90 100644 (file)
@@ -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);
 });
 
index 44fc104..892ecf5 100644 (file)
@@ -307,7 +307,7 @@ var registerBrowserPluginElement = function() {
 
 // Registers <webview> 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');
index dde701d..376860b 100644 (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` Array
 
 Evaluates `code` in page.
 
index 4a0697a..3bc4f0e 100644 (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` 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
index d73a177..fef49de 100644 (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[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() {