Browse Source

refactor: use mojo for electron internal IPC (#17406)

* refactor: use mojo for electron internal IPC

* add sender_id, drop MessageSync

* remove usages of AtomFrameMsg_Message

* iwyu

* first draft of renderer->browser direction

* refactor to reuse a single ipc interface

* implement TakeHeapSnapshot through mojo

* the rest of the owl^WtakeHeapSnapshot mojofication

* remove no-op overrides in AtomRendererClient

* delete renderer-side ElectronApiServiceImpl when its pipe is destroyed

* looks like we don't need to overlay the renderer manifest after all

* don't try to send 2 replies to a sync rpc

* undo changes to manifests.cc

* unify sandboxed + unsandboxed ipc events

* lint

* register ElectronBrowser mojo service on devtools WebContents

* fix takeHeapSnapshopt failure paths

* {electron_api => atom}::mojom

* add send_to_all to ElectronRenderer::Message

* keep interface alive until callback is called

* review comments

* use GetContext from RendererClientBase

* robustify a test that uses window.open

* MessageSync posts a task to put sync messages in the same queue as async ones

* add v8::MicrotasksScope and node::CallbackScope

* iwyu

* use weakptr to api::WebContents instead of Unretained

* make MessageSync an asynchronous message & use non-associated interface

* iwyu + comments

* remove unused WeakPtrFactory

* inline OnRendererMessage[Sync]

* cleanups & comments

* use helper methods instead of inline lambdas

* remove unneeded async in test

* add mojo to manifests deps

* add gn check for //electron/manifests and mojo

* don't register renderer side service until preload has been run

* update gn check targets list

* move interface registration back to RenderFrameCreated
Jeremy Apthorp 6 years ago
parent
commit
53f6cbccbf

+ 2 - 0
.circleci/config.yml

@@ -237,6 +237,8 @@ step-gn-check: &step-gn-check
       cd src
       gn check out/Default //electron:electron_lib
       gn check out/Default //electron:electron_app
+      gn check out/Default //electron:manifests
+      gn check out/Default //electron/atom/common/api:mojo
 
 step-electron-build: &step-electron-build
   run:

+ 3 - 0
BUILD.gn

@@ -361,8 +361,10 @@ source_set("manifests") {
   include_dirs = [ "//electron" ]
 
   deps = [
+    "//electron/atom/common/api:mojo",
     "//printing/buildflags",
     "//services/proxy_resolver/public/cpp:manifest",
+    "//services/service_manager/public/cpp",
   ]
 
   if (enable_basic_printing) {
@@ -384,6 +386,7 @@ static_library("electron_lib") {
     ":atom_js2c",
     ":manifests",
     ":resources",
+    "atom/common/api:mojo",
     "buildflags",
     "chromium_src:chrome",
     "native_mate",

+ 2 - 0
appveyor.yml

@@ -58,6 +58,8 @@ build_script:
   - gn gen out/Default "--args=import(\"%BUILD_CONFIG_PATH%\") %GN_EXTRA_ARGS%"
   - gn check out/Default //electron:electron_lib
   - gn check out/Default //electron:electron_app
+  - gn check out/Default //electron:manifests
+  - gn check out/Default //electron/atom/common/api:mojo
   - ninja -C out/Default electron:electron_app
   - gn gen out/ffmpeg "--args=import(\"//electron/build/args/ffmpeg.gn\") %GN_EXTRA_ARGS%"
   - ninja -C out/ffmpeg electron:electron_ffmpeg_zip

+ 5 - 0
atom/app/manifests.cc

@@ -5,6 +5,7 @@
 #include "atom/app/manifests.h"
 
 #include "base/no_destructor.h"
+#include "electron/atom/common/api/api.mojom.h"
 #include "printing/buildflags/buildflags.h"
 #include "services/proxy_resolver/public/cpp/manifest.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
@@ -25,6 +26,10 @@ const service_manager::Manifest& GetElectronContentBrowserOverlayManifest() {
           .RequireCapability("proxy_resolver", "factory")
           .RequireCapability("chrome_printing", "converter")
           .RequireCapability("pdf_compositor", "compositor")
+          .ExposeInterfaceFilterCapability_Deprecated(
+              "navigation:frame", "renderer",
+              service_manager::Manifest::InterfaceList<
+                  atom::mojom::ElectronBrowser>())
           .Build()};
   return *manifest;
 }

+ 1 - 1
atom/browser/api/atom_api_event.cc

@@ -10,7 +10,7 @@ namespace {
 
 v8::Local<v8::Object> CreateWithSender(v8::Isolate* isolate,
                                        v8::Local<v8::Object> sender) {
-  return mate::internal::CreateJSEvent(isolate, sender, nullptr, nullptr);
+  return mate::internal::CreateJSEvent(isolate, sender, nullptr, base::nullopt);
 }
 
 void Initialize(v8::Local<v8::Object> exports,

+ 92 - 48
atom/browser/api/atom_api_web_contents.cc

@@ -78,10 +78,13 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/context_menu_params.h"
+#include "electron/atom/common/api/api.mojom.h"
+#include "mojo/public/cpp/system/platform_handle.h"
 #include "native_mate/converter.h"
 #include "native_mate/dictionary.h"
 #include "native_mate/object_template_builder.h"
 #include "net/url_request/url_request_context.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
 #include "third_party/blink/public/platform/web_input_event.h"
 #include "ui/display/screen.h"
@@ -260,14 +263,6 @@ struct WebContents::FrameDispatchHelper {
   void OnGetZoomLevel(IPC::Message* reply_msg) {
     api_web_contents->OnGetZoomLevel(rfh, reply_msg);
   }
-
-  void OnRendererMessageSync(bool internal,
-                             const std::string& channel,
-                             const base::ListValue& args,
-                             IPC::Message* message) {
-    api_web_contents->OnRendererMessageSync(rfh, internal, channel, args,
-                                            message);
-  }
 };
 
 WebContents::WebContents(v8::Isolate* isolate,
@@ -278,6 +273,8 @@ WebContents::WebContents(v8::Isolate* isolate,
   Init(isolate);
   AttachAsUserData(web_contents);
   InitZoomController(web_contents, mate::Dictionary::CreateEmpty(isolate));
+  registry_.AddInterface(base::BindRepeating(&WebContents::BindElectronBrowser,
+                                             base::Unretained(this)));
 }
 
 WebContents::WebContents(v8::Isolate* isolate,
@@ -425,6 +422,9 @@ void WebContents::InitWithSessionAndOptions(
   // Initialize zoom controller.
   InitZoomController(web_contents(), options);
 
+  registry_.AddInterface(base::BindRepeating(&WebContents::BindElectronBrowser,
+                                             base::Unretained(this)));
+
   web_contents()->SetUserAgentOverride(GetBrowserContext()->GetUserAgent(),
                                        false);
 
@@ -823,6 +823,13 @@ void WebContents::DidChangeThemeColor(SkColor theme_color) {
   }
 }
 
+void WebContents::OnInterfaceRequestFromFrame(
+    content::RenderFrameHost* render_frame_host,
+    const std::string& interface_name,
+    mojo::ScopedMessagePipeHandle* interface_pipe) {
+  registry_.TryBindInterface(interface_name, interface_pipe, render_frame_host);
+}
+
 void WebContents::DocumentLoadedInFrame(
     content::RenderFrameHost* render_frame_host) {
   if (!render_frame_host->GetParent())
@@ -886,6 +893,47 @@ bool WebContents::EmitNavigationEvent(
               frame_routing_id);
 }
 
+void WebContents::BindElectronBrowser(
+    mojom::ElectronBrowserRequest request,
+    content::RenderFrameHost* render_frame_host) {
+  auto id = bindings_.AddBinding(this, std::move(request), render_frame_host);
+  frame_to_bindings_map_[render_frame_host].push_back(id);
+}
+
+void WebContents::Message(bool internal,
+                          const std::string& channel,
+                          base::Value arguments) {
+  // webContents.emit('-ipc-message', new Event(), internal, channel,
+  // arguments);
+  EmitWithSender("-ipc-message", bindings_.dispatch_context(), base::nullopt,
+                 internal, channel, std::move(arguments));
+}
+
+void WebContents::MessageSync(bool internal,
+                              const std::string& channel,
+                              base::Value arguments,
+                              MessageSyncCallback callback) {
+  // webContents.emit('-ipc-message-sync', new Event(sender, message), internal,
+  // channel, arguments);
+  EmitWithSender("-ipc-message-sync", bindings_.dispatch_context(),
+                 std::move(callback), internal, channel, std::move(arguments));
+}
+
+void WebContents::RenderFrameDeleted(
+    content::RenderFrameHost* render_frame_host) {
+  // A RenderFrameHost can be destroyed before the related Mojo binding is
+  // closed, which can result in Mojo calls being sent for RenderFrameHosts
+  // that no longer exist. To prevent this from happening, when a
+  // RenderFrameHost goes away, we close all the bindings related to that
+  // frame.
+  auto it = frame_to_bindings_map_.find(render_frame_host);
+  if (it == frame_to_bindings_map_.end())
+    return;
+  for (auto id : it->second)
+    bindings_.RemoveBinding(id);
+  frame_to_bindings_map_.erase(it);
+}
+
 void WebContents::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
   EmitNavigationEvent("did-start-navigation", navigation_handle);
@@ -1049,9 +1097,6 @@ bool WebContents::OnMessageReceived(const IPC::Message& message,
   bool handled = true;
   FrameDispatchHelper helper = {this, frame_host};
   IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(WebContents, message, frame_host)
-    IPC_MESSAGE_HANDLER(AtomFrameHostMsg_Message, OnRendererMessage)
-    IPC_MESSAGE_FORWARD_DELAY_REPLY(AtomFrameHostMsg_Message_Sync, &helper,
-                                    FrameDispatchHelper::OnRendererMessageSync)
     IPC_MESSAGE_HANDLER(AtomFrameHostMsg_Message_To, OnRendererMessageTo)
     IPC_MESSAGE_HANDLER(AtomFrameHostMsg_Message_Host, OnRendererMessageHost)
     IPC_MESSAGE_FORWARD_DELAY_REPLY(
@@ -1659,14 +1704,13 @@ bool WebContents::SendIPCMessageWithSender(bool internal,
     target_hosts = web_contents()->GetAllFrames();
   }
 
-  bool handled = false;
   for (auto* frame_host : target_hosts) {
-    handled = frame_host->Send(
-                  new AtomFrameMsg_Message(frame_host->GetRoutingID(), internal,
-                                           false, channel, args, sender_id)) ||
-              handled;
+    mojom::ElectronRendererAssociatedPtr electron_ptr;
+    frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
+        mojo::MakeRequest(&electron_ptr));
+    electron_ptr->Message(internal, false, channel, args.Clone(), sender_id);
   }
-  return handled;
+  return true;
 }
 
 bool WebContents::SendIPCMessageToFrame(bool internal,
@@ -1682,8 +1726,13 @@ bool WebContents::SendIPCMessageToFrame(bool internal,
     return false;
   if (!(*iter)->IsRenderFrameLive())
     return false;
-  return (*iter)->Send(new AtomFrameMsg_Message(
-      frame_id, internal, send_to_all, channel, args, 0 /* sender_id */));
+
+  mojom::ElectronRendererAssociatedPtr electron_ptr;
+  (*iter)->GetRemoteAssociatedInterfaces()->GetInterface(
+      mojo::MakeRequest(&electron_ptr));
+  electron_ptr->Message(internal, send_to_all, channel, args.Clone(),
+                        0 /* sender_id */);
+  return true;
 }
 
 void WebContents::SendInputEvent(v8::Isolate* isolate,
@@ -2062,22 +2111,36 @@ void WebContents::GrantOriginAccess(const GURL& url) {
       url::Origin::Create(url));
 }
 
-bool WebContents::TakeHeapSnapshot(const base::FilePath& file_path,
-                                   const std::string& channel) {
+void WebContents::TakeHeapSnapshot(const base::FilePath& file_path,
+                                   base::Callback<void(bool)> callback) {
   base::ThreadRestrictions::ScopedAllowIO allow_io;
 
   base::File file(file_path,
                   base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
-  if (!file.IsValid())
-    return false;
+  if (!file.IsValid()) {
+    std::move(callback).Run(false);
+    return;
+  }
 
   auto* frame_host = web_contents()->GetMainFrame();
-  if (!frame_host)
-    return false;
+  if (!frame_host) {
+    std::move(callback).Run(false);
+    return;
+  }
 
-  return frame_host->Send(new AtomFrameMsg_TakeHeapSnapshot(
-      frame_host->GetRoutingID(),
-      IPC::TakePlatformFileForTransit(std::move(file)), channel));
+  // This dance with `base::Owned` is to ensure that the interface stays alive
+  // until the callback is called. Otherwise it would be closed at the end of
+  // this function.
+  auto electron_ptr = std::make_unique<mojom::ElectronRendererAssociatedPtr>();
+  frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
+      mojo::MakeRequest(electron_ptr.get()));
+  auto* raw_ptr = electron_ptr.get();
+  (*raw_ptr)->TakeHeapSnapshot(
+      mojo::WrapPlatformFile(file.TakePlatformFile()),
+      base::BindOnce([](mojom::ElectronRendererAssociatedPtr* ep,
+                        base::Callback<void(bool)> callback,
+                        bool success) { callback.Run(success); },
+                     base::Owned(std::move(electron_ptr)), callback));
 }
 
 // static
@@ -2195,25 +2258,6 @@ AtomBrowserContext* WebContents::GetBrowserContext() const {
   return static_cast<AtomBrowserContext*>(web_contents()->GetBrowserContext());
 }
 
-void WebContents::OnRendererMessage(content::RenderFrameHost* frame_host,
-                                    bool internal,
-                                    const std::string& channel,
-                                    const base::ListValue& args) {
-  // webContents.emit('-ipc-message', new Event(), internal, channel, args);
-  EmitWithSender("-ipc-message", frame_host, nullptr, internal, channel, args);
-}
-
-void WebContents::OnRendererMessageSync(content::RenderFrameHost* frame_host,
-                                        bool internal,
-                                        const std::string& channel,
-                                        const base::ListValue& args,
-                                        IPC::Message* message) {
-  // webContents.emit('-ipc-message-sync', new Event(sender, message), internal,
-  // channel, args);
-  EmitWithSender("-ipc-message-sync", frame_host, message, internal, channel,
-                 args);
-}
-
 void WebContents::OnRendererMessageTo(content::RenderFrameHost* frame_host,
                                       bool internal,
                                       bool send_to_all,
@@ -2233,7 +2277,7 @@ void WebContents::OnRendererMessageHost(content::RenderFrameHost* frame_host,
                                         const std::string& channel,
                                         const base::ListValue& args) {
   // webContents.emit('ipc-message-host', new Event(), channel, args);
-  EmitWithSender("ipc-message-host", frame_host, nullptr, channel, args);
+  EmitWithSender("ipc-message-host", frame_host, base::nullopt, channel, args);
 }
 
 // static

+ 32 - 16
atom/browser/api/atom_api_web_contents.h

@@ -5,6 +5,7 @@
 #ifndef ATOM_BROWSER_API_ATOM_API_WEB_CONTENTS_H_
 #define ATOM_BROWSER_API_ATOM_API_WEB_CONTENTS_H_
 
+#include <map>
 #include <memory>
 #include <string>
 #include <vector>
@@ -19,11 +20,14 @@
 #include "content/common/cursors/webcursor.h"
 #include "content/public/browser/keyboard_event_processing_result.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_binding_set.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/favicon_url.h"
+#include "electron/atom/common/api/api.mojom.h"
 #include "electron/buildflags/buildflags.h"
 #include "native_mate/handle.h"
 #include "printing/buildflags/buildflags.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
 #include "ui/gfx/image/image.h"
 
 #if BUILDFLAG(ENABLE_PRINTING)
@@ -73,7 +77,8 @@ class ExtendedWebContentsObserver : public base::CheckedObserver {
 // Wrapper around the content::WebContents.
 class WebContents : public mate::TrackableObject<WebContents>,
                     public CommonWebContentsDelegate,
-                    public content::WebContentsObserver {
+                    public content::WebContentsObserver,
+                    public mojom::ElectronBrowser {
  public:
   enum Type {
     BACKGROUND_PAGE,  // A DevTools extension background page.
@@ -289,8 +294,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
   // the specified URL.
   void GrantOriginAccess(const GURL& url);
 
-  bool TakeHeapSnapshot(const base::FilePath& file_path,
-                        const std::string& channel);
+  void TakeHeapSnapshot(const base::FilePath& file_path,
+                        base::Callback<void(bool)>);
 
   // Properties.
   int32_t ID() const;
@@ -411,6 +416,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
                              content::RenderViewHost* new_host) override;
   void RenderViewDeleted(content::RenderViewHost*) override;
   void RenderProcessGone(base::TerminationStatus status) override;
+  void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
   void DocumentLoadedInFrame(
       content::RenderFrameHost* render_frame_host) override;
   void DidFinishLoad(content::RenderFrameHost* render_frame_host,
@@ -445,6 +451,10 @@ class WebContents : public mate::TrackableObject<WebContents>,
       const MediaPlayerId& id,
       content::WebContentsObserver::MediaStoppedReason reason) override;
   void DidChangeThemeColor(SkColor theme_color) override;
+  void OnInterfaceRequestFromFrame(
+      content::RenderFrameHost* render_frame_host,
+      const std::string& interface_name,
+      mojo::ScopedMessagePipeHandle* interface_pipe) override;
 
   // InspectableWebContentsDelegate:
   void DevToolsReloadPage() override;
@@ -465,6 +475,11 @@ class WebContents : public mate::TrackableObject<WebContents>,
   struct FrameDispatchHelper;
   AtomBrowserContext* GetBrowserContext() const;
 
+  // Binds the given request for the ElectronBrowser API. When the
+  // RenderFrameHost is destroyed, all related bindings will be removed.
+  void BindElectronBrowser(mojom::ElectronBrowserRequest request,
+                           content::RenderFrameHost* render_frame_host);
+
   uint32_t GetNextRequestId() { return ++request_id_; }
 
 #if BUILDFLAG(ENABLE_OSR)
@@ -472,22 +487,18 @@ class WebContents : public mate::TrackableObject<WebContents>,
   OffScreenRenderWidgetHostView* GetOffScreenRenderWidgetHostView() const;
 #endif
 
+  // mojom::ElectronBrowser
+  void Message(bool internal,
+               const std::string& channel,
+               base::Value arguments) override;
+  void MessageSync(bool internal,
+                   const std::string& channel,
+                   base::Value arguments,
+                   MessageSyncCallback callback) override;
+
   // Called when we receive a CursorChange message from chromium.
   void OnCursorChange(const content::WebCursor& cursor);
 
-  // Called when received a message from renderer.
-  void OnRendererMessage(content::RenderFrameHost* frame_host,
-                         bool internal,
-                         const std::string& channel,
-                         const base::ListValue& args);
-
-  // Called when received a synchronous message from renderer.
-  void OnRendererMessageSync(content::RenderFrameHost* frame_host,
-                             bool internal,
-                             const std::string& channel,
-                             const base::ListValue& args,
-                             IPC::Message* message);
-
   // Called when received a message from renderer to be forwarded.
   void OnRendererMessageTo(content::RenderFrameHost* frame_host,
                            bool internal,
@@ -548,6 +559,11 @@ class WebContents : public mate::TrackableObject<WebContents>,
   // -1 means no speculative RVH has been committed yet.
   int currently_committed_process_id_ = -1;
 
+  service_manager::BinderRegistryWithArgs<content::RenderFrameHost*> registry_;
+  mojo::BindingSet<mojom::ElectronBrowser, content::RenderFrameHost*> bindings_;
+  std::map<content::RenderFrameHost*, std::vector<mojo::BindingId>>
+      frame_to_bindings_map_;
+
   DISALLOW_COPY_AND_ASSIGN(WebContents);
 };
 

+ 11 - 11
atom/browser/api/event.cc

@@ -4,6 +4,8 @@
 
 #include "atom/browser/api/event.h"
 
+#include <utility>
+
 #include "atom/common/api/api_messages.h"
 #include "atom/common/native_mate_converters/string16_converter.h"
 #include "atom/common/native_mate_converters/value_converter.h"
@@ -20,11 +22,11 @@ Event::Event(v8::Isolate* isolate) {
 Event::~Event() {}
 
 void Event::SetSenderAndMessage(content::RenderFrameHost* sender,
-                                IPC::Message* message) {
+                                base::Optional<MessageSyncCallback> callback) {
   DCHECK(!sender_);
-  DCHECK(!message_);
+  DCHECK(!callback_);
   sender_ = sender;
-  message_ = message;
+  callback_ = std::move(callback);
 
   Observe(content::WebContents::FromRenderFrameHost(sender));
 }
@@ -33,7 +35,7 @@ void Event::RenderFrameDeleted(content::RenderFrameHost* rfh) {
   if (sender_ != rfh)
     return;
   sender_ = nullptr;
-  message_ = nullptr;
+  callback_.reset();
 }
 
 void Event::RenderFrameHostChanged(content::RenderFrameHost* old_rfh,
@@ -46,7 +48,7 @@ void Event::FrameDeleted(content::RenderFrameHost* rfh) {
   if (sender_ != rfh)
     return;
   sender_ = nullptr;
-  message_ = nullptr;
+  callback_.reset();
 }
 
 void Event::PreventDefault(v8::Isolate* isolate) {
@@ -54,14 +56,12 @@ void Event::PreventDefault(v8::Isolate* isolate) {
 }
 
 bool Event::SendReply(const base::ListValue& result) {
-  if (message_ == nullptr || sender_ == nullptr)
+  if (!callback_ || sender_ == nullptr)
     return false;
 
-  AtomFrameHostMsg_Message_Sync::WriteReplyParams(message_, result);
-  bool success = sender_->Send(message_);
-  message_ = nullptr;
-  sender_ = nullptr;
-  return success;
+  std::move(*callback_).Run(result.Clone());
+  callback_.reset();
+  return true;
 }
 
 // static

+ 5 - 2
atom/browser/api/event.h

@@ -5,7 +5,9 @@
 #ifndef ATOM_BROWSER_API_EVENT_H_
 #define ATOM_BROWSER_API_EVENT_H_
 
+#include "base/optional.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "electron/atom/common/api/api.mojom.h"
 #include "native_mate/handle.h"
 #include "native_mate/wrappable.h"
 
@@ -17,6 +19,7 @@ namespace mate {
 
 class Event : public Wrappable<Event>, public content::WebContentsObserver {
  public:
+  using MessageSyncCallback = atom::mojom::ElectronBrowser::MessageSyncCallback;
   static Handle<Event> Create(v8::Isolate* isolate);
 
   static void BuildPrototype(v8::Isolate* isolate,
@@ -24,7 +27,7 @@ class Event : public Wrappable<Event>, public content::WebContentsObserver {
 
   // Pass the sender and message to be replied.
   void SetSenderAndMessage(content::RenderFrameHost* sender,
-                           IPC::Message* message);
+                           base::Optional<MessageSyncCallback> callback);
 
   // event.PreventDefault().
   void PreventDefault(v8::Isolate* isolate);
@@ -45,7 +48,7 @@ class Event : public Wrappable<Event>, public content::WebContentsObserver {
  private:
   // Replyer for the synchronous messages.
   content::RenderFrameHost* sender_ = nullptr;
-  IPC::Message* message_ = nullptr;
+  base::Optional<MessageSyncCallback> callback_;
 
   DISALLOW_COPY_AND_ASSIGN(Event);
 };

+ 10 - 6
atom/browser/api/event_emitter.cc

@@ -4,6 +4,8 @@
 
 #include "atom/browser/api/event_emitter.h"
 
+#include <utility>
+
 #include "atom/browser/api/event.h"
 #include "atom/common/node_includes.h"
 #include "content/public/browser/render_frame_host.h"
@@ -42,16 +44,18 @@ v8::Local<v8::Object> CreateEventObject(v8::Isolate* isolate) {
 
 namespace internal {
 
-v8::Local<v8::Object> CreateJSEvent(v8::Isolate* isolate,
-                                    v8::Local<v8::Object> object,
-                                    content::RenderFrameHost* sender,
-                                    IPC::Message* message) {
+v8::Local<v8::Object> CreateJSEvent(
+    v8::Isolate* isolate,
+    v8::Local<v8::Object> object,
+    content::RenderFrameHost* sender,
+    base::Optional<atom::mojom::ElectronBrowser::MessageSyncCallback>
+        callback) {
   v8::Local<v8::Object> event;
-  bool use_native_event = sender && message;
+  bool use_native_event = sender && callback;
 
   if (use_native_event) {
     mate::Handle<mate::Event> native_event = mate::Event::Create(isolate);
-    native_event->SetSenderAndMessage(sender, message);
+    native_event->SetSenderAndMessage(sender, std::move(callback));
     event = v8::Local<v8::Object>::Cast(native_event.ToV8());
   } else {
     event = CreateEventObject(isolate);

+ 17 - 15
atom/browser/api/event_emitter.h

@@ -5,28 +5,28 @@
 #ifndef ATOM_BROWSER_API_EVENT_EMITTER_H_
 #define ATOM_BROWSER_API_EVENT_EMITTER_H_
 
+#include <utility>
 #include <vector>
 
 #include "atom/common/api/event_emitter_caller.h"
+#include "base/optional.h"
 #include "content/public/browser/browser_thread.h"
+#include "electron/atom/common/api/api.mojom.h"
 #include "native_mate/wrappable.h"
 
 namespace content {
 class RenderFrameHost;
 }
 
-namespace IPC {
-class Message;
-}
-
 namespace mate {
 
 namespace internal {
 
-v8::Local<v8::Object> CreateJSEvent(v8::Isolate* isolate,
-                                    v8::Local<v8::Object> object,
-                                    content::RenderFrameHost* sender,
-                                    IPC::Message* message);
+v8::Local<v8::Object> CreateJSEvent(
+    v8::Isolate* isolate,
+    v8::Local<v8::Object> object,
+    content::RenderFrameHost* sender,
+    base::Optional<atom::mojom::ElectronBrowser::MessageSyncCallback> callback);
 v8::Local<v8::Object> CreateCustomEvent(v8::Isolate* isolate,
                                         v8::Local<v8::Object> object,
                                         v8::Local<v8::Object> event);
@@ -69,23 +69,25 @@ class EventEmitter : public Wrappable<T> {
   // this.emit(name, new Event(), args...);
   template <typename... Args>
   bool Emit(const base::StringPiece& name, const Args&... args) {
-    return EmitWithSender(name, nullptr, nullptr, args...);
+    return EmitWithSender(name, nullptr, base::nullopt, args...);
   }
 
   // this.emit(name, new Event(sender, message), args...);
   template <typename... Args>
-  bool EmitWithSender(const base::StringPiece& name,
-                      content::RenderFrameHost* sender,
-                      IPC::Message* message,
-                      const Args&... args) {
+  bool EmitWithSender(
+      const base::StringPiece& name,
+      content::RenderFrameHost* sender,
+      base::Optional<atom::mojom::ElectronBrowser::MessageSyncCallback>
+          callback,
+      const Args&... args) {
     v8::Locker locker(isolate());
     v8::HandleScope handle_scope(isolate());
     v8::Local<v8::Object> wrapper = GetWrapper();
     if (wrapper.IsEmpty()) {
       return false;
     }
-    v8::Local<v8::Object> event =
-        internal::CreateJSEvent(isolate(), wrapper, sender, message);
+    v8::Local<v8::Object> event = internal::CreateJSEvent(
+        isolate(), wrapper, sender, std::move(callback));
     return EmitWithEvent(name, event, args...);
   }
 

+ 11 - 0
atom/common/api/BUILD.gn

@@ -0,0 +1,11 @@
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojo") {
+  sources = [
+    "api.mojom",
+  ]
+
+  public_deps = [
+    "//mojo/public/mojom/base",
+  ]
+}

+ 29 - 0
atom/common/api/api.mojom

@@ -0,0 +1,29 @@
+module atom.mojom;
+
+import "mojo/public/mojom/base/values.mojom";
+
+interface ElectronRenderer {
+  Message(
+      bool internal,
+      bool send_to_all,
+      string channel,
+      mojo_base.mojom.ListValue arguments,
+      int32 sender_id);
+
+  TakeHeapSnapshot(handle file) => (bool success);
+};
+
+interface ElectronBrowser {
+  Message(
+      bool internal,
+      string channel,
+      mojo_base.mojom.ListValue arguments);
+
+  // NB. this is not marked [Sync] because mojo synchronous methods can be
+  // reordered with respect to asynchronous methods on the same channel.
+  // Instead, callers can manually block on the response to this method.
+  MessageSync(
+    bool internal,
+    string channel,
+    mojo_base.mojom.ListValue arguments) => (mojo_base.mojom.Value result);
+};

+ 0 - 22
atom/common/api/api_messages.h

@@ -25,17 +25,6 @@ IPC_STRUCT_TRAITS_BEGIN(atom::DraggableRegion)
   IPC_STRUCT_TRAITS_MEMBER(bounds)
 IPC_STRUCT_TRAITS_END()
 
-IPC_MESSAGE_ROUTED3(AtomFrameHostMsg_Message,
-                    bool /* internal */,
-                    std::string /* channel */,
-                    base::ListValue /* arguments */)
-
-IPC_SYNC_MESSAGE_ROUTED3_1(AtomFrameHostMsg_Message_Sync,
-                           bool /* internal */,
-                           std::string /* channel */,
-                           base::ListValue /* arguments */,
-                           base::ListValue /* result */)
-
 IPC_MESSAGE_ROUTED5(AtomFrameHostMsg_Message_To,
                     bool /* internal */,
                     bool /* send_to_all */,
@@ -47,13 +36,6 @@ IPC_MESSAGE_ROUTED2(AtomFrameHostMsg_Message_Host,
                     std::string /* channel */,
                     base::ListValue /* arguments */)
 
-IPC_MESSAGE_ROUTED5(AtomFrameMsg_Message,
-                    bool /* internal */,
-                    bool /* send_to_all */,
-                    std::string /* channel */,
-                    base::ListValue /* arguments */,
-                    int32_t /* sender_id */)
-
 IPC_MESSAGE_ROUTED0(AtomViewMsg_Offscreen)
 
 IPC_MESSAGE_ROUTED3(AtomAutofillFrameHostMsg_ShowPopup,
@@ -85,7 +67,3 @@ IPC_SYNC_MESSAGE_ROUTED0_1(AtomFrameHostMsg_GetZoomLevel, double /* result */)
 IPC_MESSAGE_ROUTED2(AtomFrameHostMsg_PDFSaveURLAs,
                     GURL /* url */,
                     content::Referrer /* referrer */)
-
-IPC_MESSAGE_ROUTED2(AtomFrameMsg_TakeHeapSnapshot,
-                    IPC::PlatformFileForTransit /* file_handle */,
-                    std::string /* channel */)

+ 7 - 3
atom/common/api/remote_callback_freer.cc

@@ -4,11 +4,12 @@
 
 #include "atom/common/api/remote_callback_freer.h"
 
-#include "atom/common/api/api_messages.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
+#include "electron/atom/common/api/api.mojom.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 
 namespace atom {
 
@@ -41,8 +42,11 @@ void RemoteCallbackFreer::RunDestructor() {
   args.AppendInteger(object_id_);
   auto* frame_host = web_contents()->GetMainFrame();
   if (frame_host) {
-    frame_host->Send(new AtomFrameMsg_Message(frame_host->GetRoutingID(), true,
-                                              false, channel, args, sender_id));
+    mojom::ElectronRendererAssociatedPtr electron_ptr;
+    frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
+        mojo::MakeRequest(&electron_ptr));
+    electron_ptr->Message(true /* internal */, false /* send_to_all */, channel,
+                          args.Clone(), sender_id);
   }
 
   Observe(nullptr);

+ 7 - 2
atom/common/api/remote_object_freer.cc

@@ -8,6 +8,8 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "content/public/renderer/render_frame.h"
+#include "electron/atom/common/api/api.mojom.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 
 using blink::WebLocalFrame;
@@ -60,8 +62,11 @@ void RemoteObjectFreer::RunDestructor() {
   base::ListValue args;
   args.AppendString(context_id_);
   args.AppendInteger(object_id_);
-  render_frame->Send(new AtomFrameHostMsg_Message(render_frame->GetRoutingID(),
-                                                  true, channel, args));
+
+  mojom::ElectronBrowserAssociatedPtr electron_ptr;
+  render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
+      mojo::MakeRequest(&electron_ptr));
+  electron_ptr->Message(true, channel, args.Clone());
 }
 
 }  // namespace atom

+ 91 - 35
atom/renderer/api/atom_api_renderer_ipc.cc

@@ -8,10 +8,16 @@
 #include "atom/common/native_mate_converters/value_converter.h"
 #include "atom/common/node_bindings.h"
 #include "atom/common/node_includes.h"
+#include "base/task/post_task.h"
 #include "base/values.h"
 #include "content/public/renderer/render_frame.h"
+#include "electron/atom/common/api/api.mojom.h"
 #include "native_mate/arguments.h"
 #include "native_mate/dictionary.h"
+#include "native_mate/handle.h"
+#include "native_mate/object_template_builder.h"
+#include "native_mate/wrappable.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 
 using blink::WebLocalFrame;
@@ -27,40 +33,91 @@ RenderFrame* GetCurrentRenderFrame() {
   return RenderFrame::FromWebFrame(frame);
 }
 
-void Send(mate::Arguments* args,
-          bool internal,
-          const std::string& channel,
-          const base::ListValue& arguments) {
-  RenderFrame* render_frame = GetCurrentRenderFrame();
-  if (render_frame == nullptr)
-    return;
-
-  bool success = render_frame->Send(new AtomFrameHostMsg_Message(
-      render_frame->GetRoutingID(), internal, channel, arguments));
-
-  if (!success)
-    args->ThrowError("Unable to send AtomFrameHostMsg_Message");
-}
-
-base::ListValue SendSync(mate::Arguments* args,
-                         bool internal,
-                         const std::string& channel,
-                         const base::ListValue& arguments) {
-  base::ListValue result;
-
-  RenderFrame* render_frame = GetCurrentRenderFrame();
-  if (render_frame == nullptr)
+class IPCRenderer : public mate::Wrappable<IPCRenderer> {
+ public:
+  explicit IPCRenderer(v8::Isolate* isolate) {
+    Init(isolate);
+    RenderFrame* render_frame = GetCurrentRenderFrame();
+    DCHECK(render_frame);
+    render_frame->GetRemoteInterfaces()->GetInterface(
+        mojo::MakeRequest(&electron_browser_ptr_));
+  }
+  static void BuildPrototype(v8::Isolate* isolate,
+                             v8::Local<v8::FunctionTemplate> prototype) {
+    prototype->SetClassName(mate::StringToV8(isolate, "IPCRenderer"));
+    mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
+        .SetMethod("send", &IPCRenderer::Send)
+        .SetMethod("sendSync", &IPCRenderer::SendSync);
+  }
+  static mate::Handle<IPCRenderer> Create(v8::Isolate* isolate) {
+    return mate::CreateHandle(isolate, new IPCRenderer(isolate));
+  }
+
+  void Send(mate::Arguments* args,
+            bool internal,
+            const std::string& channel,
+            const base::ListValue& arguments) {
+    electron_browser_ptr_->Message(internal, channel, arguments.Clone());
+  }
+
+  base::Value SendSync(mate::Arguments* args,
+                       bool internal,
+                       const std::string& channel,
+                       const base::ListValue& arguments) {
+    base::Value result;
+
+    // A task is posted to a separate thread to execute the request so that
+    // this thread may block on a waitable event. It is safe to pass raw
+    // pointers to |result| and |event| as this stack frame will survive until
+    // the request is complete.
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+        base::CreateSingleThreadTaskRunnerWithTraits({});
+
+    base::WaitableEvent response_received_event;
+
+    // We unbind the interface from this thread to pass it over to the worker
+    // thread temporarily. This requires that no callbacks be pending for this
+    // interface.
+    auto interface_info = electron_browser_ptr_.PassInterface();
+    task_runner->PostTask(
+        FROM_HERE, base::BindOnce(&IPCRenderer::SendMessageSyncOnWorkerThread,
+                                  base::Unretained(&interface_info),
+                                  base::Unretained(&response_received_event),
+                                  base::Unretained(&result), internal, channel,
+                                  base::Unretained(&arguments)));
+    response_received_event.Wait();
+    electron_browser_ptr_.Bind(std::move(interface_info));
     return result;
-
-  IPC::SyncMessage* message = new AtomFrameHostMsg_Message_Sync(
-      render_frame->GetRoutingID(), internal, channel, arguments, &result);
-  bool success = render_frame->Send(message);
-
-  if (!success)
-    args->ThrowError("Unable to send AtomFrameHostMsg_Message_Sync");
-
-  return result;
-}
+  }
+
+ private:
+  static void SendMessageSyncOnWorkerThread(
+      atom::mojom::ElectronBrowserPtrInfo* interface_info,
+      base::WaitableEvent* event,
+      base::Value* result,
+      bool internal,
+      const std::string& channel,
+      const base::ListValue* arguments) {
+    atom::mojom::ElectronBrowserPtr browser_ptr(std::move(*interface_info));
+    browser_ptr->MessageSync(
+        internal, channel, arguments->Clone(),
+        base::BindOnce(&IPCRenderer::ReturnSyncResponseToMainThread,
+                       std::move(browser_ptr), base::Unretained(interface_info),
+                       base::Unretained(event), base::Unretained(result)));
+  }
+  static void ReturnSyncResponseToMainThread(
+      atom::mojom::ElectronBrowserPtr ptr,
+      atom::mojom::ElectronBrowserPtrInfo* interface_info,
+      base::WaitableEvent* event,
+      base::Value* result,
+      base::Value response) {
+    *result = std::move(response);
+    *interface_info = ptr.PassInterface();
+    event->Signal();
+  }
+
+  atom::mojom::ElectronBrowserPtr electron_browser_ptr_;
+};
 
 void SendTo(mate::Arguments* args,
             bool internal,
@@ -99,8 +156,7 @@ void Initialize(v8::Local<v8::Object> exports,
                 v8::Local<v8::Context> context,
                 void* priv) {
   mate::Dictionary dict(context->GetIsolate(), exports);
-  dict.SetMethod("send", &Send);
-  dict.SetMethod("sendSync", &SendSync);
+  dict.Set("ipc", IPCRenderer::Create(context->GetIsolate()));
   dict.SetMethod("sendTo", &SendTo);
   dict.SetMethod("sendToHost", &SendToHost);
 }

+ 3 - 121
atom/renderer/atom_render_frame_observer.cc

@@ -9,20 +9,21 @@
 
 #include "atom/common/api/api_messages.h"
 #include "atom/common/api/event_emitter_caller.h"
-#include "atom/common/heap_snapshot.h"
 #include "atom/common/native_mate_converters/value_converter.h"
 #include "atom/common/node_includes.h"
 #include "atom/common/options_switches.h"
 #include "base/command_line.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/threading/thread_restrictions.h"
 #include "base/trace_event/trace_event.h"
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_view.h"
+#include "electron/atom/common/api/api.mojom.h"
 #include "ipc/ipc_message_macros.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
 #include "native_mate/dictionary.h"
 #include "net/base/net_module.h"
 #include "net/grit/net_resources.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/platform/web_isolated_world_info.h"
 #include "third_party/blink/public/web/blink.h"
 #include "third_party/blink/public/web/web_document.h"
@@ -36,32 +37,6 @@ namespace atom {
 
 namespace {
 
-bool GetIPCObject(v8::Isolate* isolate,
-                  v8::Local<v8::Context> context,
-                  bool internal,
-                  v8::Local<v8::Object>* ipc) {
-  v8::Local<v8::String> key =
-      mate::StringToV8(isolate, internal ? "ipc-internal" : "ipc");
-  v8::Local<v8::Private> privateKey = v8::Private::ForApi(isolate, key);
-  v8::Local<v8::Object> global_object = context->Global();
-  v8::Local<v8::Value> value;
-  if (!global_object->GetPrivate(context, privateKey).ToLocal(&value))
-    return false;
-  if (value.IsEmpty() || !value->IsObject())
-    return false;
-  *ipc = value->ToObject(isolate);
-  return true;
-}
-
-std::vector<v8::Local<v8::Value>> ListValueToVector(
-    v8::Isolate* isolate,
-    const base::ListValue& list) {
-  v8::Local<v8::Value> array = mate::ConvertToV8(isolate, list);
-  std::vector<v8::Local<v8::Value>> result;
-  mate::ConvertFromV8(isolate, array, &result);
-  return result;
-}
-
 base::StringPiece NetResourceProvider(int key) {
   if (key == IDR_DIR_HEADER_HTML) {
     base::StringPiece html_data =
@@ -88,10 +63,6 @@ void AtomRenderFrameObserver::DidClearWindowObject() {
   renderer_client_->DidClearWindowObject(render_frame_);
 }
 
-void AtomRenderFrameObserver::DidCreateDocumentElement() {
-  document_created_ = true;
-}
-
 void AtomRenderFrameObserver::DidCreateScriptContext(
     v8::Handle<v8::Context> context,
     int world_id) {
@@ -182,93 +153,4 @@ bool AtomRenderFrameObserver::ShouldNotifyClient(int world_id) {
     return IsMainWorld(world_id);
 }
 
-bool AtomRenderFrameObserver::OnMessageReceived(const IPC::Message& message) {
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(AtomRenderFrameObserver, message)
-    IPC_MESSAGE_HANDLER(AtomFrameMsg_Message, OnBrowserMessage)
-    IPC_MESSAGE_HANDLER(AtomFrameMsg_TakeHeapSnapshot, OnTakeHeapSnapshot)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-
-  return handled;
-}
-
-void AtomRenderFrameObserver::OnBrowserMessage(bool internal,
-                                               bool send_to_all,
-                                               const std::string& channel,
-                                               const base::ListValue& args,
-                                               int32_t sender_id) {
-  // Don't handle browser messages before document element is created.
-  // When we receive a message from the browser, we try to transfer it
-  // to a web page, and when we do that Blink creates an empty
-  // document element if it hasn't been created yet, and it makes our init
-  // script to run while `window.location` is still "about:blank".
-  if (!document_created_)
-    return;
-
-  blink::WebLocalFrame* frame = render_frame_->GetWebFrame();
-  if (!frame)
-    return;
-
-  EmitIPCEvent(frame, internal, channel, args, sender_id);
-
-  // Also send the message to all sub-frames.
-  // TODO(MarshallOfSound): Completely move this logic to the main process
-  if (send_to_all) {
-    for (blink::WebFrame* child = frame->FirstChild(); child;
-         child = child->NextSibling())
-      if (child->IsWebLocalFrame()) {
-        EmitIPCEvent(child->ToWebLocalFrame(), internal, channel, args,
-                     sender_id);
-      }
-  }
-}
-
-void AtomRenderFrameObserver::OnTakeHeapSnapshot(
-    IPC::PlatformFileForTransit file_handle,
-    const std::string& channel) {
-  base::ThreadRestrictions::ScopedAllowIO allow_io;
-
-  auto file = IPC::PlatformFileForTransitToFile(file_handle);
-  bool success = TakeHeapSnapshot(blink::MainThreadIsolate(), &file);
-
-  base::ListValue args;
-  args.AppendBoolean(success);
-
-  render_frame_->Send(new AtomFrameHostMsg_Message(
-      render_frame_->GetRoutingID(), true, channel, args));
-}
-
-void AtomRenderFrameObserver::EmitIPCEvent(blink::WebLocalFrame* frame,
-                                           bool internal,
-                                           const std::string& channel,
-                                           const base::ListValue& args,
-                                           int32_t sender_id) {
-  if (!frame)
-    return;
-
-  v8::Isolate* isolate = blink::MainThreadIsolate();
-  v8::HandleScope handle_scope(isolate);
-
-  v8::Local<v8::Context> context = renderer_client_->GetContext(frame, isolate);
-  v8::Context::Scope context_scope(context);
-
-  // Only emit IPC event for context with node integration.
-  node::Environment* env = node::Environment::GetCurrent(context);
-  if (!env)
-    return;
-
-  v8::Local<v8::Object> ipc;
-  if (GetIPCObject(isolate, context, internal, &ipc)) {
-    TRACE_EVENT0("devtools.timeline", "FunctionCall");
-    auto args_vector = ListValueToVector(isolate, args);
-    // Insert the Event object, event.sender is ipc.
-    mate::Dictionary event = mate::Dictionary::CreateEmpty(isolate);
-    event.Set("sender", ipc);
-    event.Set("senderId", sender_id);
-    args_vector.insert(args_vector.begin(), event.GetHandle());
-    mate::EmitEvent(isolate, ipc, channel, args_vector);
-  }
-}
-
 }  // namespace atom

+ 0 - 15
atom/renderer/atom_render_frame_observer.h

@@ -51,32 +51,17 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
   void WillReleaseScriptContext(v8::Local<v8::Context> context,
                                 int world_id) override;
   void OnDestruct() override;
-  bool OnMessageReceived(const IPC::Message& message) override;
-  void DidCreateDocumentElement() override;
-
- protected:
-  virtual void EmitIPCEvent(blink::WebLocalFrame* frame,
-                            bool internal,
-                            const std::string& channel,
-                            const base::ListValue& args,
-                            int32_t sender_id);
 
  private:
   bool ShouldNotifyClient(int world_id);
   void CreateIsolatedWorldContext();
   bool IsMainWorld(int world_id);
   bool IsIsolatedWorld(int world_id);
-  void OnBrowserMessage(bool internal,
-                        bool send_to_all,
-                        const std::string& channel,
-                        const base::ListValue& args,
-                        int32_t sender_id);
   void OnTakeHeapSnapshot(IPC::PlatformFileForTransit file_handle,
                           const std::string& channel);
 
   content::RenderFrame* render_frame_;
   RendererClientBase* renderer_client_;
-  bool document_created_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver);
 };

+ 0 - 8
atom/renderer/atom_renderer_client.cc

@@ -41,20 +41,12 @@ AtomRendererClient::~AtomRendererClient() {
   asar::ClearArchives();
 }
 
-void AtomRendererClient::RenderThreadStarted() {
-  RendererClientBase::RenderThreadStarted();
-}
-
 void AtomRendererClient::RenderFrameCreated(
     content::RenderFrame* render_frame) {
   new AtomRenderFrameObserver(render_frame, this);
   RendererClientBase::RenderFrameCreated(render_frame);
 }
 
-void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) {
-  RendererClientBase::RenderViewCreated(render_view);
-}
-
 void AtomRendererClient::RunScriptsAtDocumentStart(
     content::RenderFrame* render_frame) {
   // Inform the document start pharse.

+ 0 - 2
atom/renderer/atom_renderer_client.h

@@ -39,9 +39,7 @@ class AtomRendererClient : public RendererClientBase {
 
  private:
   // content::ContentRendererClient:
-  void RenderThreadStarted() override;
   void RenderFrameCreated(content::RenderFrame*) override;
-  void RenderViewCreated(content::RenderView*) override;
   void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
   void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override;
   bool ShouldFork(blink::WebLocalFrame* frame,

+ 25 - 64
atom/renderer/atom_sandboxed_renderer_client.cc

@@ -30,7 +30,7 @@ namespace atom {
 
 namespace {
 
-const char kIpcKey[] = "ipcNative";
+const char kLifecycleKey[] = "lifecycle";
 const char kModuleCacheKey[] = "native-module-cache";
 
 bool IsDevTools(content::RenderFrame* render_frame) {
@@ -91,41 +91,26 @@ v8::Local<v8::Value> CreatePreloadScript(v8::Isolate* isolate,
                                        preloadSrc);
 }
 
-class AtomSandboxedRenderFrameObserver : public AtomRenderFrameObserver {
- public:
-  AtomSandboxedRenderFrameObserver(content::RenderFrame* render_frame,
-                                   AtomSandboxedRendererClient* renderer_client)
-      : AtomRenderFrameObserver(render_frame, renderer_client),
-        renderer_client_(renderer_client) {}
-
- protected:
-  void EmitIPCEvent(blink::WebLocalFrame* frame,
-                    bool internal,
-                    const std::string& channel,
-                    const base::ListValue& args,
-                    int32_t sender_id) override {
-    if (!frame)
-      return;
-
-    auto* isolate = blink::MainThreadIsolate();
-    v8::HandleScope handle_scope(isolate);
-
-    auto context = renderer_client_->GetContext(frame, isolate);
-    v8::Context::Scope context_scope(context);
-
-    v8::Local<v8::Value> argv[] = {mate::ConvertToV8(isolate, internal),
-                                   mate::ConvertToV8(isolate, channel),
-                                   mate::ConvertToV8(isolate, args),
-                                   mate::ConvertToV8(isolate, sender_id)};
-    renderer_client_->InvokeIpcCallback(
-        context, "onMessage",
-        std::vector<v8::Local<v8::Value>>(argv, argv + node::arraysize(argv)));
-  }
-
- private:
-  AtomSandboxedRendererClient* renderer_client_;
-  DISALLOW_COPY_AND_ASSIGN(AtomSandboxedRenderFrameObserver);
-};
+void InvokeHiddenCallback(v8::Handle<v8::Context> context,
+                          const std::string& hidden_key,
+                          const std::string& callback_name) {
+  auto* isolate = context->GetIsolate();
+  auto binding_key = mate::ConvertToV8(isolate, hidden_key)->ToString(isolate);
+  auto private_binding_key = v8::Private::ForApi(isolate, binding_key);
+  auto global_object = context->Global();
+  v8::Local<v8::Value> value;
+  if (!global_object->GetPrivate(context, private_binding_key).ToLocal(&value))
+    return;
+  if (value.IsEmpty() || !value->IsObject())
+    return;
+  auto binding = value->ToObject(isolate);
+  auto callback_key =
+      mate::ConvertToV8(isolate, callback_name)->ToString(isolate);
+  auto callback_value = binding->Get(callback_key);
+  DCHECK(callback_value->IsFunction());  // set by sandboxed_renderer/init.js
+  auto callback = v8::Handle<v8::Function>::Cast(callback_value);
+  ignore_result(callback->Call(context, binding, 0, nullptr));
+}
 
 }  // namespace
 
@@ -166,7 +151,7 @@ void AtomSandboxedRendererClient::InitializeBindings(
 
 void AtomSandboxedRendererClient::RenderFrameCreated(
     content::RenderFrame* render_frame) {
-  new AtomSandboxedRenderFrameObserver(render_frame, this);
+  new AtomRenderFrameObserver(render_frame, this);
   RendererClientBase::RenderFrameCreated(render_frame);
 }
 
@@ -187,8 +172,7 @@ void AtomSandboxedRendererClient::RunScriptsAtDocumentStart(
       GetContext(render_frame->GetWebFrame(), isolate);
   v8::Context::Scope context_scope(context);
 
-  InvokeIpcCallback(context, "onDocumentStart",
-                    std::vector<v8::Local<v8::Value>>());
+  InvokeHiddenCallback(context, kLifecycleKey, "onDocumentStart");
 }
 
 void AtomSandboxedRendererClient::RunScriptsAtDocumentEnd(
@@ -203,8 +187,7 @@ void AtomSandboxedRendererClient::RunScriptsAtDocumentEnd(
       GetContext(render_frame->GetWebFrame(), isolate);
   v8::Context::Scope context_scope(context);
 
-  InvokeIpcCallback(context, "onDocumentEnd",
-                    std::vector<v8::Local<v8::Value>>());
+  InvokeHiddenCallback(context, kLifecycleKey, "onDocumentEnd");
 }
 
 void AtomSandboxedRendererClient::DidCreateScriptContext(
@@ -303,29 +286,7 @@ void AtomSandboxedRendererClient::WillReleaseScriptContext(
   auto* isolate = context->GetIsolate();
   v8::HandleScope handle_scope(isolate);
   v8::Context::Scope context_scope(context);
-  InvokeIpcCallback(context, "onExit", std::vector<v8::Local<v8::Value>>());
-}
-
-void AtomSandboxedRendererClient::InvokeIpcCallback(
-    v8::Handle<v8::Context> context,
-    const std::string& callback_name,
-    std::vector<v8::Handle<v8::Value>> args) {
-  auto* isolate = context->GetIsolate();
-  auto binding_key = mate::ConvertToV8(isolate, kIpcKey)->ToString(isolate);
-  auto private_binding_key = v8::Private::ForApi(isolate, binding_key);
-  auto global_object = context->Global();
-  v8::Local<v8::Value> value;
-  if (!global_object->GetPrivate(context, private_binding_key).ToLocal(&value))
-    return;
-  if (value.IsEmpty() || !value->IsObject())
-    return;
-  auto binding = value->ToObject(isolate);
-  auto callback_key =
-      mate::ConvertToV8(isolate, callback_name)->ToString(isolate);
-  auto callback_value = binding->Get(callback_key);
-  DCHECK(callback_value->IsFunction());  // set by sandboxed_renderer/init.js
-  auto callback = v8::Handle<v8::Function>::Cast(callback_value);
-  ignore_result(callback->Call(context, binding, args.size(), args.data()));
+  InvokeHiddenCallback(context, kLifecycleKey, "onExit");
 }
 
 }  // namespace atom

+ 0 - 3
atom/renderer/atom_sandboxed_renderer_client.h

@@ -22,9 +22,6 @@ class AtomSandboxedRendererClient : public RendererClientBase {
   void InitializeBindings(v8::Local<v8::Object> binding,
                           v8::Local<v8::Context> context,
                           bool is_main_frame);
-  void InvokeIpcCallback(v8::Handle<v8::Context> context,
-                         const std::string& callback_name,
-                         std::vector<v8::Handle<v8::Value>> args);
   // atom::RendererClientBase:
   void DidCreateScriptContext(v8::Handle<v8::Context> context,
                               content::RenderFrame* render_frame) override;

+ 167 - 0
atom/renderer/electron_api_service_impl.cc

@@ -0,0 +1,167 @@
+// Copyright (c) 2019 Slack Technologies, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "electron/atom/renderer/electron_api_service_impl.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "atom/common/heap_snapshot.h"
+#include "atom/common/native_mate_converters/value_converter.h"
+#include "base/macros.h"
+#include "base/threading/thread_restrictions.h"
+#include "electron/atom/common/api/event_emitter_caller.h"
+#include "electron/atom/common/node_includes.h"
+#include "electron/atom/common/options_switches.h"
+#include "electron/atom/renderer/atom_render_frame_observer.h"
+#include "electron/atom/renderer/renderer_client_base.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+#include "native_mate/dictionary.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace atom {
+
+namespace {
+
+const char kIpcKey[] = "ipcNative";
+
+// Gets the private object under kIpcKey
+v8::Local<v8::Object> GetIpcObject(v8::Local<v8::Context> context) {
+  auto* isolate = context->GetIsolate();
+  auto binding_key = mate::ConvertToV8(isolate, kIpcKey)->ToString(isolate);
+  auto private_binding_key = v8::Private::ForApi(isolate, binding_key);
+  auto global_object = context->Global();
+  auto value =
+      global_object->GetPrivate(context, private_binding_key).ToLocalChecked();
+  DCHECK(!value.IsEmpty() && value->IsObject());
+  return value->ToObject(isolate);
+}
+
+void InvokeIpcCallback(v8::Local<v8::Context> context,
+                       const std::string& callback_name,
+                       std::vector<v8::Local<v8::Value>> args) {
+  TRACE_EVENT0("devtools.timeline", "FunctionCall");
+  auto* isolate = context->GetIsolate();
+
+  auto ipcNative = GetIpcObject(context);
+
+  // Only set up the node::CallbackScope if there's a node environment.
+  // Sandboxed renderers don't have a node environment.
+  node::Environment* env = node::Environment::GetCurrent(context);
+  std::unique_ptr<node::CallbackScope> callback_scope;
+  if (env) {
+    callback_scope.reset(new node::CallbackScope(isolate, ipcNative, {0, 0}));
+  }
+
+  auto callback_key =
+      mate::ConvertToV8(isolate, callback_name)->ToString(isolate);
+  auto callback_value = ipcNative->Get(callback_key);
+  DCHECK(callback_value->IsFunction());  // set by init.ts
+  auto callback = v8::Local<v8::Function>::Cast(callback_value);
+  ignore_result(callback->Call(context, ipcNative, args.size(), args.data()));
+}
+
+void EmitIPCEvent(v8::Local<v8::Context> context,
+                  bool internal,
+                  const std::string& channel,
+                  const std::vector<base::Value>& args,
+                  int32_t sender_id) {
+  auto* isolate = context->GetIsolate();
+
+  v8::HandleScope handle_scope(isolate);
+  v8::Context::Scope context_scope(context);
+  v8::MicrotasksScope script_scope(isolate,
+                                   v8::MicrotasksScope::kRunMicrotasks);
+
+  std::vector<v8::Local<v8::Value>> argv = {
+      mate::ConvertToV8(isolate, internal), mate::ConvertToV8(isolate, channel),
+      mate::ConvertToV8(isolate, args), mate::ConvertToV8(isolate, sender_id)};
+
+  InvokeIpcCallback(context, "onMessage", argv);
+}
+
+}  // namespace
+
+ElectronApiServiceImpl::~ElectronApiServiceImpl() = default;
+
+ElectronApiServiceImpl::ElectronApiServiceImpl(
+    content::RenderFrame* render_frame,
+    RendererClientBase* renderer_client,
+    mojom::ElectronRendererAssociatedRequest request)
+    : content::RenderFrameObserver(render_frame),
+      binding_(this),
+      render_frame_(render_frame),
+      renderer_client_(renderer_client) {
+  binding_.Bind(std::move(request));
+  binding_.set_connection_error_handler(base::BindOnce(
+      &ElectronApiServiceImpl::OnDestruct, base::Unretained(this)));
+}
+
+// static
+void ElectronApiServiceImpl::CreateMojoService(
+    content::RenderFrame* render_frame,
+    RendererClientBase* renderer_client,
+    mojom::ElectronRendererAssociatedRequest request) {
+  DCHECK(render_frame);
+
+  // Owns itself. Will be deleted when the render frame is destroyed.
+  new ElectronApiServiceImpl(render_frame, renderer_client, std::move(request));
+}
+
+void ElectronApiServiceImpl::OnDestruct() {
+  delete this;
+}
+
+void ElectronApiServiceImpl::Message(bool internal,
+                                     bool send_to_all,
+                                     const std::string& channel,
+                                     base::Value arguments,
+                                     int32_t sender_id) {
+  blink::WebLocalFrame* frame = render_frame_->GetWebFrame();
+  if (!frame)
+    return;
+
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::HandleScope handle_scope(isolate);
+
+  v8::Local<v8::Context> context = renderer_client_->GetContext(frame, isolate);
+
+  EmitIPCEvent(context, internal, channel, arguments.GetList(), sender_id);
+
+  // Also send the message to all sub-frames.
+  // TODO(MarshallOfSound): Completely move this logic to the main process
+  if (send_to_all) {
+    for (blink::WebFrame* child = frame->FirstChild(); child;
+         child = child->NextSibling())
+      if (child->IsWebLocalFrame()) {
+        v8::Local<v8::Context> child_context =
+            renderer_client_->GetContext(child->ToWebLocalFrame(), isolate);
+        EmitIPCEvent(child_context, internal, channel, arguments.GetList(),
+                     sender_id);
+      }
+  }
+}
+
+void ElectronApiServiceImpl::TakeHeapSnapshot(
+    mojo::ScopedHandle file,
+    TakeHeapSnapshotCallback callback) {
+  base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+  base::PlatformFile platform_file;
+  if (mojo::UnwrapPlatformFile(std::move(file), &platform_file) !=
+      MOJO_RESULT_OK) {
+    LOG(ERROR) << "Unable to get the file handle from mojo.";
+    std::move(callback).Run(false);
+    return;
+  }
+  base::File base_file(platform_file);
+
+  bool success = atom::TakeHeapSnapshot(blink::MainThreadIsolate(), &base_file);
+
+  std::move(callback).Run(success);
+}
+
+}  // namespace atom

+ 55 - 0
atom/renderer/electron_api_service_impl.h

@@ -0,0 +1,55 @@
+// Copyright (c) 2019 Slack Technologies, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef ATOM_RENDERER_ELECTRON_API_SERVICE_IMPL_H_
+#define ATOM_RENDERER_ELECTRON_API_SERVICE_IMPL_H_
+
+#include <string>
+
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "electron/atom/common/api/api.mojom.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+
+namespace atom {
+
+class RendererClientBase;
+
+class ElectronApiServiceImpl : public mojom::ElectronRenderer,
+                               public content::RenderFrameObserver {
+ public:
+  static void CreateMojoService(
+      content::RenderFrame* render_frame,
+      RendererClientBase* renderer_client,
+      mojom::ElectronRendererAssociatedRequest request);
+
+  void Message(bool internal,
+               bool send_to_all,
+               const std::string& channel,
+               base::Value arguments,
+               int32_t sender_id) override;
+
+  void TakeHeapSnapshot(mojo::ScopedHandle file,
+                        TakeHeapSnapshotCallback callback) override;
+
+ private:
+  ~ElectronApiServiceImpl() override;
+  ElectronApiServiceImpl(content::RenderFrame* render_frame,
+                         RendererClientBase* renderer_client,
+                         mojom::ElectronRendererAssociatedRequest request);
+
+  // RenderFrameObserver implementation.
+  void OnDestruct() override;
+
+  mojo::AssociatedBinding<mojom::ElectronRenderer> binding_;
+
+  content::RenderFrame* render_frame_;
+  RendererClientBase* renderer_client_;
+
+  DISALLOW_COPY_AND_ASSIGN(ElectronApiServiceImpl);
+};
+
+}  // namespace atom
+
+#endif  // ATOM_RENDERER_ELECTRON_API_SERVICE_IMPL_H_

+ 14 - 0
atom/renderer/renderer_client_base.cc

@@ -15,6 +15,7 @@
 #include "atom/renderer/atom_render_frame_observer.h"
 #include "atom/renderer/atom_render_view_observer.h"
 #include "atom/renderer/content_settings_observer.h"
+#include "atom/renderer/electron_api_service_impl.h"
 #include "atom/renderer/preferences_manager.h"
 #include "base/command_line.h"
 #include "base/strings/string_split.h"
@@ -26,6 +27,7 @@
 #include "electron/buildflags/buildflags.h"
 #include "native_mate/dictionary.h"
 #include "printing/buildflags/buildflags.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
 #include "third_party/blink/public/web/blink.h"
 #include "third_party/blink/public/web/web_custom_element.h"  // NOLINT(build/include_alpha)
 #include "third_party/blink/public/web/web_frame_widget.h"
@@ -215,6 +217,18 @@ void RendererClientBase::RenderFrameCreated(
       render_frame, std::make_unique<atom::PrintRenderFrameHelperDelegate>());
 #endif
 
+  // TODO(nornagon): it might be possible for an IPC message sent to this
+  // service to trigger v8 context creation before the page has begun loading.
+  // However, it's unclear whether such a timing is possible to trigger, and we
+  // don't have any test to confirm it. Add a test that confirms that a
+  // main->renderer IPC can't cause the preload script to be executed twice. If
+  // it is possible to trigger the preload script before the document is ready
+  // through this interface, we should delay adding it to the registry until
+  // the document is ready.
+  render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
+      base::BindRepeating(&ElectronApiServiceImpl::CreateMojoService,
+                          render_frame, this));
+
 #if BUILDFLAG(ENABLE_PDF_VIEWER)
   // Allow access to file scheme from pdf viewer.
   blink::WebSecurityPolicy::AddOriginAccessWhitelistEntry(

+ 2 - 0
filenames.gni

@@ -661,6 +661,8 @@ filenames = {
     "atom/renderer/atom_renderer_client.h",
     "atom/renderer/content_settings_observer.cc",
     "atom/renderer/content_settings_observer.h",
+    "atom/renderer/electron_api_service_impl.cc",
+    "atom/renderer/electron_api_service_impl.h",
     "atom/renderer/atom_sandboxed_renderer_client.cc",
     "atom/renderer/atom_sandboxed_renderer_client.h",
     "atom/renderer/guest_view_container.cc",

+ 1 - 5
lib/browser/api/web-contents.js

@@ -228,17 +228,13 @@ WebContents.prototype.getZoomFactor = function (callback) {
 
 WebContents.prototype.takeHeapSnapshot = function (filePath) {
   return new Promise((resolve, reject) => {
-    const channel = `ELECTRON_TAKE_HEAP_SNAPSHOT_RESULT_${getNextId()}`
-    ipcMainInternal.once(channel, (event, success) => {
+    this._takeHeapSnapshot(filePath, (success) => {
       if (success) {
         resolve()
       } else {
         reject(new Error('takeHeapSnapshot failed'))
       }
     })
-    if (!this._takeHeapSnapshot(filePath, channel)) {
-      ipcMainInternal.emit(channel, false)
-    }
   })
 }
 

+ 2 - 2
lib/renderer/api/ipc-renderer.js

@@ -8,11 +8,11 @@ const ipcRenderer = v8Util.getHiddenValue(global, 'ipc')
 const internal = false
 
 ipcRenderer.send = function (channel, ...args) {
-  return binding.send(internal, channel, args)
+  return binding.ipc.send(internal, channel, args)
 }
 
 ipcRenderer.sendSync = function (channel, ...args) {
-  return binding.sendSync(internal, channel, args)[0]
+  return binding.ipc.sendSync(internal, channel, args)[0]
 }
 
 ipcRenderer.sendToHost = function (channel, ...args) {

+ 11 - 2
lib/renderer/init.ts

@@ -23,8 +23,17 @@ globalPaths.push(path.join(__dirname, 'api', 'exports'))
 // The global variable will be used by ipc for event dispatching
 const v8Util = process.electronBinding('v8_util')
 
-v8Util.setHiddenValue(global, 'ipc', new EventEmitter())
-v8Util.setHiddenValue(global, 'ipc-internal', new EventEmitter())
+const ipcEmitter = new EventEmitter()
+const ipcInternalEmitter = new EventEmitter()
+v8Util.setHiddenValue(global, 'ipc', ipcEmitter)
+v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter)
+
+v8Util.setHiddenValue(global, 'ipcNative', {
+  onMessage (internal: boolean, channel: string, args: any[], senderId: number) {
+    const sender = internal ? ipcInternalEmitter : ipcEmitter
+    sender.emit(channel, { sender, senderId }, ...args)
+  }
+})
 
 // Use electron module after everything is ready.
 const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal')

+ 2 - 2
lib/renderer/ipc-renderer-internal.ts

@@ -6,11 +6,11 @@ export const ipcRendererInternal: Electron.IpcRendererInternal = v8Util.getHidde
 const internal = true
 
 ipcRendererInternal.send = function (channel, ...args) {
-  return binding.send(internal, channel, args)
+  return binding.ipc.send(internal, channel, args)
 }
 
 ipcRendererInternal.sendSync = function (channel, ...args) {
-  return binding.sendSync(internal, channel, args)[0]
+  return binding.ipc.sendSync(internal, channel, args)[0]
 }
 
 ipcRendererInternal.sendTo = function (webContentsId, channel, ...args) {

+ 19 - 22
lib/sandboxed_renderer/init.js

@@ -45,30 +45,27 @@ const loadedModules = new Map([
   ['url', require('url')]
 ])
 
-// AtomSandboxedRendererClient will look for the "ipcNative" hidden object when
-// invoking the `onMessage`/`onExit` callbacks.
-const ipcNative = process.electronBinding('ipc')
-v8Util.setHiddenValue(global, 'ipcNative', ipcNative)
-
-ipcNative.onMessage = function (internal, channel, args, senderId) {
-  if (internal) {
-    ipcRendererInternal.emit(channel, { sender: ipcRendererInternal, senderId }, ...args)
-  } else {
-    electron.ipcRenderer.emit(channel, { sender: electron.ipcRenderer, senderId }, ...args)
+// ElectronApiServiceImpl will look for the "ipcNative" hidden object when
+// invoking the 'onMessage' callback.
+v8Util.setHiddenValue(global, 'ipcNative', {
+  onMessage (internal, channel, args, senderId) {
+    const sender = internal ? ipcRendererInternal : electron.ipcRenderer
+    sender.emit(channel, { sender, senderId }, ...args)
   }
-}
-
-ipcNative.onExit = function () {
-  process.emit('exit')
-}
-
-ipcNative.onDocumentStart = function () {
-  process.emit('document-start')
-}
+})
 
-ipcNative.onDocumentEnd = function () {
-  process.emit('document-end')
-}
+// AtomSandboxedRendererClient will look for the "lifecycle" hidden object when
+v8Util.setHiddenValue(global, 'lifecycle', {
+  onExit () {
+    process.emit('exit')
+  },
+  onDocumentStart () {
+    process.emit('document-start')
+  },
+  onDocumentEnd () {
+    process.emit('document-end')
+  }
+})
 
 const { webFrameInit } = require('@electron/internal/renderer/web-frame-init')
 webFrameInit()