mirror of https://github.com/electron/electron
296 lines
12 KiB
C++
296 lines
12 KiB
C++
// Copyright (c) 2024 Samuel Maddock <sam@samuelmaddock.com>.
|
|
// 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
|