feat: service worker preload scripts for improved extensions support (#44411)
* feat: preload scripts for service workers * feat: service worker IPC * test: service worker preload scripts and ipc
This commit is contained in:
parent
bc22ee7897
commit
26da3c5d6e
67 changed files with 2103 additions and 298 deletions
|
@ -1762,6 +1762,12 @@ gin::Handle<Session> Session::CreateFrom(
|
|||
// to use partition strings, instead of using the Session object directly.
|
||||
handle->Pin(isolate);
|
||||
|
||||
v8::TryCatch try_catch(isolate);
|
||||
gin_helper::CallMethod(isolate, handle.get(), "_init");
|
||||
if (try_catch.HasCaught()) {
|
||||
node::errors::TriggerUncaughtException(isolate, try_catch);
|
||||
}
|
||||
|
||||
App::Get()->EmitWithoutEvent("session-created", handle);
|
||||
|
||||
return handle;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "gin/wrappable.h"
|
||||
#include "services/network/public/mojom/host_resolver.mojom-forward.h"
|
||||
#include "services/network/public/mojom/ssl_config.mojom-forward.h"
|
||||
#include "shell/browser/api/ipc_dispatcher.h"
|
||||
#include "shell/browser/event_emitter_mixin.h"
|
||||
#include "shell/browser/net/resolve_proxy_helper.h"
|
||||
#include "shell/common/gin_helper/cleaned_up_at_exit.h"
|
||||
|
@ -66,6 +67,7 @@ class Session final : public gin::Wrappable<Session>,
|
|||
public gin_helper::Constructible<Session>,
|
||||
public gin_helper::EventEmitterMixin<Session>,
|
||||
public gin_helper::CleanedUpAtExit,
|
||||
public IpcDispatcher<Session>,
|
||||
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
|
||||
private SpellcheckHunspellDictionary::Observer,
|
||||
#endif
|
||||
|
|
|
@ -132,6 +132,7 @@
|
|||
#include "shell/common/gin_helper/locker.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/gin_helper/reply_channel.h"
|
||||
#include "shell/common/language_util.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/node_util.h"
|
||||
|
@ -1982,66 +1983,6 @@ void WebContents::OnFirstNonEmptyLayout(
|
|||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// This object wraps the InvokeCallback so that if it gets GC'd by V8, we can
|
||||
// still call the callback and send an error. Not doing so causes a Mojo DCHECK,
|
||||
// since Mojo requires callbacks to be called before they are destroyed.
|
||||
class ReplyChannel final : public gin::Wrappable<ReplyChannel> {
|
||||
public:
|
||||
using InvokeCallback = electron::mojom::ElectronApiIPC::InvokeCallback;
|
||||
static gin::Handle<ReplyChannel> Create(v8::Isolate* isolate,
|
||||
InvokeCallback callback) {
|
||||
return gin::CreateHandle(isolate, new ReplyChannel(std::move(callback)));
|
||||
}
|
||||
|
||||
// gin::Wrappable
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override {
|
||||
return gin::Wrappable<ReplyChannel>::GetObjectTemplateBuilder(isolate)
|
||||
.SetMethod("sendReply", &ReplyChannel::SendReply);
|
||||
}
|
||||
const char* GetTypeName() override { return "ReplyChannel"; }
|
||||
|
||||
void SendError(const std::string& msg) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
// If there's no current context, it means we're shutting down, so we
|
||||
// don't need to send an event.
|
||||
if (!isolate->GetCurrentContext().IsEmpty()) {
|
||||
v8::HandleScope scope(isolate);
|
||||
auto message = gin::DataObjectBuilder(isolate).Set("error", msg).Build();
|
||||
SendReply(isolate, message);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
explicit ReplyChannel(InvokeCallback callback)
|
||||
: callback_(std::move(callback)) {}
|
||||
~ReplyChannel() override {
|
||||
if (callback_)
|
||||
SendError("reply was never sent");
|
||||
}
|
||||
|
||||
bool SendReply(v8::Isolate* isolate, v8::Local<v8::Value> arg) {
|
||||
if (!callback_)
|
||||
return false;
|
||||
blink::CloneableMessage message;
|
||||
if (!gin::ConvertFromV8(isolate, arg, &message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::move(callback_).Run(std::move(message));
|
||||
return true;
|
||||
}
|
||||
|
||||
InvokeCallback callback_;
|
||||
};
|
||||
|
||||
gin::WrapperInfo ReplyChannel::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
} // namespace
|
||||
|
||||
gin::Handle<gin_helper::internal::Event> WebContents::MakeEventWithSender(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* frame,
|
||||
|
@ -2050,7 +1991,7 @@ gin::Handle<gin_helper::internal::Event> WebContents::MakeEventWithSender(
|
|||
if (!GetWrapper(isolate).ToLocal(&wrapper)) {
|
||||
if (callback) {
|
||||
// We must always invoke the callback if present.
|
||||
ReplyChannel::Create(isolate, std::move(callback))
|
||||
gin_helper::internal::ReplyChannel::Create(isolate, std::move(callback))
|
||||
->SendError("WebContents was destroyed");
|
||||
}
|
||||
return {};
|
||||
|
@ -2058,9 +1999,10 @@ gin::Handle<gin_helper::internal::Event> WebContents::MakeEventWithSender(
|
|||
gin::Handle<gin_helper::internal::Event> event =
|
||||
gin_helper::internal::Event::New(isolate);
|
||||
gin_helper::Dictionary dict(isolate, event.ToV8().As<v8::Object>());
|
||||
dict.Set("type", "frame");
|
||||
if (callback)
|
||||
dict.Set("_replyChannel",
|
||||
ReplyChannel::Create(isolate, std::move(callback)));
|
||||
dict.Set("_replyChannel", gin_helper::internal::ReplyChannel::Create(
|
||||
isolate, std::move(callback)));
|
||||
if (frame) {
|
||||
dict.SetGetter("senderFrame", frame);
|
||||
dict.Set("frameId", frame->GetRoutingID());
|
||||
|
|
89
shell/browser/api/ipc_dispatcher.h
Normal file
89
shell/browser/api/ipc_dispatcher.h
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2025 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_API_IPC_DISPATCHER_H_
|
||||
#define ELECTRON_SHELL_BROWSER_API_IPC_DISPATCHER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/trace_event/trace_event.h"
|
||||
#include "base/values.h"
|
||||
#include "gin/handle.h"
|
||||
#include "shell/browser/api/message_port.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/api/api.mojom.h"
|
||||
#include "shell/common/gin_converters/blink_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/event.h"
|
||||
#include "shell/common/gin_helper/reply_channel.h"
|
||||
#include "shell/common/v8_util.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
// Handles dispatching IPCs to JS.
|
||||
// See ipc-dispatch.ts for JS listeners.
|
||||
template <typename T>
|
||||
class IpcDispatcher {
|
||||
public:
|
||||
void Message(gin::Handle<gin_helper::internal::Event>& event,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage args) {
|
||||
TRACE_EVENT1("electron", "IpcDispatcher::Message", "channel", channel);
|
||||
emitter()->EmitWithoutEvent("-ipc-message", event, channel, args);
|
||||
}
|
||||
|
||||
void Invoke(gin::Handle<gin_helper::internal::Event>& event,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments,
|
||||
electron::mojom::ElectronApiIPC::InvokeCallback callback) {
|
||||
TRACE_EVENT1("electron", "IpcHelper::Invoke", "channel", channel);
|
||||
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
gin_helper::Dictionary dict(isolate, event.ToV8().As<v8::Object>());
|
||||
dict.Set("_replyChannel", gin_helper::internal::ReplyChannel::Create(
|
||||
isolate, std::move(callback)));
|
||||
|
||||
emitter()->EmitWithoutEvent("-ipc-invoke", event, channel,
|
||||
std::move(arguments));
|
||||
}
|
||||
|
||||
void ReceivePostMessage(gin::Handle<gin_helper::internal::Event>& event,
|
||||
const std::string& channel,
|
||||
blink::TransferableMessage message) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
auto wrapped_ports =
|
||||
MessagePort::EntanglePorts(isolate, std::move(message.ports));
|
||||
v8::Local<v8::Value> message_value =
|
||||
electron::DeserializeV8Value(isolate, message);
|
||||
emitter()->EmitWithoutEvent("-ipc-ports", event, channel, message_value,
|
||||
std::move(wrapped_ports));
|
||||
}
|
||||
|
||||
void MessageSync(
|
||||
gin::Handle<gin_helper::internal::Event>& event,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments,
|
||||
electron::mojom::ElectronApiIPC::MessageSyncCallback callback) {
|
||||
TRACE_EVENT1("electron", "IpcHelper::MessageSync", "channel", channel);
|
||||
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
gin_helper::Dictionary dict(isolate, event.ToV8().As<v8::Object>());
|
||||
dict.Set("_replyChannel", gin_helper::internal::ReplyChannel::Create(
|
||||
isolate, std::move(callback)));
|
||||
|
||||
emitter()->EmitWithoutEvent("-ipc-message-sync", event, channel,
|
||||
std::move(arguments));
|
||||
}
|
||||
|
||||
private:
|
||||
inline T* emitter() {
|
||||
// T must inherit from gin_helper::EventEmitterMixin<T>
|
||||
return static_cast<T*>(this);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_API_IPC_DISPATCHER_H_
|
199
shell/browser/electron_api_sw_ipc_handler_impl.cc
Normal file
199
shell/browser/electron_api_sw_ipc_handler_impl.cc
Normal file
|
@ -0,0 +1,199 @@
|
|||
// Copyright (c) 2025 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/electron_api_sw_ipc_handler_impl.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/containers/unique_ptr_adapters.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
const void* const kUserDataKey = &kUserDataKey;
|
||||
|
||||
class ServiceWorkerIPCList : public base::SupportsUserData::Data {
|
||||
public:
|
||||
std::vector<std::unique_ptr<ElectronApiSWIPCHandlerImpl>> list;
|
||||
|
||||
static ServiceWorkerIPCList* Get(
|
||||
content::RenderProcessHost* render_process_host,
|
||||
bool create_if_not_exists) {
|
||||
auto* service_worker_ipc_list = static_cast<ServiceWorkerIPCList*>(
|
||||
render_process_host->GetUserData(kUserDataKey));
|
||||
if (!service_worker_ipc_list && !create_if_not_exists) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!service_worker_ipc_list) {
|
||||
auto new_ipc_list = std::make_unique<ServiceWorkerIPCList>();
|
||||
service_worker_ipc_list = new_ipc_list.get();
|
||||
render_process_host->SetUserData(kUserDataKey, std::move(new_ipc_list));
|
||||
}
|
||||
return service_worker_ipc_list;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
ElectronApiSWIPCHandlerImpl::ElectronApiSWIPCHandlerImpl(
|
||||
content::RenderProcessHost* render_process_host,
|
||||
int64_t version_id,
|
||||
mojo::PendingAssociatedReceiver<mojom::ElectronApiIPC> receiver)
|
||||
: render_process_host_(render_process_host), version_id_(version_id) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
|
||||
receiver_.Bind(std::move(receiver));
|
||||
receiver_.set_disconnect_handler(
|
||||
base::BindOnce(&ElectronApiSWIPCHandlerImpl::RemoteDisconnected,
|
||||
base::Unretained(this)));
|
||||
|
||||
render_process_host_->AddObserver(this);
|
||||
}
|
||||
|
||||
ElectronApiSWIPCHandlerImpl::~ElectronApiSWIPCHandlerImpl() {
|
||||
render_process_host_->RemoveObserver(this);
|
||||
}
|
||||
|
||||
void ElectronApiSWIPCHandlerImpl::RemoteDisconnected() {
|
||||
receiver_.reset();
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void ElectronApiSWIPCHandlerImpl::Message(bool internal,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments) {
|
||||
auto* session = GetSession();
|
||||
if (session) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
MakeIPCEvent(isolate, internal);
|
||||
session->Message(event, channel, std::move(arguments));
|
||||
}
|
||||
}
|
||||
|
||||
void ElectronApiSWIPCHandlerImpl::Invoke(bool internal,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments,
|
||||
InvokeCallback callback) {
|
||||
auto* session = GetSession();
|
||||
if (session) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
MakeIPCEvent(isolate, internal);
|
||||
session->Invoke(event, channel, std::move(arguments), std::move(callback));
|
||||
}
|
||||
}
|
||||
|
||||
void ElectronApiSWIPCHandlerImpl::ReceivePostMessage(
|
||||
const std::string& channel,
|
||||
blink::TransferableMessage message) {
|
||||
auto* session = GetSession();
|
||||
if (session) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
MakeIPCEvent(isolate, false);
|
||||
session->ReceivePostMessage(event, channel, std::move(message));
|
||||
}
|
||||
}
|
||||
|
||||
void ElectronApiSWIPCHandlerImpl::MessageSync(bool internal,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments,
|
||||
MessageSyncCallback callback) {
|
||||
auto* session = GetSession();
|
||||
if (session) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
MakeIPCEvent(isolate, internal);
|
||||
session->MessageSync(event, channel, std::move(arguments),
|
||||
std::move(callback));
|
||||
}
|
||||
}
|
||||
|
||||
void ElectronApiSWIPCHandlerImpl::MessageHost(
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments) {
|
||||
NOTIMPLEMENTED(); // Service workers have no <webview>
|
||||
}
|
||||
|
||||
ElectronBrowserContext* ElectronApiSWIPCHandlerImpl::GetBrowserContext() {
|
||||
auto* browser_context = static_cast<ElectronBrowserContext*>(
|
||||
render_process_host_->GetBrowserContext());
|
||||
return browser_context;
|
||||
}
|
||||
|
||||
api::Session* ElectronApiSWIPCHandlerImpl::GetSession() {
|
||||
return api::Session::FromBrowserContext(GetBrowserContext());
|
||||
}
|
||||
|
||||
gin::Handle<gin_helper::internal::Event>
|
||||
ElectronApiSWIPCHandlerImpl::MakeIPCEvent(v8::Isolate* isolate, bool internal) {
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
gin_helper::internal::Event::New(isolate);
|
||||
v8::Local<v8::Object> event_object = event.ToV8().As<v8::Object>();
|
||||
|
||||
gin_helper::Dictionary dict(isolate, event_object);
|
||||
dict.Set("type", "service-worker");
|
||||
dict.Set("versionId", version_id_);
|
||||
dict.Set("processId", render_process_host_->GetID().GetUnsafeValue());
|
||||
|
||||
// Set session to provide context for getting preloads
|
||||
dict.Set("session", GetSession());
|
||||
|
||||
if (internal)
|
||||
dict.SetHidden("internal", internal);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
void ElectronApiSWIPCHandlerImpl::Destroy() {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
|
||||
auto* service_worker_ipc_list = ServiceWorkerIPCList::Get(
|
||||
render_process_host_, /*create_if_not_exists=*/false);
|
||||
CHECK(service_worker_ipc_list);
|
||||
// std::erase_if will lead to a call to the destructor for this object.
|
||||
std::erase_if(service_worker_ipc_list->list, base::MatchesUniquePtr(this));
|
||||
}
|
||||
|
||||
void ElectronApiSWIPCHandlerImpl::RenderProcessExited(
|
||||
content::RenderProcessHost* host,
|
||||
const content::ChildProcessTerminationInfo& info) {
|
||||
CHECK_EQ(host, render_process_host_);
|
||||
// TODO(crbug.com/1407197): Investigate clearing the user data from
|
||||
// RenderProcessHostImpl::Cleanup.
|
||||
Destroy();
|
||||
// This instance has now been deleted.
|
||||
}
|
||||
|
||||
// static
|
||||
void ElectronApiSWIPCHandlerImpl::BindReceiver(
|
||||
int render_process_id,
|
||||
int64_t version_id,
|
||||
mojo::PendingAssociatedReceiver<mojom::ElectronApiIPC> receiver) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
auto* render_process_host =
|
||||
content::RenderProcessHost::FromID(render_process_id);
|
||||
if (!render_process_host) {
|
||||
return;
|
||||
}
|
||||
auto* service_worker_ipc_list = ServiceWorkerIPCList::Get(
|
||||
render_process_host, /*create_if_not_exists=*/true);
|
||||
service_worker_ipc_list->list.push_back(
|
||||
std::make_unique<ElectronApiSWIPCHandlerImpl>(
|
||||
render_process_host, version_id, std::move(receiver)));
|
||||
}
|
||||
|
||||
} // namespace electron
|
98
shell/browser/electron_api_sw_ipc_handler_impl.h
Normal file
98
shell/browser/electron_api_sw_ipc_handler_impl.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright (c) 2025 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_ELECTRON_API_SW_IPC_HANDLER_IMPL_H_
|
||||
#define ELECTRON_SHELL_BROWSER_ELECTRON_API_SW_IPC_HANDLER_IMPL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/render_process_host_observer.h"
|
||||
#include "electron/shell/common/api/api.mojom.h"
|
||||
#include "gin/handle.h"
|
||||
#include "mojo/public/cpp/bindings/associated_receiver.h"
|
||||
#include "shell/common/gin_helper/event.h"
|
||||
|
||||
namespace content {
|
||||
class RenderProcessHost;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
class ElectronBrowserContext;
|
||||
|
||||
namespace api {
|
||||
class Session;
|
||||
}
|
||||
|
||||
class ElectronApiSWIPCHandlerImpl : public mojom::ElectronApiIPC,
|
||||
public content::RenderProcessHostObserver {
|
||||
public:
|
||||
explicit ElectronApiSWIPCHandlerImpl(
|
||||
content::RenderProcessHost* render_process_host,
|
||||
int64_t version_id,
|
||||
mojo::PendingAssociatedReceiver<mojom::ElectronApiIPC> receiver);
|
||||
|
||||
static void BindReceiver(
|
||||
int render_process_id,
|
||||
int64_t version_id,
|
||||
mojo::PendingAssociatedReceiver<mojom::ElectronApiIPC> receiver);
|
||||
|
||||
// disable copy
|
||||
ElectronApiSWIPCHandlerImpl(const ElectronApiSWIPCHandlerImpl&) = delete;
|
||||
ElectronApiSWIPCHandlerImpl& operator=(const ElectronApiSWIPCHandlerImpl&) =
|
||||
delete;
|
||||
~ElectronApiSWIPCHandlerImpl() override;
|
||||
|
||||
// mojom::ElectronApiIPC:
|
||||
void Message(bool internal,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments) override;
|
||||
void Invoke(bool internal,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments,
|
||||
InvokeCallback callback) override;
|
||||
void ReceivePostMessage(const std::string& channel,
|
||||
blink::TransferableMessage message) override;
|
||||
void MessageSync(bool internal,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments,
|
||||
MessageSyncCallback callback) override;
|
||||
void MessageHost(const std::string& channel,
|
||||
blink::CloneableMessage arguments) override;
|
||||
|
||||
base::WeakPtr<ElectronApiSWIPCHandlerImpl> GetWeakPtr() {
|
||||
return weak_factory_.GetWeakPtr();
|
||||
}
|
||||
|
||||
private:
|
||||
ElectronBrowserContext* GetBrowserContext();
|
||||
api::Session* GetSession();
|
||||
|
||||
gin::Handle<gin_helper::internal::Event> MakeIPCEvent(v8::Isolate* isolate,
|
||||
bool internal);
|
||||
|
||||
// content::RenderProcessHostObserver
|
||||
void RenderProcessExited(
|
||||
content::RenderProcessHost* host,
|
||||
const content::ChildProcessTerminationInfo& info) override;
|
||||
|
||||
void RemoteDisconnected();
|
||||
|
||||
// Destroys this instance by removing it from the ServiceWorkerIPCList.
|
||||
void Destroy();
|
||||
|
||||
// This is safe because ElectronApiSWIPCHandlerImpl is tied to the life time
|
||||
// of RenderProcessHost.
|
||||
const raw_ptr<content::RenderProcessHost> render_process_host_;
|
||||
|
||||
// Service worker version ID.
|
||||
int64_t version_id_;
|
||||
|
||||
mojo::AssociatedReceiver<mojom::ElectronApiIPC> receiver_{this};
|
||||
|
||||
base::WeakPtrFactory<ElectronApiSWIPCHandlerImpl> weak_factory_{this};
|
||||
};
|
||||
} // namespace electron
|
||||
#endif // ELECTRON_SHELL_BROWSER_ELECTRON_API_SW_IPC_HANDLER_IMPL_H_
|
|
@ -79,6 +79,7 @@
|
|||
#include "shell/browser/bluetooth/electron_bluetooth_delegate.h"
|
||||
#include "shell/browser/child_web_contents_tracker.h"
|
||||
#include "shell/browser/electron_api_ipc_handler_impl.h"
|
||||
#include "shell/browser/electron_api_sw_ipc_handler_impl.h"
|
||||
#include "shell/browser/electron_autofill_driver_factory.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/electron_browser_main_parts.h"
|
||||
|
@ -581,6 +582,18 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches(
|
|||
web_preferences->AppendCommandLineSwitches(
|
||||
command_line, IsRendererSubFrame(unsafe_process_id));
|
||||
}
|
||||
|
||||
// Service worker processes should only run preloads if one has been
|
||||
// registered prior to startup.
|
||||
auto* render_process_host = content::RenderProcessHost::FromID(process_id);
|
||||
if (render_process_host) {
|
||||
auto* browser_context = render_process_host->GetBrowserContext();
|
||||
auto* session_prefs =
|
||||
SessionPreferences::FromBrowserContext(browser_context);
|
||||
if (session_prefs->HasServiceWorkerPreloadScript()) {
|
||||
command_line->AppendSwitch(switches::kServiceWorkerPreload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1409,6 +1422,13 @@ void ElectronBrowserClient::OverrideURLLoaderFactoryParams(
|
|||
void ElectronBrowserClient::RegisterAssociatedInterfaceBindersForServiceWorker(
|
||||
const content::ServiceWorkerVersionBaseInfo& service_worker_version_info,
|
||||
blink::AssociatedInterfaceRegistry& associated_registry) {
|
||||
CHECK(service_worker_version_info.process_id !=
|
||||
content::ChildProcessHost::kInvalidUniqueID);
|
||||
associated_registry.AddInterface<mojom::ElectronApiIPC>(
|
||||
base::BindRepeating(&ElectronApiSWIPCHandlerImpl::BindReceiver,
|
||||
service_worker_version_info.process_id,
|
||||
service_worker_version_info.version_id));
|
||||
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
associated_registry.AddInterface<extensions::mojom::RendererHost>(
|
||||
base::BindRepeating(&extensions::RendererStartupHelper::BindForRenderer,
|
||||
|
|
|
@ -30,4 +30,13 @@ SessionPreferences* SessionPreferences::FromBrowserContext(
|
|||
return static_cast<SessionPreferences*>(context->GetUserData(&kLocatorKey));
|
||||
}
|
||||
|
||||
bool SessionPreferences::HasServiceWorkerPreloadScript() {
|
||||
const auto& preloads = preload_scripts();
|
||||
auto it = std::find_if(
|
||||
preloads.begin(), preloads.end(), [](const PreloadScript& script) {
|
||||
return script.script_type == PreloadScript::ScriptType::kServiceWorker;
|
||||
});
|
||||
return it != preloads.end();
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
|
|
@ -28,6 +28,8 @@ class SessionPreferences : public base::SupportsUserData::Data {
|
|||
|
||||
std::vector<PreloadScript>& preload_scripts() { return preload_scripts_; }
|
||||
|
||||
bool HasServiceWorkerPreloadScript();
|
||||
|
||||
private:
|
||||
SessionPreferences();
|
||||
|
||||
|
|
|
@ -33,7 +33,10 @@ struct TranslatorHolder {
|
|||
};
|
||||
|
||||
// Cached JavaScript version of |CallTranslator|.
|
||||
v8::Persistent<v8::FunctionTemplate> g_call_translator;
|
||||
// v8::Persistent handles are bound to a specific v8::Isolate. Require
|
||||
// initializing per-thread to avoid using the wrong isolate from service
|
||||
// worker preload scripts.
|
||||
thread_local v8::Persistent<v8::FunctionTemplate> g_call_translator;
|
||||
|
||||
void CallTranslator(v8::Local<v8::External> external,
|
||||
v8::Local<v8::Object> state,
|
||||
|
|
66
shell/common/gin_helper/reply_channel.cc
Normal file
66
shell/common/gin_helper/reply_channel.cc
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2023 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/common/gin_helper/reply_channel.h"
|
||||
|
||||
#include "base/debug/stack_trace.h"
|
||||
#include "gin/data_object_builder.h"
|
||||
#include "gin/handle.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_converters/blink_converter.h"
|
||||
|
||||
namespace gin_helper::internal {
|
||||
|
||||
// static
|
||||
using InvokeCallback = electron::mojom::ElectronApiIPC::InvokeCallback;
|
||||
gin::Handle<ReplyChannel> ReplyChannel::Create(v8::Isolate* isolate,
|
||||
InvokeCallback callback) {
|
||||
return gin::CreateHandle(isolate, new ReplyChannel(std::move(callback)));
|
||||
}
|
||||
|
||||
gin::ObjectTemplateBuilder ReplyChannel::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
return gin::Wrappable<ReplyChannel>::GetObjectTemplateBuilder(isolate)
|
||||
.SetMethod("sendReply", &ReplyChannel::SendReply);
|
||||
}
|
||||
|
||||
const char* ReplyChannel::GetTypeName() {
|
||||
return "ReplyChannel";
|
||||
}
|
||||
|
||||
ReplyChannel::ReplyChannel(InvokeCallback callback)
|
||||
: callback_(std::move(callback)) {}
|
||||
|
||||
ReplyChannel::~ReplyChannel() {
|
||||
if (callback_)
|
||||
SendError("reply was never sent");
|
||||
}
|
||||
|
||||
void ReplyChannel::SendError(const std::string& msg) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
// If there's no current context, it means we're shutting down, so we
|
||||
// don't need to send an event.
|
||||
if (!isolate->GetCurrentContext().IsEmpty()) {
|
||||
v8::HandleScope scope(isolate);
|
||||
auto message = gin::DataObjectBuilder(isolate).Set("error", msg).Build();
|
||||
SendReply(isolate, message);
|
||||
}
|
||||
}
|
||||
|
||||
bool ReplyChannel::SendReply(v8::Isolate* isolate, v8::Local<v8::Value> arg) {
|
||||
if (!callback_)
|
||||
return false;
|
||||
blink::CloneableMessage message;
|
||||
if (!gin::ConvertFromV8(isolate, arg, &message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::move(callback_).Run(std::move(message));
|
||||
return true;
|
||||
}
|
||||
|
||||
gin::WrapperInfo ReplyChannel::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
} // namespace gin_helper::internal
|
54
shell/common/gin_helper/reply_channel.h
Normal file
54
shell/common/gin_helper/reply_channel.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) 2023 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_COMMON_GIN_HELPER_REPLY_CHANNEL_H_
|
||||
#define ELECTRON_SHELL_COMMON_GIN_HELPER_REPLY_CHANNEL_H_
|
||||
|
||||
#include "gin/wrappable.h"
|
||||
#include "shell/common/api/api.mojom.h"
|
||||
|
||||
namespace gin {
|
||||
template <typename T>
|
||||
class Handle;
|
||||
} // namespace gin
|
||||
|
||||
namespace v8 {
|
||||
class Isolate;
|
||||
template <typename T>
|
||||
class Local;
|
||||
class Object;
|
||||
class ObjectTemplate;
|
||||
} // namespace v8
|
||||
|
||||
namespace gin_helper::internal {
|
||||
|
||||
// This object wraps the InvokeCallback so that if it gets GC'd by V8, we can
|
||||
// still call the callback and send an error. Not doing so causes a Mojo DCHECK,
|
||||
// since Mojo requires callbacks to be called before they are destroyed.
|
||||
class ReplyChannel : public gin::Wrappable<ReplyChannel> {
|
||||
public:
|
||||
using InvokeCallback = electron::mojom::ElectronApiIPC::InvokeCallback;
|
||||
static gin::Handle<ReplyChannel> Create(v8::Isolate* isolate,
|
||||
InvokeCallback callback);
|
||||
|
||||
// gin::Wrappable
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override;
|
||||
const char* GetTypeName() override;
|
||||
|
||||
void SendError(const std::string& msg);
|
||||
|
||||
private:
|
||||
explicit ReplyChannel(InvokeCallback callback);
|
||||
~ReplyChannel() override;
|
||||
|
||||
bool SendReply(v8::Isolate* isolate, v8::Local<v8::Value> arg);
|
||||
|
||||
InvokeCallback callback_;
|
||||
};
|
||||
|
||||
} // namespace gin_helper::internal
|
||||
|
||||
#endif // ELECTRON_SHELL_COMMON_GIN_HELPER_REPLY_CHANNEL_H_
|
|
@ -31,8 +31,12 @@ v8::MaybeLocal<v8::Value> CompileAndCall(
|
|||
v8::MaybeLocal<v8::Function> compiled = builtin_loader.LookupAndCompile(
|
||||
context, id, parameters, node::Realm::GetCurrent(context));
|
||||
|
||||
if (compiled.IsEmpty())
|
||||
if (compiled.IsEmpty()) {
|
||||
// TODO(samuelmaddock): how can we get the compilation error message?
|
||||
LOG(ERROR) << "CompileAndCall failed to compile electron script (" << id
|
||||
<< ")";
|
||||
return {};
|
||||
}
|
||||
|
||||
v8::Local<v8::Function> fn = compiled.ToLocalChecked().As<v8::Function>();
|
||||
v8::MaybeLocal<v8::Value> ret = fn->Call(
|
||||
|
@ -47,7 +51,7 @@ v8::MaybeLocal<v8::Value> CompileAndCall(
|
|||
} else if (try_catch.HasTerminated()) {
|
||||
msg = "script execution has been terminated";
|
||||
}
|
||||
LOG(ERROR) << "Failed to CompileAndCall electron script (" << id
|
||||
LOG(ERROR) << "CompileAndCall failed to evaluate electron script (" << id
|
||||
<< "): " << msg;
|
||||
}
|
||||
return ret;
|
||||
|
|
|
@ -288,6 +288,10 @@ inline constexpr base::cstring_view kEnableAuthNegotiatePort =
|
|||
// If set, NTLM v2 is disabled for POSIX platforms.
|
||||
inline constexpr base::cstring_view kDisableNTLMv2 = "disable-ntlm-v2";
|
||||
|
||||
// Indicates that preloads for service workers are registered.
|
||||
inline constexpr base::cstring_view kServiceWorkerPreload =
|
||||
"service-worker-preload";
|
||||
|
||||
} // namespace switches
|
||||
|
||||
} // namespace electron
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/world_ids.h"
|
||||
#include "shell/renderer/preload_realm_context.h"
|
||||
#include "third_party/blink/public/web/web_blob.h"
|
||||
#include "third_party/blink/public/web/web_element.h"
|
||||
#include "third_party/blink/public/web/web_local_frame.h"
|
||||
|
@ -765,6 +766,14 @@ v8::MaybeLocal<v8::Context> GetTargetContext(v8::Isolate* isolate,
|
|||
world_id == WorldIDs::MAIN_WORLD_ID
|
||||
? frame->MainWorldScriptContext()
|
||||
: frame->GetScriptContextFromWorldId(isolate, world_id);
|
||||
} else if (execution_context->IsShadowRealmGlobalScope()) {
|
||||
if (world_id != WorldIDs::MAIN_WORLD_ID) {
|
||||
isolate->ThrowException(v8::Exception::Error(gin::StringToV8(
|
||||
isolate, "Isolated worlds are not supported in preload realms.")));
|
||||
return maybe_target_context;
|
||||
}
|
||||
maybe_target_context =
|
||||
electron::preload_realm::GetInitiatorContext(source_context);
|
||||
} else {
|
||||
NOTREACHED();
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
#include "content/public/renderer/render_frame_observer.h"
|
||||
#include "content/public/renderer/worker_thread.h"
|
||||
#include "gin/dictionary.h"
|
||||
#include "gin/handle.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
|
@ -14,15 +15,20 @@
|
|||
#include "shell/common/api/api.mojom.h"
|
||||
#include "shell/common/gin_converters/blink_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/error_thrower.h"
|
||||
#include "shell/common/gin_helper/function_template_extensions.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/node_bindings.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/v8_util.h"
|
||||
#include "shell/renderer/preload_realm_context.h"
|
||||
#include "shell/renderer/service_worker_data.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
|
||||
#include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_proxy.h"
|
||||
#include "third_party/blink/public/web/web_local_frame.h"
|
||||
#include "third_party/blink/public/web/web_message_port_converter.h"
|
||||
#include "third_party/blink/renderer/core/execution_context/execution_context.h" // nogncheck
|
||||
|
||||
using blink::WebLocalFrame;
|
||||
using content::RenderFrame;
|
||||
|
@ -40,50 +46,23 @@ RenderFrame* GetCurrentRenderFrame() {
|
|||
return RenderFrame::FromWebFrame(frame);
|
||||
}
|
||||
|
||||
class IPCRenderer final : public gin::Wrappable<IPCRenderer>,
|
||||
private content::RenderFrameObserver {
|
||||
// Thread identifier for the main renderer thread (as opposed to a service
|
||||
// worker thread).
|
||||
inline constexpr int kMainThreadId = 0;
|
||||
|
||||
bool IsWorkerThread() {
|
||||
return content::WorkerThread::GetCurrentId() != kMainThreadId;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class IPCBase : public gin::Wrappable<T> {
|
||||
public:
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
|
||||
static gin::Handle<IPCRenderer> Create(v8::Isolate* isolate) {
|
||||
return gin::CreateHandle(isolate, new IPCRenderer(isolate));
|
||||
static gin::Handle<T> Create(v8::Isolate* isolate) {
|
||||
return gin::CreateHandle(isolate, new T(isolate));
|
||||
}
|
||||
|
||||
explicit IPCRenderer(v8::Isolate* isolate)
|
||||
: content::RenderFrameObserver(GetCurrentRenderFrame()) {
|
||||
RenderFrame* render_frame = GetCurrentRenderFrame();
|
||||
DCHECK(render_frame);
|
||||
weak_context_ =
|
||||
v8::Global<v8::Context>(isolate, isolate->GetCurrentContext());
|
||||
weak_context_.SetWeak();
|
||||
|
||||
render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
&electron_ipc_remote_);
|
||||
}
|
||||
|
||||
void OnDestruct() override { electron_ipc_remote_.reset(); }
|
||||
|
||||
void WillReleaseScriptContext(v8::Local<v8::Context> context,
|
||||
int32_t world_id) override {
|
||||
if (weak_context_.IsEmpty() ||
|
||||
weak_context_.Get(context->GetIsolate()) == context)
|
||||
electron_ipc_remote_.reset();
|
||||
}
|
||||
|
||||
// gin::Wrappable:
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override {
|
||||
return gin::Wrappable<IPCRenderer>::GetObjectTemplateBuilder(isolate)
|
||||
.SetMethod("send", &IPCRenderer::SendMessage)
|
||||
.SetMethod("sendSync", &IPCRenderer::SendSync)
|
||||
.SetMethod("sendToHost", &IPCRenderer::SendToHost)
|
||||
.SetMethod("invoke", &IPCRenderer::Invoke)
|
||||
.SetMethod("postMessage", &IPCRenderer::PostMessage);
|
||||
}
|
||||
|
||||
const char* GetTypeName() override { return "IPCRenderer"; }
|
||||
|
||||
private:
|
||||
void SendMessage(v8::Isolate* isolate,
|
||||
gin_helper::ErrorThrower thrower,
|
||||
bool internal,
|
||||
|
@ -202,18 +181,95 @@ class IPCRenderer final : public gin::Wrappable<IPCRenderer>,
|
|||
return electron::DeserializeV8Value(isolate, result);
|
||||
}
|
||||
|
||||
v8::Global<v8::Context> weak_context_;
|
||||
// gin::Wrappable:
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override {
|
||||
return gin::Wrappable<T>::GetObjectTemplateBuilder(isolate)
|
||||
.SetMethod("send", &T::SendMessage)
|
||||
.SetMethod("sendSync", &T::SendSync)
|
||||
.SetMethod("sendToHost", &T::SendToHost)
|
||||
.SetMethod("invoke", &T::Invoke)
|
||||
.SetMethod("postMessage", &T::PostMessage);
|
||||
}
|
||||
|
||||
protected:
|
||||
mojo::AssociatedRemote<electron::mojom::ElectronApiIPC> electron_ipc_remote_;
|
||||
};
|
||||
|
||||
gin::WrapperInfo IPCRenderer::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
class IPCRenderFrame : public IPCBase<IPCRenderFrame>,
|
||||
private content::RenderFrameObserver {
|
||||
public:
|
||||
explicit IPCRenderFrame(v8::Isolate* isolate)
|
||||
: content::RenderFrameObserver(GetCurrentRenderFrame()) {
|
||||
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
||||
blink::ExecutionContext* execution_context =
|
||||
blink::ExecutionContext::From(context);
|
||||
|
||||
if (execution_context->IsWindow()) {
|
||||
RenderFrame* render_frame = GetCurrentRenderFrame();
|
||||
DCHECK(render_frame);
|
||||
render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
&electron_ipc_remote_);
|
||||
} else {
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
weak_context_ =
|
||||
v8::Global<v8::Context>(isolate, isolate->GetCurrentContext());
|
||||
weak_context_.SetWeak();
|
||||
}
|
||||
|
||||
void OnDestruct() override { electron_ipc_remote_.reset(); }
|
||||
|
||||
void WillReleaseScriptContext(v8::Local<v8::Context> context,
|
||||
int32_t world_id) override {
|
||||
if (weak_context_.IsEmpty() ||
|
||||
weak_context_.Get(context->GetIsolate()) == context) {
|
||||
OnDestruct();
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetTypeName() override { return "IPCRenderFrame"; }
|
||||
|
||||
private:
|
||||
v8::Global<v8::Context> weak_context_;
|
||||
};
|
||||
|
||||
template <>
|
||||
gin::WrapperInfo IPCBase<IPCRenderFrame>::kWrapperInfo = {
|
||||
gin::kEmbedderNativeGin};
|
||||
|
||||
class IPCServiceWorker : public IPCBase<IPCServiceWorker>,
|
||||
public content::WorkerThread::Observer {
|
||||
public:
|
||||
explicit IPCServiceWorker(v8::Isolate* isolate) {
|
||||
DCHECK(IsWorkerThread());
|
||||
content::WorkerThread::AddObserver(this);
|
||||
|
||||
electron::ServiceWorkerData* service_worker_data =
|
||||
electron::preload_realm::GetServiceWorkerData(
|
||||
isolate->GetCurrentContext());
|
||||
DCHECK(service_worker_data);
|
||||
service_worker_data->proxy()->GetRemoteAssociatedInterface(
|
||||
electron_ipc_remote_.BindNewEndpointAndPassReceiver());
|
||||
}
|
||||
|
||||
void WillStopCurrentWorkerThread() override { electron_ipc_remote_.reset(); }
|
||||
|
||||
const char* GetTypeName() override { return "IPCServiceWorker"; }
|
||||
};
|
||||
|
||||
template <>
|
||||
gin::WrapperInfo IPCBase<IPCServiceWorker>::kWrapperInfo = {
|
||||
gin::kEmbedderNativeGin};
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv) {
|
||||
gin::Dictionary dict(context->GetIsolate(), exports);
|
||||
dict.Set("ipc", IPCRenderer::Create(context->GetIsolate()));
|
||||
gin_helper::Dictionary dict(context->GetIsolate(), exports);
|
||||
dict.SetMethod("createForRenderFrame", &IPCRenderFrame::Create);
|
||||
dict.SetMethod("createForServiceWorker", &IPCServiceWorker::Create);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "shell/common/options_switches.h"
|
||||
#include "shell/common/thread_restrictions.h"
|
||||
#include "shell/common/v8_util.h"
|
||||
#include "shell/renderer/electron_ipc_native.h"
|
||||
#include "shell/renderer/electron_render_frame_observer.h"
|
||||
#include "shell/renderer/renderer_client_base.h"
|
||||
#include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom-shared.h"
|
||||
|
@ -31,73 +32,6 @@
|
|||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view 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 = gin::StringToV8(isolate, kIpcKey);
|
||||
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();
|
||||
if (value.IsEmpty() || !value->IsObject()) {
|
||||
LOG(ERROR) << "Attempted to get the 'ipcNative' object but it was missing";
|
||||
return {};
|
||||
}
|
||||
return value->ToObject(context).ToLocalChecked();
|
||||
}
|
||||
|
||||
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);
|
||||
if (ipcNative.IsEmpty())
|
||||
return;
|
||||
|
||||
// Only set up the node::CallbackScope if there's a node environment.
|
||||
// Sandboxed renderers don't have a node environment.
|
||||
std::unique_ptr<node::CallbackScope> callback_scope;
|
||||
if (node::Environment::GetCurrent(context)) {
|
||||
callback_scope = std::make_unique<node::CallbackScope>(
|
||||
isolate, ipcNative, node::async_context{0, 0});
|
||||
}
|
||||
|
||||
auto callback_key = gin::ConvertToV8(isolate, callback_name)
|
||||
->ToString(context)
|
||||
.ToLocalChecked();
|
||||
auto callback_value = ipcNative->Get(context, callback_key).ToLocalChecked();
|
||||
DCHECK(callback_value->IsFunction()); // set by init.ts
|
||||
auto callback = callback_value.As<v8::Function>();
|
||||
std::ignore = callback->Call(context, ipcNative, args.size(), args.data());
|
||||
}
|
||||
|
||||
void EmitIPCEvent(v8::Local<v8::Context> context,
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
std::vector<v8::Local<v8::Value>> ports,
|
||||
v8::Local<v8::Value> args) {
|
||||
auto* isolate = context->GetIsolate();
|
||||
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
v8::Context::Scope context_scope(context);
|
||||
v8::MicrotasksScope script_scope(isolate, context->GetMicrotaskQueue(),
|
||||
v8::MicrotasksScope::kRunMicrotasks);
|
||||
|
||||
std::vector<v8::Local<v8::Value>> argv = {
|
||||
gin::ConvertToV8(isolate, internal), gin::ConvertToV8(isolate, channel),
|
||||
gin::ConvertToV8(isolate, ports), args};
|
||||
|
||||
InvokeIpcCallback(context, "onMessage", argv);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ElectronApiServiceImpl::~ElectronApiServiceImpl() = default;
|
||||
|
||||
ElectronApiServiceImpl::ElectronApiServiceImpl(
|
||||
|
@ -166,7 +100,7 @@ void ElectronApiServiceImpl::Message(bool internal,
|
|||
|
||||
v8::Local<v8::Value> args = gin::ConvertToV8(isolate, arguments);
|
||||
|
||||
EmitIPCEvent(context, internal, channel, {}, args);
|
||||
ipc_native::EmitIPCEvent(context, internal, channel, {}, args);
|
||||
}
|
||||
|
||||
void ElectronApiServiceImpl::ReceivePostMessage(
|
||||
|
@ -193,7 +127,8 @@ void ElectronApiServiceImpl::ReceivePostMessage(
|
|||
|
||||
std::vector<v8::Local<v8::Value>> args = {message_value};
|
||||
|
||||
EmitIPCEvent(context, false, channel, ports, gin::ConvertToV8(isolate, args));
|
||||
ipc_native::EmitIPCEvent(context, false, channel, ports,
|
||||
gin::ConvertToV8(isolate, args));
|
||||
}
|
||||
|
||||
void ElectronApiServiceImpl::TakeHeapSnapshot(
|
||||
|
|
84
shell/renderer/electron_ipc_native.cc
Normal file
84
shell/renderer/electron_ipc_native.cc
Normal file
|
@ -0,0 +1,84 @@
|
|||
// 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/shell/renderer/electron_ipc_native.h"
|
||||
|
||||
#include "base/trace_event/trace_event.h"
|
||||
#include "shell/common/gin_converters/blink_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/v8_util.h"
|
||||
#include "third_party/blink/public/web/blink.h"
|
||||
#include "third_party/blink/public/web/web_message_port_converter.h"
|
||||
|
||||
namespace electron::ipc_native {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kIpcKey = "ipcNative";
|
||||
|
||||
// Gets the private object under kIpcKey
|
||||
v8::Local<v8::Object> GetIpcObject(const v8::Local<v8::Context>& context) {
|
||||
auto* isolate = context->GetIsolate();
|
||||
auto binding_key = gin::StringToV8(isolate, kIpcKey);
|
||||
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();
|
||||
if (value.IsEmpty() || !value->IsObject()) {
|
||||
LOG(ERROR) << "Attempted to get the 'ipcNative' object but it was missing";
|
||||
return {};
|
||||
}
|
||||
return value->ToObject(context).ToLocalChecked();
|
||||
}
|
||||
|
||||
void InvokeIpcCallback(const 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);
|
||||
if (ipcNative.IsEmpty())
|
||||
return;
|
||||
|
||||
// Only set up the node::CallbackScope if there's a node environment.
|
||||
// Sandboxed renderers don't have a node environment.
|
||||
std::unique_ptr<node::CallbackScope> callback_scope;
|
||||
if (node::Environment::GetCurrent(context)) {
|
||||
callback_scope = std::make_unique<node::CallbackScope>(
|
||||
isolate, ipcNative, node::async_context{0, 0});
|
||||
}
|
||||
|
||||
auto callback_key = gin::ConvertToV8(isolate, callback_name)
|
||||
->ToString(context)
|
||||
.ToLocalChecked();
|
||||
auto callback_value = ipcNative->Get(context, callback_key).ToLocalChecked();
|
||||
DCHECK(callback_value->IsFunction()); // set by init.ts
|
||||
auto callback = callback_value.As<v8::Function>();
|
||||
std::ignore = callback->Call(context, ipcNative, args.size(), args.data());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void EmitIPCEvent(const v8::Local<v8::Context>& context,
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
std::vector<v8::Local<v8::Value>> ports,
|
||||
v8::Local<v8::Value> args) {
|
||||
auto* isolate = context->GetIsolate();
|
||||
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
v8::Context::Scope context_scope(context);
|
||||
v8::MicrotasksScope script_scope(isolate, context->GetMicrotaskQueue(),
|
||||
v8::MicrotasksScope::kRunMicrotasks);
|
||||
|
||||
std::vector<v8::Local<v8::Value>> argv = {
|
||||
gin::ConvertToV8(isolate, internal), gin::ConvertToV8(isolate, channel),
|
||||
gin::ConvertToV8(isolate, ports), args};
|
||||
|
||||
InvokeIpcCallback(context, "onMessage", argv);
|
||||
}
|
||||
|
||||
} // namespace electron::ipc_native
|
22
shell/renderer/electron_ipc_native.h
Normal file
22
shell/renderer/electron_ipc_native.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
// 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 ELECTRON_SHELL_RENDERER_ELECTRON_IPC_NATIVE_H_
|
||||
#define ELECTRON_SHELL_RENDERER_ELECTRON_IPC_NATIVE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "v8/include/v8-forward.h"
|
||||
|
||||
namespace electron::ipc_native {
|
||||
|
||||
void EmitIPCEvent(const v8::Local<v8::Context>& context,
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
std::vector<v8::Local<v8::Value>> ports,
|
||||
v8::Local<v8::Value> args);
|
||||
|
||||
} // namespace electron::ipc_native
|
||||
|
||||
#endif // ELECTRON_SHELL_RENDERER_ELECTRON_IPC_NATIVE_H_
|
|
@ -11,18 +11,19 @@
|
|||
#include "base/base_paths.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/process/process_handle.h"
|
||||
#include "base/process/process_metrics.h"
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
#include "shell/common/api/electron_bindings.h"
|
||||
#include "shell/common/application_info.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/microtasks_scope.h"
|
||||
#include "shell/common/node_bindings.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/node_util.h"
|
||||
#include "shell/common/options_switches.h"
|
||||
#include "shell/renderer/electron_render_frame_observer.h"
|
||||
#include "shell/renderer/preload_realm_context.h"
|
||||
#include "shell/renderer/preload_utils.h"
|
||||
#include "shell/renderer/service_worker_data.h"
|
||||
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
|
||||
#include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h"
|
||||
#include "third_party/blink/public/web/blink.h"
|
||||
|
@ -33,67 +34,10 @@ namespace electron {
|
|||
|
||||
namespace {
|
||||
|
||||
// Data which only lives on the service worker's thread
|
||||
constinit thread_local ServiceWorkerData* service_worker_data = nullptr;
|
||||
|
||||
constexpr std::string_view kEmitProcessEventKey = "emit-process-event";
|
||||
constexpr std::string_view kBindingCacheKey = "native-binding-cache";
|
||||
|
||||
v8::Local<v8::Object> GetBindingCache(v8::Isolate* isolate) {
|
||||
auto context = isolate->GetCurrentContext();
|
||||
gin_helper::Dictionary global(isolate, context->Global());
|
||||
v8::Local<v8::Value> cache;
|
||||
|
||||
if (!global.GetHidden(kBindingCacheKey, &cache)) {
|
||||
cache = v8::Object::New(isolate);
|
||||
global.SetHidden(kBindingCacheKey, cache);
|
||||
}
|
||||
|
||||
return cache->ToObject(context).ToLocalChecked();
|
||||
}
|
||||
|
||||
// adapted from node.cc
|
||||
v8::Local<v8::Value> GetBinding(v8::Isolate* isolate,
|
||||
v8::Local<v8::String> key,
|
||||
gin_helper::Arguments* margs) {
|
||||
v8::Local<v8::Object> exports;
|
||||
std::string binding_key = gin::V8ToString(isolate, key);
|
||||
gin_helper::Dictionary cache(isolate, GetBindingCache(isolate));
|
||||
|
||||
if (cache.Get(binding_key, &exports)) {
|
||||
return exports;
|
||||
}
|
||||
|
||||
auto* mod = node::binding::get_linked_module(binding_key.c_str());
|
||||
|
||||
if (!mod) {
|
||||
char errmsg[1024];
|
||||
snprintf(errmsg, sizeof(errmsg), "No such binding: %s",
|
||||
binding_key.c_str());
|
||||
margs->ThrowError(errmsg);
|
||||
return exports;
|
||||
}
|
||||
|
||||
exports = v8::Object::New(isolate);
|
||||
DCHECK_EQ(mod->nm_register_func, nullptr);
|
||||
DCHECK_NE(mod->nm_context_register_func, nullptr);
|
||||
mod->nm_context_register_func(exports, v8::Null(isolate),
|
||||
isolate->GetCurrentContext(), mod->nm_priv);
|
||||
cache.Set(binding_key, exports);
|
||||
return exports;
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> CreatePreloadScript(v8::Isolate* isolate,
|
||||
v8::Local<v8::String> source) {
|
||||
auto context = isolate->GetCurrentContext();
|
||||
auto maybe_script = v8::Script::Compile(context, source);
|
||||
v8::Local<v8::Script> script;
|
||||
if (!maybe_script.ToLocal(&script))
|
||||
return {};
|
||||
return script->Run(context).ToLocalChecked();
|
||||
}
|
||||
|
||||
double Uptime() {
|
||||
return (base::Time::Now() - base::Process::Current().CreationTime())
|
||||
.InSecondsF();
|
||||
}
|
||||
|
||||
void InvokeEmitProcessEvent(v8::Local<v8::Context> context,
|
||||
const std::string& event_name) {
|
||||
|
@ -132,8 +76,8 @@ void ElectronSandboxedRendererClient::InitializeBindings(
|
|||
content::RenderFrame* render_frame) {
|
||||
auto* isolate = context->GetIsolate();
|
||||
gin_helper::Dictionary b(isolate, binding);
|
||||
b.SetMethod("get", GetBinding);
|
||||
b.SetMethod("createPreloadScript", CreatePreloadScript);
|
||||
b.SetMethod("get", preload_utils::GetBinding);
|
||||
b.SetMethod("createPreloadScript", preload_utils::CreatePreloadScript);
|
||||
|
||||
auto process = gin_helper::Dictionary::CreateEmpty(isolate);
|
||||
b.Set("process", process);
|
||||
|
@ -141,7 +85,7 @@ void ElectronSandboxedRendererClient::InitializeBindings(
|
|||
ElectronBindings::BindProcess(isolate, &process, metrics_.get());
|
||||
BindProcess(isolate, &process, render_frame);
|
||||
|
||||
process.SetMethod("uptime", Uptime);
|
||||
process.SetMethod("uptime", preload_utils::Uptime);
|
||||
process.Set("argv", base::CommandLine::ForCurrentProcess()->argv());
|
||||
process.SetReadOnly("pid", base::GetCurrentProcId());
|
||||
process.SetReadOnly("sandboxed", true);
|
||||
|
@ -231,4 +175,44 @@ void ElectronSandboxedRendererClient::EmitProcessEvent(
|
|||
InvokeEmitProcessEvent(context, event_name);
|
||||
}
|
||||
|
||||
void ElectronSandboxedRendererClient::WillEvaluateServiceWorkerOnWorkerThread(
|
||||
blink::WebServiceWorkerContextProxy* context_proxy,
|
||||
v8::Local<v8::Context> v8_context,
|
||||
int64_t service_worker_version_id,
|
||||
const GURL& service_worker_scope,
|
||||
const GURL& script_url,
|
||||
const blink::ServiceWorkerToken& service_worker_token) {
|
||||
RendererClientBase::WillEvaluateServiceWorkerOnWorkerThread(
|
||||
context_proxy, v8_context, service_worker_version_id,
|
||||
service_worker_scope, script_url, service_worker_token);
|
||||
|
||||
auto* command_line = base::CommandLine::ForCurrentProcess();
|
||||
if (command_line->HasSwitch(switches::kServiceWorkerPreload)) {
|
||||
if (!service_worker_data) {
|
||||
service_worker_data = new ServiceWorkerData(
|
||||
context_proxy, service_worker_version_id, v8_context);
|
||||
}
|
||||
|
||||
preload_realm::OnCreatePreloadableV8Context(v8_context,
|
||||
service_worker_data);
|
||||
}
|
||||
}
|
||||
|
||||
void ElectronSandboxedRendererClient::
|
||||
WillDestroyServiceWorkerContextOnWorkerThread(
|
||||
v8::Local<v8::Context> context,
|
||||
int64_t service_worker_version_id,
|
||||
const GURL& service_worker_scope,
|
||||
const GURL& script_url) {
|
||||
if (service_worker_data) {
|
||||
DCHECK_EQ(service_worker_version_id,
|
||||
service_worker_data->service_worker_version_id());
|
||||
delete service_worker_data;
|
||||
service_worker_data = nullptr;
|
||||
}
|
||||
|
||||
RendererClientBase::WillDestroyServiceWorkerContextOnWorkerThread(
|
||||
context, service_worker_version_id, service_worker_scope, script_url);
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
|
|
@ -42,6 +42,18 @@ class ElectronSandboxedRendererClient : public RendererClientBase {
|
|||
void RenderFrameCreated(content::RenderFrame*) override;
|
||||
void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
|
||||
void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override;
|
||||
void WillEvaluateServiceWorkerOnWorkerThread(
|
||||
blink::WebServiceWorkerContextProxy* context_proxy,
|
||||
v8::Local<v8::Context> v8_context,
|
||||
int64_t service_worker_version_id,
|
||||
const GURL& service_worker_scope,
|
||||
const GURL& script_url,
|
||||
const blink::ServiceWorkerToken& service_worker_token) override;
|
||||
void WillDestroyServiceWorkerContextOnWorkerThread(
|
||||
v8::Local<v8::Context> context,
|
||||
int64_t service_worker_version_id,
|
||||
const GURL& service_worker_scope,
|
||||
const GURL& script_url) override;
|
||||
|
||||
private:
|
||||
void EmitProcessEvent(content::RenderFrame* render_frame,
|
||||
|
|
295
shell/renderer/preload_realm_context.cc
Normal file
295
shell/renderer/preload_realm_context.cc
Normal file
|
@ -0,0 +1,295 @@
|
|||
// Copyright (c) 2025 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/renderer/preload_realm_context.h"
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/process/process.h"
|
||||
#include "base/process/process_metrics.h"
|
||||
#include "shell/common/api/electron_bindings.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/node_util.h"
|
||||
#include "shell/renderer/preload_utils.h"
|
||||
#include "shell/renderer/service_worker_data.h"
|
||||
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h" // nogncheck
|
||||
#include "third_party/blink/renderer/core/execution_context/execution_context.h" // nogncheck
|
||||
#include "third_party/blink/renderer/core/inspector/worker_thread_debugger.h" // nogncheck
|
||||
#include "third_party/blink/renderer/core/shadow_realm/shadow_realm_global_scope.h" // nogncheck
|
||||
#include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h" // nogncheck
|
||||
#include "third_party/blink/renderer/platform/bindings/script_state.h" // nogncheck
|
||||
#include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h" // nogncheck
|
||||
#include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h" // nogncheck
|
||||
#include "third_party/blink/renderer/platform/context_lifecycle_observer.h" // nogncheck
|
||||
#include "v8/include/v8-context.h"
|
||||
|
||||
namespace electron::preload_realm {
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr int kElectronContextEmbedderDataIndex =
|
||||
static_cast<int>(gin::kPerContextDataStartIndex) +
|
||||
static_cast<int>(gin::kEmbedderElectron);
|
||||
|
||||
// This is a helper class to make the initiator ExecutionContext the owner
|
||||
// of a ShadowRealmGlobalScope and its ScriptState. When the initiator
|
||||
// ExecutionContext is destroyed, the ShadowRealmGlobalScope is destroyed,
|
||||
// too.
|
||||
class PreloadRealmLifetimeController
|
||||
: public blink::GarbageCollected<PreloadRealmLifetimeController>,
|
||||
public blink::ContextLifecycleObserver {
|
||||
public:
|
||||
explicit PreloadRealmLifetimeController(
|
||||
blink::ExecutionContext* initiator_execution_context,
|
||||
blink::ScriptState* initiator_script_state,
|
||||
blink::ShadowRealmGlobalScope* shadow_realm_global_scope,
|
||||
blink::ScriptState* shadow_realm_script_state,
|
||||
electron::ServiceWorkerData* service_worker_data)
|
||||
: initiator_script_state_(initiator_script_state),
|
||||
is_initiator_worker_or_worklet_(
|
||||
initiator_execution_context->IsWorkerOrWorkletGlobalScope()),
|
||||
shadow_realm_global_scope_(shadow_realm_global_scope),
|
||||
shadow_realm_script_state_(shadow_realm_script_state),
|
||||
service_worker_data_(service_worker_data) {
|
||||
// Align lifetime of this controller to that of the initiator's context.
|
||||
self_ = this;
|
||||
|
||||
SetContextLifecycleNotifier(initiator_execution_context);
|
||||
RegisterDebugger(initiator_execution_context);
|
||||
|
||||
initiator_context()->SetAlignedPointerInEmbedderData(
|
||||
kElectronContextEmbedderDataIndex, static_cast<void*>(this));
|
||||
realm_context()->SetAlignedPointerInEmbedderData(
|
||||
kElectronContextEmbedderDataIndex, static_cast<void*>(this));
|
||||
|
||||
metrics_ = base::ProcessMetrics::CreateCurrentProcessMetrics();
|
||||
RunInitScript();
|
||||
}
|
||||
|
||||
static PreloadRealmLifetimeController* From(v8::Local<v8::Context> context) {
|
||||
if (context->GetNumberOfEmbedderDataFields() <=
|
||||
kElectronContextEmbedderDataIndex) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* controller = static_cast<PreloadRealmLifetimeController*>(
|
||||
context->GetAlignedPointerFromEmbedderData(
|
||||
kElectronContextEmbedderDataIndex));
|
||||
CHECK(controller);
|
||||
return controller;
|
||||
}
|
||||
|
||||
void Trace(blink::Visitor* visitor) const override {
|
||||
visitor->Trace(initiator_script_state_);
|
||||
visitor->Trace(shadow_realm_global_scope_);
|
||||
visitor->Trace(shadow_realm_script_state_);
|
||||
ContextLifecycleObserver::Trace(visitor);
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Context> GetContext() {
|
||||
return shadow_realm_script_state_->ContextIsValid()
|
||||
? shadow_realm_script_state_->GetContext()
|
||||
: v8::MaybeLocal<v8::Context>();
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Context> GetInitiatorContext() {
|
||||
return initiator_script_state_->ContextIsValid()
|
||||
? initiator_script_state_->GetContext()
|
||||
: v8::MaybeLocal<v8::Context>();
|
||||
}
|
||||
|
||||
electron::ServiceWorkerData* service_worker_data() {
|
||||
return service_worker_data_;
|
||||
}
|
||||
|
||||
protected:
|
||||
void ContextDestroyed() override {
|
||||
v8::HandleScope handle_scope(realm_isolate());
|
||||
realm_context()->SetAlignedPointerInEmbedderData(
|
||||
kElectronContextEmbedderDataIndex, nullptr);
|
||||
|
||||
// See ShadowRealmGlobalScope::ContextDestroyed
|
||||
shadow_realm_script_state_->DisposePerContextData();
|
||||
if (is_initiator_worker_or_worklet_) {
|
||||
shadow_realm_script_state_->DissociateContext();
|
||||
}
|
||||
shadow_realm_script_state_.Clear();
|
||||
shadow_realm_global_scope_->NotifyContextDestroyed();
|
||||
shadow_realm_global_scope_.Clear();
|
||||
|
||||
self_.Clear();
|
||||
}
|
||||
|
||||
private:
|
||||
v8::Isolate* realm_isolate() {
|
||||
return shadow_realm_script_state_->GetIsolate();
|
||||
}
|
||||
v8::Local<v8::Context> realm_context() {
|
||||
return shadow_realm_script_state_->GetContext();
|
||||
}
|
||||
v8::Local<v8::Context> initiator_context() {
|
||||
return initiator_script_state_->GetContext();
|
||||
}
|
||||
|
||||
void RegisterDebugger(blink::ExecutionContext* initiator_execution_context) {
|
||||
v8::Isolate* isolate = realm_isolate();
|
||||
v8::Local<v8::Context> context = realm_context();
|
||||
|
||||
blink::WorkerThreadDebugger* debugger =
|
||||
blink::WorkerThreadDebugger::From(isolate);
|
||||
;
|
||||
const auto* worker_context =
|
||||
To<blink::WorkerOrWorkletGlobalScope>(initiator_execution_context);
|
||||
|
||||
// Override path to make preload realm easier to find in debugger.
|
||||
blink::KURL url_for_debugger(worker_context->Url());
|
||||
url_for_debugger.SetPath("electron-preload-realm");
|
||||
|
||||
debugger->ContextCreated(worker_context->GetThread(), url_for_debugger,
|
||||
context);
|
||||
}
|
||||
|
||||
void RunInitScript() {
|
||||
v8::Isolate* isolate = realm_isolate();
|
||||
v8::Local<v8::Context> context = realm_context();
|
||||
|
||||
v8::Context::Scope context_scope(context);
|
||||
v8::MicrotasksScope microtasks_scope(
|
||||
isolate, context->GetMicrotaskQueue(),
|
||||
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
||||
|
||||
v8::Local<v8::Object> binding = v8::Object::New(isolate);
|
||||
|
||||
gin_helper::Dictionary b(isolate, binding);
|
||||
b.SetMethod("get", preload_utils::GetBinding);
|
||||
b.SetMethod("createPreloadScript", preload_utils::CreatePreloadScript);
|
||||
|
||||
gin_helper::Dictionary process = gin::Dictionary::CreateEmpty(isolate);
|
||||
b.Set("process", process);
|
||||
|
||||
ElectronBindings::BindProcess(isolate, &process, metrics_.get());
|
||||
|
||||
process.SetMethod("uptime", preload_utils::Uptime);
|
||||
process.Set("argv", base::CommandLine::ForCurrentProcess()->argv());
|
||||
process.SetReadOnly("pid", base::GetCurrentProcId());
|
||||
process.SetReadOnly("sandboxed", true);
|
||||
process.SetReadOnly("type", "service-worker");
|
||||
process.SetReadOnly("contextIsolated", true);
|
||||
|
||||
std::vector<v8::Local<v8::String>> preload_realm_bundle_params = {
|
||||
node::FIXED_ONE_BYTE_STRING(isolate, "binding")};
|
||||
|
||||
std::vector<v8::Local<v8::Value>> preload_realm_bundle_args = {binding};
|
||||
|
||||
util::CompileAndCall(context, "electron/js2c/preload_realm_bundle",
|
||||
&preload_realm_bundle_params,
|
||||
&preload_realm_bundle_args);
|
||||
}
|
||||
|
||||
const blink::WeakMember<blink::ScriptState> initiator_script_state_;
|
||||
bool is_initiator_worker_or_worklet_;
|
||||
blink::Member<blink::ShadowRealmGlobalScope> shadow_realm_global_scope_;
|
||||
blink::Member<blink::ScriptState> shadow_realm_script_state_;
|
||||
|
||||
std::unique_ptr<base::ProcessMetrics> metrics_;
|
||||
raw_ptr<ServiceWorkerData> service_worker_data_;
|
||||
|
||||
blink::Persistent<PreloadRealmLifetimeController> self_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
v8::MaybeLocal<v8::Context> GetInitiatorContext(
|
||||
v8::Local<v8::Context> context) {
|
||||
DCHECK(!context.IsEmpty());
|
||||
blink::ExecutionContext* execution_context =
|
||||
blink::ExecutionContext::From(context);
|
||||
if (!execution_context->IsShadowRealmGlobalScope())
|
||||
return v8::MaybeLocal<v8::Context>();
|
||||
auto* controller = PreloadRealmLifetimeController::From(context);
|
||||
if (controller)
|
||||
return controller->GetInitiatorContext();
|
||||
return v8::MaybeLocal<v8::Context>();
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Context> GetPreloadRealmContext(
|
||||
v8::Local<v8::Context> context) {
|
||||
DCHECK(!context.IsEmpty());
|
||||
blink::ExecutionContext* execution_context =
|
||||
blink::ExecutionContext::From(context);
|
||||
if (!execution_context->IsServiceWorkerGlobalScope())
|
||||
return v8::MaybeLocal<v8::Context>();
|
||||
auto* controller = PreloadRealmLifetimeController::From(context);
|
||||
if (controller)
|
||||
return controller->GetContext();
|
||||
return v8::MaybeLocal<v8::Context>();
|
||||
}
|
||||
|
||||
electron::ServiceWorkerData* GetServiceWorkerData(
|
||||
v8::Local<v8::Context> context) {
|
||||
auto* controller = PreloadRealmLifetimeController::From(context);
|
||||
return controller ? controller->service_worker_data() : nullptr;
|
||||
}
|
||||
|
||||
void OnCreatePreloadableV8Context(
|
||||
v8::Local<v8::Context> initiator_context,
|
||||
electron::ServiceWorkerData* service_worker_data) {
|
||||
v8::Isolate* isolate = initiator_context->GetIsolate();
|
||||
blink::ScriptState* initiator_script_state =
|
||||
blink::ScriptState::MaybeFrom(isolate, initiator_context);
|
||||
DCHECK(initiator_script_state);
|
||||
blink::ExecutionContext* initiator_execution_context =
|
||||
blink::ExecutionContext::From(initiator_context);
|
||||
DCHECK(initiator_execution_context);
|
||||
blink::DOMWrapperWorld* world = blink::DOMWrapperWorld::Create(
|
||||
isolate, blink::DOMWrapperWorld::WorldType::kShadowRealm);
|
||||
CHECK(world); // Not yet run out of the world id.
|
||||
|
||||
// Create a new ShadowRealmGlobalScope.
|
||||
blink::ShadowRealmGlobalScope* shadow_realm_global_scope =
|
||||
blink::MakeGarbageCollected<blink::ShadowRealmGlobalScope>(
|
||||
initiator_execution_context);
|
||||
const blink::WrapperTypeInfo* wrapper_type_info =
|
||||
shadow_realm_global_scope->GetWrapperTypeInfo();
|
||||
|
||||
// Create a new v8::Context.
|
||||
// Initialize V8 extensions before creating the context.
|
||||
v8::ExtensionConfiguration extension_configuration =
|
||||
blink::ScriptController::ExtensionsFor(shadow_realm_global_scope);
|
||||
|
||||
v8::Local<v8::ObjectTemplate> global_template =
|
||||
wrapper_type_info->GetV8ClassTemplate(isolate, *world)
|
||||
.As<v8::FunctionTemplate>()
|
||||
->InstanceTemplate();
|
||||
v8::Local<v8::Object> global_proxy; // Will request a new global proxy.
|
||||
v8::Local<v8::Context> context =
|
||||
v8::Context::New(isolate, &extension_configuration, global_template,
|
||||
global_proxy, v8::DeserializeInternalFieldsCallback(),
|
||||
initiator_execution_context->GetMicrotaskQueue());
|
||||
context->UseDefaultSecurityToken();
|
||||
|
||||
// Associate the Blink object with the v8::Context.
|
||||
blink::ScriptState* script_state =
|
||||
blink::ScriptState::Create(context, world, shadow_realm_global_scope);
|
||||
|
||||
// Associate the Blink object with the v8::Objects.
|
||||
global_proxy = context->Global();
|
||||
blink::V8DOMWrapper::SetNativeInfo(isolate, global_proxy,
|
||||
shadow_realm_global_scope);
|
||||
v8::Local<v8::Object> global_object =
|
||||
global_proxy->GetPrototype().As<v8::Object>();
|
||||
blink::V8DOMWrapper::SetNativeInfo(isolate, global_object,
|
||||
shadow_realm_global_scope);
|
||||
|
||||
// Install context-dependent properties.
|
||||
std::ignore =
|
||||
script_state->PerContextData()->ConstructorForType(wrapper_type_info);
|
||||
|
||||
// Make the initiator execution context the owner of the
|
||||
// ShadowRealmGlobalScope and the ScriptState.
|
||||
blink::MakeGarbageCollected<PreloadRealmLifetimeController>(
|
||||
initiator_execution_context, initiator_script_state,
|
||||
shadow_realm_global_scope, script_state, service_worker_data);
|
||||
}
|
||||
|
||||
} // namespace electron::preload_realm
|
34
shell/renderer/preload_realm_context.h
Normal file
34
shell/renderer/preload_realm_context.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2025 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_RENDERER_PRELOAD_REALM_CONTEXT_H_
|
||||
#define ELECTRON_SHELL_RENDERER_PRELOAD_REALM_CONTEXT_H_
|
||||
|
||||
#include "v8/include/v8-forward.h"
|
||||
|
||||
namespace electron {
|
||||
class ServiceWorkerData;
|
||||
}
|
||||
|
||||
namespace electron::preload_realm {
|
||||
|
||||
// Get initiator context given the preload context.
|
||||
v8::MaybeLocal<v8::Context> GetInitiatorContext(v8::Local<v8::Context> context);
|
||||
|
||||
// Get the preload context given the initiator context.
|
||||
v8::MaybeLocal<v8::Context> GetPreloadRealmContext(
|
||||
v8::Local<v8::Context> context);
|
||||
|
||||
// Get service worker data given the preload realm context.
|
||||
electron::ServiceWorkerData* GetServiceWorkerData(
|
||||
v8::Local<v8::Context> context);
|
||||
|
||||
// Create
|
||||
void OnCreatePreloadableV8Context(
|
||||
v8::Local<v8::Context> initiator_context,
|
||||
electron::ServiceWorkerData* service_worker_data);
|
||||
|
||||
} // namespace electron::preload_realm
|
||||
|
||||
#endif // ELECTRON_SHELL_RENDERER_PRELOAD_REALM_CONTEXT_H_
|
80
shell/renderer/preload_utils.cc
Normal file
80
shell/renderer/preload_utils.cc
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/renderer/preload_utils.h"
|
||||
|
||||
#include "base/process/process.h"
|
||||
#include "shell/common/gin_helper/arguments.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "v8/include/v8-context.h"
|
||||
|
||||
namespace electron::preload_utils {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kBindingCacheKey = "native-binding-cache";
|
||||
|
||||
v8::Local<v8::Object> GetBindingCache(v8::Isolate* isolate) {
|
||||
auto context = isolate->GetCurrentContext();
|
||||
gin_helper::Dictionary global(isolate, context->Global());
|
||||
v8::Local<v8::Value> cache;
|
||||
|
||||
if (!global.GetHidden(kBindingCacheKey, &cache)) {
|
||||
cache = v8::Object::New(isolate);
|
||||
global.SetHidden(kBindingCacheKey, cache);
|
||||
}
|
||||
|
||||
return cache->ToObject(context).ToLocalChecked();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// adapted from node.cc
|
||||
v8::Local<v8::Value> GetBinding(v8::Isolate* isolate,
|
||||
v8::Local<v8::String> key,
|
||||
gin_helper::Arguments* margs) {
|
||||
v8::Local<v8::Object> exports;
|
||||
std::string binding_key = gin::V8ToString(isolate, key);
|
||||
gin_helper::Dictionary cache(isolate, GetBindingCache(isolate));
|
||||
|
||||
if (cache.Get(binding_key, &exports)) {
|
||||
return exports;
|
||||
}
|
||||
|
||||
auto* mod = node::binding::get_linked_module(binding_key.c_str());
|
||||
|
||||
if (!mod) {
|
||||
char errmsg[1024];
|
||||
snprintf(errmsg, sizeof(errmsg), "No such binding: %s",
|
||||
binding_key.c_str());
|
||||
margs->ThrowError(errmsg);
|
||||
return exports;
|
||||
}
|
||||
|
||||
exports = v8::Object::New(isolate);
|
||||
DCHECK_EQ(mod->nm_register_func, nullptr);
|
||||
DCHECK_NE(mod->nm_context_register_func, nullptr);
|
||||
mod->nm_context_register_func(exports, v8::Null(isolate),
|
||||
isolate->GetCurrentContext(), mod->nm_priv);
|
||||
cache.Set(binding_key, exports);
|
||||
return exports;
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> CreatePreloadScript(v8::Isolate* isolate,
|
||||
v8::Local<v8::String> source) {
|
||||
auto context = isolate->GetCurrentContext();
|
||||
auto maybe_script = v8::Script::Compile(context, source);
|
||||
v8::Local<v8::Script> script;
|
||||
if (!maybe_script.ToLocal(&script))
|
||||
return {};
|
||||
return script->Run(context).ToLocalChecked();
|
||||
}
|
||||
|
||||
double Uptime() {
|
||||
return (base::Time::Now() - base::Process::Current().CreationTime())
|
||||
.InSecondsF();
|
||||
}
|
||||
|
||||
} // namespace electron::preload_utils
|
27
shell/renderer/preload_utils.h
Normal file
27
shell/renderer/preload_utils.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_RENDERER_PRELOAD_UTILS_H_
|
||||
#define ELECTRON_SHELL_RENDERER_PRELOAD_UTILS_H_
|
||||
|
||||
#include "v8/include/v8-forward.h"
|
||||
|
||||
namespace gin_helper {
|
||||
class Arguments;
|
||||
}
|
||||
|
||||
namespace electron::preload_utils {
|
||||
|
||||
v8::Local<v8::Value> GetBinding(v8::Isolate* isolate,
|
||||
v8::Local<v8::String> key,
|
||||
gin_helper::Arguments* margs);
|
||||
|
||||
v8::Local<v8::Value> CreatePreloadScript(v8::Isolate* isolate,
|
||||
v8::Local<v8::String> source);
|
||||
|
||||
double Uptime();
|
||||
|
||||
} // namespace electron::preload_utils
|
||||
|
||||
#endif // ELECTRON_SHELL_RENDERER_PRELOAD_UTILS_H_
|
72
shell/renderer/service_worker_data.cc
Normal file
72
shell/renderer/service_worker_data.cc
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 2025 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "electron/shell/renderer/service_worker_data.h"
|
||||
|
||||
#include "shell/common/gin_converters/blink_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/heap_snapshot.h"
|
||||
#include "shell/renderer/electron_ipc_native.h"
|
||||
#include "shell/renderer/preload_realm_context.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
ServiceWorkerData::~ServiceWorkerData() = default;
|
||||
|
||||
ServiceWorkerData::ServiceWorkerData(blink::WebServiceWorkerContextProxy* proxy,
|
||||
int64_t service_worker_version_id,
|
||||
const v8::Local<v8::Context>& v8_context)
|
||||
: proxy_(proxy),
|
||||
service_worker_version_id_(service_worker_version_id),
|
||||
isolate_(v8_context->GetIsolate()),
|
||||
v8_context_(v8_context->GetIsolate(), v8_context) {
|
||||
proxy_->GetAssociatedInterfaceRegistry()
|
||||
.AddInterface<mojom::ElectronRenderer>(
|
||||
base::BindRepeating(&ServiceWorkerData::OnElectronRendererRequest,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void ServiceWorkerData::OnElectronRendererRequest(
|
||||
mojo::PendingAssociatedReceiver<mojom::ElectronRenderer> receiver) {
|
||||
receiver_.reset();
|
||||
receiver_.Bind(std::move(receiver));
|
||||
}
|
||||
|
||||
void ServiceWorkerData::Message(bool internal,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments) {
|
||||
v8::Isolate* isolate = isolate_.get();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
v8::Local<v8::Context> context = v8_context_.Get(isolate_);
|
||||
|
||||
v8::MaybeLocal<v8::Context> maybe_preload_context =
|
||||
preload_realm::GetPreloadRealmContext(context);
|
||||
|
||||
if (maybe_preload_context.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
v8::Local<v8::Context> preload_context =
|
||||
maybe_preload_context.ToLocalChecked();
|
||||
v8::Context::Scope context_scope(preload_context);
|
||||
|
||||
v8::Local<v8::Value> args = gin::ConvertToV8(isolate, arguments);
|
||||
|
||||
ipc_native::EmitIPCEvent(preload_context, internal, channel, {}, args);
|
||||
}
|
||||
|
||||
void ServiceWorkerData::ReceivePostMessage(const std::string& channel,
|
||||
blink::TransferableMessage message) {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
void ServiceWorkerData::TakeHeapSnapshot(mojo::ScopedHandle file,
|
||||
TakeHeapSnapshotCallback callback) {
|
||||
NOTIMPLEMENTED();
|
||||
std::move(callback).Run(false);
|
||||
}
|
||||
|
||||
} // namespace electron
|
68
shell/renderer/service_worker_data.h
Normal file
68
shell/renderer/service_worker_data.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) 2025 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_RENDERER_SERVICE_WORKER_DATA_H_
|
||||
#define ELECTRON_SHELL_RENDERER_SERVICE_WORKER_DATA_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "electron/shell/common/api/api.mojom.h"
|
||||
#include "mojo/public/cpp/bindings/associated_receiver.h"
|
||||
#include "mojo/public/cpp/bindings/associated_remote.h"
|
||||
#include "mojo/public/cpp/bindings/pending_receiver.h"
|
||||
#include "mojo/public/cpp/bindings/receiver.h"
|
||||
#include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_proxy.h"
|
||||
#include "v8/include/v8-context.h"
|
||||
#include "v8/include/v8-forward.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
// Per ServiceWorker data in worker thread.
|
||||
class ServiceWorkerData : public mojom::ElectronRenderer {
|
||||
public:
|
||||
ServiceWorkerData(blink::WebServiceWorkerContextProxy* proxy,
|
||||
int64_t service_worker_version_id,
|
||||
const v8::Local<v8::Context>& v8_context);
|
||||
~ServiceWorkerData() override;
|
||||
|
||||
// disable copy
|
||||
ServiceWorkerData(const ServiceWorkerData&) = delete;
|
||||
ServiceWorkerData& operator=(const ServiceWorkerData&) = delete;
|
||||
|
||||
int64_t service_worker_version_id() const {
|
||||
return service_worker_version_id_;
|
||||
}
|
||||
|
||||
blink::WebServiceWorkerContextProxy* proxy() const { return proxy_; }
|
||||
|
||||
// mojom::ElectronRenderer
|
||||
void Message(bool internal,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments) override;
|
||||
void ReceivePostMessage(const std::string& channel,
|
||||
blink::TransferableMessage message) override;
|
||||
void TakeHeapSnapshot(mojo::ScopedHandle file,
|
||||
TakeHeapSnapshotCallback callback) override;
|
||||
|
||||
private:
|
||||
void OnElectronRendererRequest(
|
||||
mojo::PendingAssociatedReceiver<mojom::ElectronRenderer> receiver);
|
||||
|
||||
raw_ptr<blink::WebServiceWorkerContextProxy> proxy_;
|
||||
const int64_t service_worker_version_id_;
|
||||
|
||||
// The v8 context the bindings are accessible to.
|
||||
raw_ptr<v8::Isolate> isolate_;
|
||||
v8::Global<v8::Context> v8_context_;
|
||||
|
||||
mojo::AssociatedReceiver<mojom::ElectronRenderer> receiver_{this};
|
||||
|
||||
base::WeakPtrFactory<ServiceWorkerData> weak_ptr_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_RENDERER_SERVICE_WORKER_DATA_H_
|
Loading…
Add table
Add a link
Reference in a new issue