blob: d7050727cda533d69f1883e59127721ce0f00e36 [file] [log] [blame]
// 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_OBSERVABLE_ARRAY_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_OBSERVABLE_ARRAY_H_
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/heap/heap_traits.h"
#include "third_party/blink/renderer/platform/heap/member.h"
#include "v8/include/v8-forward.h"
// Overview of Blink implementation of Web IDL observable arrays
//
// Observable arrays are implemented with two objects:
// 1) backing list object
// https://webidl.spec.whatwg.org/#observable-array-attribute-backing-list
// and 2) exotic object.
// https://webidl.spec.whatwg.org/#backing-observable-array-exotic-object
// As DOM objects are implemented with a ScriptWrappable and its V8 wrappers,
// there are a ScriptWrappable of a backing list object and its V8 wrappers,
// also a ScriptWrappable of a exotic object and its (pseudo) V8 wrappers.
//
// By definition, observable array exotic object is a JS Proxy in ECMAScript
// binding.
//
// let exotic_object = new Proxy(backing_list_object, handler_object);
//
// For web developers, the JS Proxy object is the only visible object. Web
// developers cannot access the backing list object directly.
//
// For Blink developers, the backing list object looks the primary object.
// However, when exposing an observable array to web developers, the exotic
// object must be exposed instead of the backing list object. This is done in
// auto-generated bindings (generated bindings automatically call
// GetExoticObject()).
//
// class MyIdlInterface : public ScriptWrappable {
// public:
// V8ObservableArrayNode* myAttr() const {
// return my_observable_array_;
// }
// private:
// // my_observable_array_ is a backing list object.
// Member<V8ObservableArrayNode> my_observable_array_;
// };
//
// Class hierarchy and relationship:
// bindings::ObservableArrayBase -- the base class of backing list objects
// +-- bindings::ObservableArrayImplHelper<T> -- just a helper
// +-- V8ObservableArrayNode -- generated implementation of IDL
// ObservableArray<Node>. Bindings code generator produces this
// class from *.idl files.
// ObservableArrayExoticObject -- the implementation of exotic objects
//
// v8_exotic_object (= JS Proxy)
// --(proxy target)--> v8_array (= JS Array)
// --(private property)--> v8_backing_list_object
// --(internal field)--> blink_backing_list_object
// --(data member)--> blink_exotic_object
// --(ToV8Traits)--> v8_exotic_object
namespace blink {
class ObservableArrayExoticObject;
namespace bindings {
// ObservableArrayBase is the common base class of all the observable array
// classes, and represents the backing list for an IDL attribute of an
// observable array type (but the actual implementation lives in
// bindings/core/v8/).
// https://webidl.spec.whatwg.org/#observable-array-attribute-backing-list
class PLATFORM_EXPORT ObservableArrayBase : public ScriptWrappable {
public:
ObservableArrayBase(
GarbageCollectedMixin* platform_object,
ObservableArrayExoticObject* observable_array_exotic_object);
~ObservableArrayBase() override = default;
// Returns the observable array exotic object, which is the value to be
// returned as the IDL attribute value. Do not use `this` object (= the
// observable array backing list object) as the IDL attribute value.
ObservableArrayExoticObject* GetExoticObject() const {
return observable_array_exotic_object_.Get();
}
v8::Local<v8::Object> GetProxyHandlerObject(ScriptState* script_state);
void Trace(Visitor* visitor) const override;
protected:
GarbageCollectedMixin* GetPlatformObject() { return platform_object_.Get(); }
virtual v8::Local<v8::FunctionTemplate> GetProxyHandlerFunctionTemplate(
ScriptState* script_state) = 0;
private:
Member<GarbageCollectedMixin> platform_object_; // IDL attribute owner
Member<ObservableArrayExoticObject> observable_array_exotic_object_;
};
template <typename ElementType>
class ObservableArrayImplHelper : public bindings::ObservableArrayBase {
public:
using BackingListType = VectorOf<ElementType>;
using size_type = typename BackingListType::size_type;
using value_type = typename BackingListType::value_type;
using reference = typename BackingListType::reference;
using const_reference = typename BackingListType::const_reference;
using pointer = typename BackingListType::pointer;
using const_pointer = typename BackingListType::const_pointer;
using iterator = typename BackingListType::iterator;
using const_iterator = typename BackingListType::const_iterator;
using reverse_iterator = typename BackingListType::reverse_iterator;
using const_reverse_iterator =
typename BackingListType::const_reverse_iterator;
explicit ObservableArrayImplHelper(GarbageCollectedMixin* platform_object)
: bindings::ObservableArrayBase(
platform_object,
MakeGarbageCollected<ObservableArrayExoticObject>(this)) {}
~ObservableArrayImplHelper() override = default;
// Returns the observable array exotic object, which is the value to be
// returned as the IDL attribute value. Do not use `this` object (= the
// observable array backing list object) as the IDL attribute value.
using bindings::ObservableArrayBase::GetExoticObject;
// Vector-compatible APIs (accessors)
size_type size() const { return backing_list_.size(); }
size_type capacity() const { return backing_list_.capacity(); }
bool empty() const { return backing_list_.empty(); }
void reserve(size_type new_capacity) { backing_list_.reserve(new_capacity); }
void ReserveInitialCapacity(size_type initial_capacity) {
backing_list_.ReserveInitialCapacity(initial_capacity);
}
reference at(size_type index) { return backing_list_.at(index); }
const_reference at(size_type index) const { return backing_list_.at(index); }
reference operator[](size_type index) { return backing_list_[index]; }
const_reference operator[](size_type index) const {
return backing_list_[index];
}
value_type* data() { return backing_list_.data(); }
const value_type* data() const { return backing_list_.data(); }
iterator begin() { return backing_list_.begin(); }
iterator end() { return backing_list_.end(); }
const_iterator begin() const { return backing_list_.begin(); }
const_iterator end() const { return backing_list_.end(); }
reverse_iterator rbegin() { return backing_list_.rbegin(); }
reverse_iterator rend() { return backing_list_.rend(); }
const_reverse_iterator rbegin() const { return backing_list_.rbegin(); }
const_reverse_iterator rend() const { return backing_list_.rend(); }
reference front() { return backing_list_.front(); }
reference back() { return backing_list_.back(); }
const_reference front() const { return backing_list_.front(); }
const_reference back() const { return backing_list_.back(); }
// Vector-compatible APIs (modifiers)
void resize(size_type size) { backing_list_.resize(size); }
void clear() { backing_list_.clear(); }
template <typename T>
void push_back(T&& value) {
backing_list_.push_back(std::forward<T>(value));
}
void pop_back() { backing_list_.pop_back(); }
template <typename... Args>
reference emplace_back(Args&&... args) {
return backing_list_.emplace_back(std::forward<Args>(args)...);
}
template <typename T>
void insert(iterator position, T&& value) {
backing_list_.InsertAt(position, std::forward<T>(value));
}
iterator erase(iterator position) { return backing_list_.erase(position); }
iterator erase(iterator first, iterator last) {
return backing_list_.erase(first, last);
}
void Trace(Visitor* visitor) const override {
ObservableArrayBase::Trace(visitor);
TraceIfNeeded<BackingListType>::Trace(visitor, backing_list_);
}
private:
BackingListType backing_list_;
};
} // namespace bindings
// Represents a backing observable array exotic object.
// https://webidl.spec.whatwg.org/#backing-observable-array-exotic-object
class PLATFORM_EXPORT ObservableArrayExoticObject final
: public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
// Returns the backing list object extracted from the proxy target object
// of type JS Array.
template <typename T>
static T* ProxyTargetToObservableArray(v8::Isolate* isolate,
v8::Local<v8::Array> v8_proxy_target) {
return ToScriptWrappable<T>(
isolate, GetBackingObjectFromProxyTarget(isolate, v8_proxy_target));
}
explicit ObservableArrayExoticObject(
bindings::ObservableArrayBase* observable_array_backing_list_object);
~ObservableArrayExoticObject() final = default;
bindings::ObservableArrayBase* GetBackingListObject() const {
return observable_array_backing_list_object_.Get();
}
// ScriptWrappable overrides:
v8::Local<v8::Value> Wrap(ScriptState* script_state) final;
[[nodiscard]] v8::Local<v8::Object> AssociateWithWrapper(
v8::Isolate* isolate,
const WrapperTypeInfo* wrapper_type_info,
v8::Local<v8::Object> wrapper) final;
void Trace(Visitor* visitor) const final;
private:
static v8::Local<v8::Object> GetBackingObjectFromProxyTarget(
v8::Isolate*,
v8::Local<v8::Array> v8_proxy_target);
Member<bindings::ObservableArrayBase> observable_array_backing_list_object_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_OBSERVABLE_ARRAY_H_