v8::MaybeLocal PassValueToOtherContext()

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);
  }
}