diff --git a/.circleci/config.yml b/.circleci/config.yml index 74c9b383c73..dc2e12fd738 100644 --- a/.circleci/config.yml +++ b/.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: diff --git a/BUILD.gn b/BUILD.gn index f3e57e0f8de..b113353a888 100644 --- a/BUILD.gn +++ b/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", diff --git a/appveyor.yml b/appveyor.yml index 69d703b1cc7..74785f8d699 100644 --- a/appveyor.yml +++ b/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 diff --git a/atom/app/manifests.cc b/atom/app/manifests.cc index 17d1374f80d..a5288d00260 100644 --- a/atom/app/manifests.cc +++ b/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; } diff --git a/atom/browser/api/atom_api_event.cc b/atom/browser/api/atom_api_event.cc index 96a3bf530e4..0c213dcfe21 100644 --- a/atom/browser/api/atom_api_event.cc +++ b/atom/browser/api/atom_api_event.cc @@ -10,7 +10,7 @@ namespace { v8::Local CreateWithSender(v8::Isolate* isolate, v8::Local sender) { - return mate::internal::CreateJSEvent(isolate, sender, nullptr, nullptr); + return mate::internal::CreateJSEvent(isolate, sender, nullptr, base::nullopt); } void Initialize(v8::Local exports, diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 1d47c1971b8..ab9a5f2d582 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/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 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(); + 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 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(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 diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 867be4cb50f..053c252bf89 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/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 #include #include #include @@ -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, 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, // 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); // Properties. int32_t ID() const; @@ -411,6 +416,7 @@ class WebContents : public mate::TrackableObject, 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, 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, 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, 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, // -1 means no speculative RVH has been committed yet. int currently_committed_process_id_ = -1; + service_manager::BinderRegistryWithArgs registry_; + mojo::BindingSet bindings_; + std::map> + frame_to_bindings_map_; + DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/atom/browser/api/event.cc b/atom/browser/api/event.cc index 7b617034eca..25ee4c641d7 100644 --- a/atom/browser/api/event.cc +++ b/atom/browser/api/event.cc @@ -4,6 +4,8 @@ #include "atom/browser/api/event.h" +#include + #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 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 diff --git a/atom/browser/api/event.h b/atom/browser/api/event.h index fbec8f27574..35c241ea3e3 100644 --- a/atom/browser/api/event.h +++ b/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, public content::WebContentsObserver { public: + using MessageSyncCallback = atom::mojom::ElectronBrowser::MessageSyncCallback; static Handle Create(v8::Isolate* isolate); static void BuildPrototype(v8::Isolate* isolate, @@ -24,7 +27,7 @@ class Event : public Wrappable, public content::WebContentsObserver { // Pass the sender and message to be replied. void SetSenderAndMessage(content::RenderFrameHost* sender, - IPC::Message* message); + base::Optional callback); // event.PreventDefault(). void PreventDefault(v8::Isolate* isolate); @@ -45,7 +48,7 @@ class Event : public Wrappable, public content::WebContentsObserver { private: // Replyer for the synchronous messages. content::RenderFrameHost* sender_ = nullptr; - IPC::Message* message_ = nullptr; + base::Optional callback_; DISALLOW_COPY_AND_ASSIGN(Event); }; diff --git a/atom/browser/api/event_emitter.cc b/atom/browser/api/event_emitter.cc index 060978b4ca3..0f8ee7c8bdc 100644 --- a/atom/browser/api/event_emitter.cc +++ b/atom/browser/api/event_emitter.cc @@ -4,6 +4,8 @@ #include "atom/browser/api/event_emitter.h" +#include + #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 CreateEventObject(v8::Isolate* isolate) { namespace internal { -v8::Local CreateJSEvent(v8::Isolate* isolate, - v8::Local object, - content::RenderFrameHost* sender, - IPC::Message* message) { +v8::Local CreateJSEvent( + v8::Isolate* isolate, + v8::Local object, + content::RenderFrameHost* sender, + base::Optional + callback) { v8::Local event; - bool use_native_event = sender && message; + bool use_native_event = sender && callback; if (use_native_event) { mate::Handle native_event = mate::Event::Create(isolate); - native_event->SetSenderAndMessage(sender, message); + native_event->SetSenderAndMessage(sender, std::move(callback)); event = v8::Local::Cast(native_event.ToV8()); } else { event = CreateEventObject(isolate); diff --git a/atom/browser/api/event_emitter.h b/atom/browser/api/event_emitter.h index ada3a74fa7b..09013e95700 100644 --- a/atom/browser/api/event_emitter.h +++ b/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 #include #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 CreateJSEvent(v8::Isolate* isolate, - v8::Local object, - content::RenderFrameHost* sender, - IPC::Message* message); +v8::Local CreateJSEvent( + v8::Isolate* isolate, + v8::Local object, + content::RenderFrameHost* sender, + base::Optional callback); v8::Local CreateCustomEvent(v8::Isolate* isolate, v8::Local object, v8::Local event); @@ -69,23 +69,25 @@ class EventEmitter : public Wrappable { // this.emit(name, new Event(), args...); template 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 - 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 + callback, + const Args&... args) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); v8::Local wrapper = GetWrapper(); if (wrapper.IsEmpty()) { return false; } - v8::Local event = - internal::CreateJSEvent(isolate(), wrapper, sender, message); + v8::Local event = internal::CreateJSEvent( + isolate(), wrapper, sender, std::move(callback)); return EmitWithEvent(name, event, args...); } diff --git a/atom/common/api/BUILD.gn b/atom/common/api/BUILD.gn new file mode 100644 index 00000000000..ed29819c656 --- /dev/null +++ b/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", + ] +} diff --git a/atom/common/api/api.mojom b/atom/common/api/api.mojom new file mode 100644 index 00000000000..844080f46d5 --- /dev/null +++ b/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); +}; diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index 4ea3c121118..ac416256bcc 100644 --- a/atom/common/api/api_messages.h +++ b/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 */) diff --git a/atom/common/api/remote_callback_freer.cc b/atom/common/api/remote_callback_freer.cc index 4c5603106cc..561b625d244 100644 --- a/atom/common/api/remote_callback_freer.cc +++ b/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); diff --git a/atom/common/api/remote_object_freer.cc b/atom/common/api/remote_object_freer.cc index da6ba6de8f6..39e14f0a2a5 100644 --- a/atom/common/api/remote_object_freer.cc +++ b/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 diff --git a/atom/renderer/api/atom_api_renderer_ipc.cc b/atom/renderer/api/atom_api_renderer_ipc.cc index c6d0b94807d..d5df9f1012f 100644 --- a/atom/renderer/api/atom_api_renderer_ipc.cc +++ b/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; +class IPCRenderer : public mate::Wrappable { + 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 prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "IPCRenderer")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .SetMethod("send", &IPCRenderer::Send) + .SetMethod("sendSync", &IPCRenderer::SendSync); + } + static mate::Handle Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new IPCRenderer(isolate)); + } - bool success = render_frame->Send(new AtomFrameHostMsg_Message( - render_frame->GetRoutingID(), internal, channel, arguments)); + void Send(mate::Arguments* args, + bool internal, + const std::string& channel, + const base::ListValue& arguments) { + electron_browser_ptr_->Message(internal, channel, arguments.Clone()); + } - if (!success) - args->ThrowError("Unable to send AtomFrameHostMsg_Message"); -} + base::Value SendSync(mate::Arguments* args, + bool internal, + const std::string& channel, + const base::ListValue& arguments) { + base::Value result; -base::ListValue SendSync(mate::Arguments* args, - bool internal, - const std::string& channel, - const base::ListValue& arguments) { - base::ListValue 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 task_runner = + base::CreateSingleThreadTaskRunnerWithTraits({}); - RenderFrame* render_frame = GetCurrentRenderFrame(); - if (render_frame == nullptr) + 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); + 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(); + } - if (!success) - args->ThrowError("Unable to send AtomFrameHostMsg_Message_Sync"); - - return result; -} + atom::mojom::ElectronBrowserPtr electron_browser_ptr_; +}; void SendTo(mate::Arguments* args, bool internal, @@ -99,8 +156,7 @@ void Initialize(v8::Local exports, v8::Local 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); } diff --git a/atom/renderer/atom_render_frame_observer.cc b/atom/renderer/atom_render_frame_observer.cc index af340620fd8..0cfb93dc6ac 100644 --- a/atom/renderer/atom_render_frame_observer.cc +++ b/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 context, - bool internal, - v8::Local* ipc) { - v8::Local key = - mate::StringToV8(isolate, internal ? "ipc-internal" : "ipc"); - v8::Local privateKey = v8::Private::ForApi(isolate, key); - v8::Local global_object = context->Global(); - v8::Local 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> ListValueToVector( - v8::Isolate* isolate, - const base::ListValue& list) { - v8::Local array = mate::ConvertToV8(isolate, list); - std::vector> 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 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 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 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 diff --git a/atom/renderer/atom_render_frame_observer.h b/atom/renderer/atom_render_frame_observer.h index 4bb547e0f5b..5b31a38f1b6 100644 --- a/atom/renderer/atom_render_frame_observer.h +++ b/atom/renderer/atom_render_frame_observer.h @@ -51,32 +51,17 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { void WillReleaseScriptContext(v8::Local 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); }; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 2495a9b4871..0ab08b25207 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/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. diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index 181f26935d3..437d741073e 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/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, diff --git a/atom/renderer/atom_sandboxed_renderer_client.cc b/atom/renderer/atom_sandboxed_renderer_client.cc index a64102d1772..20bd915aea8 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.cc +++ b/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 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 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>(argv, argv + node::arraysize(argv))); - } - - private: - AtomSandboxedRendererClient* renderer_client_; - DISALLOW_COPY_AND_ASSIGN(AtomSandboxedRenderFrameObserver); -}; +void InvokeHiddenCallback(v8::Handle 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 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::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>()); + 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>()); + 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>()); -} - -void AtomSandboxedRendererClient::InvokeIpcCallback( - v8::Handle context, - const std::string& callback_name, - std::vector> 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 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::Cast(callback_value); - ignore_result(callback->Call(context, binding, args.size(), args.data())); + InvokeHiddenCallback(context, kLifecycleKey, "onExit"); } } // namespace atom diff --git a/atom/renderer/atom_sandboxed_renderer_client.h b/atom/renderer/atom_sandboxed_renderer_client.h index ee6c925c368..caede061359 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.h +++ b/atom/renderer/atom_sandboxed_renderer_client.h @@ -22,9 +22,6 @@ class AtomSandboxedRendererClient : public RendererClientBase { void InitializeBindings(v8::Local binding, v8::Local context, bool is_main_frame); - void InvokeIpcCallback(v8::Handle context, - const std::string& callback_name, - std::vector> args); // atom::RendererClientBase: void DidCreateScriptContext(v8::Handle context, content::RenderFrame* render_frame) override; diff --git a/atom/renderer/electron_api_service_impl.cc b/atom/renderer/electron_api_service_impl.cc new file mode 100644 index 00000000000..aa20b2a0881 --- /dev/null +++ b/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 +#include +#include + +#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 GetIpcObject(v8::Local 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 context, + const std::string& callback_name, + std::vector> 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 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::Cast(callback_value); + ignore_result(callback->Call(context, ipcNative, args.size(), args.data())); +} + +void EmitIPCEvent(v8::Local context, + bool internal, + const std::string& channel, + const std::vector& 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> 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 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 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 diff --git a/atom/renderer/electron_api_service_impl.h b/atom/renderer/electron_api_service_impl.h new file mode 100644 index 00000000000..19c51b81091 --- /dev/null +++ b/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 + +#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 binding_; + + content::RenderFrame* render_frame_; + RendererClientBase* renderer_client_; + + DISALLOW_COPY_AND_ASSIGN(ElectronApiServiceImpl); +}; + +} // namespace atom + +#endif // ATOM_RENDERER_ELECTRON_API_SERVICE_IMPL_H_ diff --git a/atom/renderer/renderer_client_base.cc b/atom/renderer/renderer_client_base.cc index 037a97b6e06..645cd1edce9 100644 --- a/atom/renderer/renderer_client_base.cc +++ b/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()); #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( diff --git a/filenames.gni b/filenames.gni index 81524e40bf5..75d53f97ee1 100644 --- a/filenames.gni +++ b/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", diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 3f4d2e1294c..2330416a47c 100644 --- a/lib/browser/api/web-contents.js +++ b/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) - } }) } diff --git a/lib/renderer/api/ipc-renderer.js b/lib/renderer/api/ipc-renderer.js index e91b7d71a4c..b0a242a12dd 100644 --- a/lib/renderer/api/ipc-renderer.js +++ b/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) { diff --git a/lib/renderer/init.ts b/lib/renderer/init.ts index e22f68409ef..32e28bca886 100644 --- a/lib/renderer/init.ts +++ b/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') diff --git a/lib/renderer/ipc-renderer-internal.ts b/lib/renderer/ipc-renderer-internal.ts index 9da1186e9a7..7d4884c226b 100644 --- a/lib/renderer/ipc-renderer-internal.ts +++ b/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) { diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 1eff5fb7b3e..62c50a38585 100644 --- a/lib/sandboxed_renderer/init.js +++ b/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()