910e62b5创建于 1月15日历史提交
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/platform/bindings/observable_array.h"

#include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
#include "v8/include/v8-container.h"
#include "v8/include/v8-function.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-proxy.h"
#include "v8/include/v8-template.h"

namespace blink {

namespace {

const V8PrivateProperty::SymbolKey kV8ProxyTargetToV8WrapperKey;

const WrapperTypeInfo kWrapperTypeInfoBody{
    {gin::kEmbedderBlink},
    /*install_interface_template_func=*/nullptr,
    /*install_context_dependent_props_func=*/nullptr,
    "ObservableArrayExoticObject",
    /*parent_class=*/nullptr,
    static_cast<v8::CppHeapPointerTag>(
        ScriptWrappableArrayTag::kObservableArrayExoticObjectTag),
    static_cast<v8::CppHeapPointerTag>(
        ScriptWrappableArrayTag::kV8ObservableArraySpeechRecognitionPhraseTag),
    WrapperTypeInfo::kWrapperTypeNoPrototype,
    // v8::Proxy (without an internal field) is used as a (pseudo) wrapper.
    WrapperTypeInfo::kNoInternalFieldClassId,
    WrapperTypeInfo::kIdlOtherType,
};

}  // namespace

namespace bindings {

ObservableArrayBase::ObservableArrayBase(
    GarbageCollectedMixin* platform_object,
    ObservableArrayExoticObject* observable_array_exotic_object)
    : platform_object_(platform_object),
      observable_array_exotic_object_(observable_array_exotic_object) {
  DCHECK(platform_object_);
}

v8::Local<v8::Object> ObservableArrayBase::GetProxyHandlerObject(
    ScriptState* script_state) {
  v8::Local<v8::FunctionTemplate> v8_function_template =
      GetProxyHandlerFunctionTemplate(script_state);
  v8::Local<v8::Context> v8_context = script_state->GetContext();
  v8::Local<v8::Function> v8_function =
      v8_function_template->GetFunction(v8_context).ToLocalChecked();
  v8::Local<v8::Object> v8_object =
      v8_function->NewInstance(v8_context).ToLocalChecked();
  CHECK(v8_object
            ->SetPrototypeV2(v8_context, v8::Null(script_state->GetIsolate()))
            .ToChecked());
  return v8_object;
}

void ObservableArrayBase::Trace(Visitor* visitor) const {
  ScriptWrappable::Trace(visitor);
  visitor->Trace(platform_object_);
  visitor->Trace(observable_array_exotic_object_);
}

}  // namespace bindings

// static
const WrapperTypeInfo& ObservableArrayExoticObject::wrapper_type_info_ =
    kWrapperTypeInfoBody;

// static
v8::Local<v8::Object>
ObservableArrayExoticObject::GetBackingObjectFromProxyTarget(
    v8::Isolate* isolate,
    v8::Local<v8::Array> v8_proxy_target) {
  // See the implementation comment in ObservableArrayExoticObject::Wrap.
  auto private_property =
      V8PrivateProperty::GetSymbol(isolate, kV8ProxyTargetToV8WrapperKey);
  v8::Local<v8::Value> backing_list_wrapper =
      private_property.GetOrUndefined(v8_proxy_target).ToLocalChecked();
  // Crash when author script managed to pass something else other than the
  // right proxy target object.
  CHECK(backing_list_wrapper->IsObject());
  return backing_list_wrapper.As<v8::Object>();
}

ObservableArrayExoticObject::ObservableArrayExoticObject(
    bindings::ObservableArrayBase* observable_array_backing_list_object)
    : observable_array_backing_list_object_(
          observable_array_backing_list_object) {}

void ObservableArrayExoticObject::Trace(Visitor* visitor) const {
  ScriptWrappable::Trace(visitor);
  visitor->Trace(observable_array_backing_list_object_);
}

v8::Local<v8::Value> ObservableArrayExoticObject::Wrap(
    ScriptState* script_state) {
  v8::Isolate* isolate = script_state->GetIsolate();
  DCHECK(!DOMDataStore::ContainsWrapper(isolate, this));

  // The proxy target object must be a JS Array (v8::Array) by definition.
  // Especially it's important that IsArray(proxy) evaluates to true.
  // https://tc39.es/ecma262/#sec-isarray
  //
  // Thus, we create the following structure of objects:
  //   exotic_object = new Proxy(target_object, handler_object);
  // where
  //   target_object = new Array();
  //   target_object--(private property)-->v8_wrapper_of_backing_list
  //   v8_wrapper_of_backing_list--(internal field)-->blink_backing_list
  //   blink_backing_list = instance of V8ObservableArrayXxxx
  //
  // The back reference from blink_backing_list to the JS Array object is not
  // supported just because there is no use case so far.
  v8::Local<v8::Value> backing_list_wrapper =
      GetBackingListObject()->ToV8(script_state);
  CHECK(backing_list_wrapper->IsObject());
  v8::Local<v8::Array> target = v8::Array::New(isolate);
  auto private_property =
      V8PrivateProperty::GetSymbol(isolate, kV8ProxyTargetToV8WrapperKey);
  private_property.Set(target, backing_list_wrapper);

  v8::Local<v8::Object> handler =
      GetBackingListObject()->GetProxyHandlerObject(script_state);
  v8::Local<v8::Proxy> proxy = v8::Proxy::New(script_state->GetContext(),
                                              target.As<v8::Object>(), handler)
                                   .ToLocalChecked();
  v8::Local<v8::Object> wrapper = proxy.As<v8::Object>();

  // Register the proxy object as a (pseudo) wrapper object although the proxy
  // object does not have an internal field pointing to a Blink object.
  const bool is_new_entry = script_state->World().DomDataStore().Set(
      script_state->GetIsolate(), this, GetWrapperTypeInfo(), wrapper);
  CHECK(is_new_entry);

  return wrapper;
}

v8::Local<v8::Object> ObservableArrayExoticObject::AssociateWithWrapper(
    v8::Isolate* isolate,
    const WrapperTypeInfo* wrapper_type_info,
    v8::Local<v8::Object> wrapper) {
  // The proxy object does not have an internal field and cannot be associated
  // with a Blink object directly.
  NOTREACHED();
}

}  // namespace blink