in shell/renderer/api/electron_api_context_bridge.cc [136:387]
v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Local<v8::Context> source_context,
v8::Local<v8::Context> destination_context,
v8::Local<v8::Value> value,
context_bridge::ObjectCache* object_cache,
bool support_dynamic_properties,
int recursion_depth,
BridgeErrorTarget error_target) {
TRACE_EVENT0("electron", "ContextBridge::PassValueToOtherContext");
if (recursion_depth >= kMaxRecursion) {
v8::Context::Scope error_scope(error_target == BridgeErrorTarget::kSource
? source_context
: destination_context);
source_context->GetIsolate()->ThrowException(v8::Exception::TypeError(
gin::StringToV8(source_context->GetIsolate(),
"Electron contextBridge recursion depth exceeded. "
"Nested objects "
"deeper than 1000 are not supported.")));
return v8::MaybeLocal<v8::Value>();
}
// Certain primitives always use the current contexts prototype and we can
// pass these through directly which is significantly more performant than
// copying them. This list of primitives is based on the classification of
// "primitive value" as defined in the ECMA262 spec
// https://tc39.es/ecma262/#sec-primitive-value
if (value->IsString() || value->IsNumber() || value->IsNullOrUndefined() ||
value->IsBoolean() || value->IsSymbol() || value->IsBigInt()) {
return v8::MaybeLocal<v8::Value>(value);
}
// Check Cache
auto cached_value = object_cache->GetCachedProxiedObject(value);
if (!cached_value.IsEmpty()) {
return cached_value;
}
// Proxy functions and monitor the lifetime in the new context to release
// the global handle at the right time.
if (value->IsFunction()) {
auto func = value.As<v8::Function>();
v8::MaybeLocal<v8::Value> maybe_original_fn = GetPrivate(
source_context, func, context_bridge::kOriginalFunctionPrivateKey);
{
v8::Context::Scope destination_scope(destination_context);
v8::Local<v8::Value> proxy_func;
// If this function has already been sent over the bridge,
// then it is being sent _back_ over the bridge and we can
// simply return the original method here for performance reasons
// For safety reasons we check if the destination context is the
// creation context of the original method. If it's not we proceed
// with the proxy logic
if (maybe_original_fn.ToLocal(&proxy_func) && proxy_func->IsFunction() &&
proxy_func.As<v8::Object>()->CreationContext() ==
destination_context) {
return v8::MaybeLocal<v8::Value>(proxy_func);
}
v8::Local<v8::Object> state =
v8::Object::New(destination_context->GetIsolate());
SetPrivate(destination_context, state,
context_bridge::kProxyFunctionPrivateKey, func);
SetPrivate(destination_context, state,
context_bridge::kSupportsDynamicPropertiesPrivateKey,
gin::ConvertToV8(destination_context->GetIsolate(),
support_dynamic_properties));
if (!v8::Function::New(destination_context, ProxyFunctionWrapper, state)
.ToLocal(&proxy_func))
return v8::MaybeLocal<v8::Value>();
SetPrivate(destination_context, proxy_func.As<v8::Object>(),
context_bridge::kOriginalFunctionPrivateKey, func);
object_cache->CacheProxiedObject(value, proxy_func);
return v8::MaybeLocal<v8::Value>(proxy_func);
}
}
// Proxy promises as they have a safe and guaranteed memory lifecycle
if (value->IsPromise()) {
v8::Context::Scope destination_scope(destination_context);
auto source_promise = value.As<v8::Promise>();
// Make the promise a shared_ptr so that when the original promise is
// freed the proxy promise is correctly freed as well instead of being
// left dangling
auto proxied_promise =
std::make_shared<gin_helper::Promise<v8::Local<v8::Value>>>(
destination_context->GetIsolate());
v8::Local<v8::Promise> proxied_promise_handle =
proxied_promise->GetHandle();
v8::Global<v8::Context> global_then_source_context(
source_context->GetIsolate(), source_context);
v8::Global<v8::Context> global_then_destination_context(
destination_context->GetIsolate(), destination_context);
global_then_source_context.SetWeak();
global_then_destination_context.SetWeak();
auto then_cb = base::BindOnce(
[](std::shared_ptr<gin_helper::Promise<v8::Local<v8::Value>>>
proxied_promise,
v8::Isolate* isolate, v8::Global<v8::Context> global_source_context,
v8::Global<v8::Context> global_destination_context,
v8::Local<v8::Value> result) {
if (global_source_context.IsEmpty() ||
global_destination_context.IsEmpty())
return;
context_bridge::ObjectCache object_cache;
auto val =
PassValueToOtherContext(global_source_context.Get(isolate),
global_destination_context.Get(isolate),
result, &object_cache, false, 0);
if (!val.IsEmpty())
proxied_promise->Resolve(val.ToLocalChecked());
},
proxied_promise, destination_context->GetIsolate(),
std::move(global_then_source_context),
std::move(global_then_destination_context));
v8::Global<v8::Context> global_catch_source_context(
source_context->GetIsolate(), source_context);
v8::Global<v8::Context> global_catch_destination_context(
destination_context->GetIsolate(), destination_context);
global_catch_source_context.SetWeak();
global_catch_destination_context.SetWeak();
auto catch_cb = base::BindOnce(
[](std::shared_ptr<gin_helper::Promise<v8::Local<v8::Value>>>
proxied_promise,
v8::Isolate* isolate, v8::Global<v8::Context> global_source_context,
v8::Global<v8::Context> global_destination_context,
v8::Local<v8::Value> result) {
if (global_source_context.IsEmpty() ||
global_destination_context.IsEmpty())
return;
context_bridge::ObjectCache object_cache;
auto val =
PassValueToOtherContext(global_source_context.Get(isolate),
global_destination_context.Get(isolate),
result, &object_cache, false, 0);
if (!val.IsEmpty())
proxied_promise->Reject(val.ToLocalChecked());
},
proxied_promise, destination_context->GetIsolate(),
std::move(global_catch_source_context),
std::move(global_catch_destination_context));
ignore_result(source_promise->Then(
source_context,
gin::ConvertToV8(destination_context->GetIsolate(), std::move(then_cb))
.As<v8::Function>(),
gin::ConvertToV8(destination_context->GetIsolate(), std::move(catch_cb))
.As<v8::Function>()));
object_cache->CacheProxiedObject(value, proxied_promise_handle);
return v8::MaybeLocal<v8::Value>(proxied_promise_handle);
}
// Errors aren't serializable currently, we need to pull the message out and
// re-construct in the destination context
if (value->IsNativeError()) {
v8::Context::Scope destination_context_scope(destination_context);
// We should try to pull "message" straight off of the error as a
// v8::Message includes some pretext that can get duplicated each time it
// crosses the bridge we fallback to the v8::Message approach if we can't
// pull "message" for some reason
v8::MaybeLocal<v8::Value> maybe_message = value.As<v8::Object>()->Get(
source_context,
gin::ConvertToV8(source_context->GetIsolate(), "message"));
v8::Local<v8::Value> message;
if (maybe_message.ToLocal(&message) && message->IsString()) {
return v8::MaybeLocal<v8::Value>(
v8::Exception::Error(message.As<v8::String>()));
}
return v8::MaybeLocal<v8::Value>(v8::Exception::Error(
v8::Exception::CreateMessage(destination_context->GetIsolate(), value)
->Get()));
}
// Manually go through the array and pass each value individually into a new
// array so that functions deep inside arrays get proxied or arrays of
// promises are proxied correctly.
if (IsPlainArray(value)) {
v8::Context::Scope destination_context_scope(destination_context);
v8::Local<v8::Array> arr = value.As<v8::Array>();
size_t length = arr->Length();
v8::Local<v8::Array> cloned_arr =
v8::Array::New(destination_context->GetIsolate(), length);
for (size_t i = 0; i < length; i++) {
auto value_for_array = PassValueToOtherContext(
source_context, destination_context,
arr->Get(source_context, i).ToLocalChecked(), object_cache,
support_dynamic_properties, recursion_depth + 1);
if (value_for_array.IsEmpty())
return v8::MaybeLocal<v8::Value>();
if (!IsTrue(cloned_arr->Set(destination_context, static_cast<int>(i),
value_for_array.ToLocalChecked()))) {
return v8::MaybeLocal<v8::Value>();
}
}
object_cache->CacheProxiedObject(value, cloned_arr);
return v8::MaybeLocal<v8::Value>(cloned_arr);
}
// Custom logic to "clone" Element references
blink::WebElement elem = blink::WebElement::FromV8Value(value);
if (!elem.IsNull()) {
v8::Context::Scope destination_context_scope(destination_context);
return v8::MaybeLocal<v8::Value>(elem.ToV8Value(
destination_context->Global(), destination_context->GetIsolate()));
}
// Custom logic to "clone" Blob references
blink::WebBlob blob = blink::WebBlob::FromV8Value(value);
if (!blob.IsNull()) {
v8::Context::Scope destination_context_scope(destination_context);
return v8::MaybeLocal<v8::Value>(blob.ToV8Value(
destination_context->Global(), destination_context->GetIsolate()));
}
// Proxy all objects
if (IsPlainObject(value)) {
auto object_value = value.As<v8::Object>();
auto passed_value = CreateProxyForAPI(
object_value, source_context, destination_context, object_cache,
support_dynamic_properties, recursion_depth + 1);
if (passed_value.IsEmpty())
return v8::MaybeLocal<v8::Value>();
return v8::MaybeLocal<v8::Value>(passed_value.ToLocalChecked());
}
// Serializable objects
blink::CloneableMessage ret;
{
v8::Local<v8::Context> error_context =
error_target == BridgeErrorTarget::kSource ? source_context
: destination_context;
v8::Context::Scope error_scope(error_context);
// V8 serializer will throw an error if required
if (!gin::ConvertFromV8(error_context->GetIsolate(), value, &ret))
return v8::MaybeLocal<v8::Value>();
}
{
v8::Context::Scope destination_context_scope(destination_context);
v8::Local<v8::Value> cloned_value =
gin::ConvertToV8(destination_context->GetIsolate(), ret);
object_cache->CacheProxiedObject(value, cloned_value);
return v8::MaybeLocal<v8::Value>(cloned_value);
}
}