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:
Sam Maddock 2025-01-31 09:32:45 -05:00 committed by GitHub
parent bc22ee7897
commit 26da3c5d6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 2103 additions and 298 deletions

View file

@ -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,

View 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

View 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_

View file

@ -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;

View file

@ -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