Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions include/cef_v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "include/cef_frame.h"
#include "include/cef_task.h"

class CefV8BackingStore;
class CefV8Exception;
class CefV8Handler;
class CefV8StackFrame;
Expand Down Expand Up @@ -425,6 +426,59 @@ class CefV8ArrayBufferReleaseCallback : public virtual CefBaseRefCounted {
virtual void ReleaseBuffer(void* buffer) = 0;
};

#if CEF_API_ADDED(CEF_NEXT)
///
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New classes/methods need to be protected by #if CEF_API_ADDED(CEF_NEXT) with appropriate /*--cef(added=next)--*/ metadata. See https://bitbucket.org/chromiumembedded/cef/wiki/ApiVersioning.md#markdown-header-usage-in-pull-requests

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed that

/// Class representing a V8 ArrayBuffer backing store. The backing store holds
/// the memory that backs an ArrayBuffer. It must be created on a thread with a
/// valid V8 isolate (renderer main thread or WebWorker thread). Once created,
/// the Data() pointer can be safely read/written from any thread. This allows
/// expensive operations like memcpy to be performed on a background thread
/// before creating the ArrayBuffer on the V8 thread.
///
/// The backing store is consumed when passed to
/// CefV8Value::CreateArrayBufferFromBackingStore(), after which IsValid()
/// returns false.
///
/*--cef(source=library,no_debugct_check,added=next)--*/
class CefV8BackingStore : public virtual CefBaseRefCounted {
public:
///
/// Create a new backing store with allocated memory of |byte_length| bytes.
/// The memory is uninitialized. This method must be called on a thread with a
/// valid V8 isolate. The returned object can safely be passed to other
/// threads. Returns nullptr on failure.
///
/*--cef()--*/
static CefRefPtr<CefV8BackingStore> Create(size_t byte_length);

///
/// Returns a pointer to the allocated memory, or nullptr if the backing
/// store has been consumed or is otherwise invalid. The pointer is safe to
/// read/write from any thread. The caller must ensure all writes are complete
/// before passing this object to CreateArrayBufferFromBackingStore().
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a warning here about not keeping a pointer reference after passing to CreateArrayBufferFromBackingStore?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a warning to the Data() doc comment: "Pointers obtained from this method should not be retained after calling CreateArrayBufferFromBackingStore(), as the memory will then be owned by the ArrayBuffer and subject to V8 garbage collection."

/// Pointers obtained from this method should not be retained after calling
/// CreateArrayBufferFromBackingStore(), as the memory will then be owned by
/// the ArrayBuffer and subject to V8 garbage collection.
///
/*--cef()--*/
virtual void* Data() = 0;

///
/// Returns the size of the allocated memory in bytes, or 0 if the backing
/// store has been consumed.
///
/*--cef()--*/
virtual size_t ByteLength() = 0;

///
/// Returns true if this backing store has not yet been consumed by
/// CreateArrayBufferFromBackingStore().
///
/*--cef()--*/
virtual bool IsValid() = 0;
};
#endif

///
/// Class representing a V8 value handle. V8 handles can only be accessed from
/// the thread on which they are created. Valid threads for creating a V8 handle
Expand Down Expand Up @@ -540,6 +594,22 @@ class CefV8Value : public virtual CefBaseRefCounted {
static CefRefPtr<CefV8Value> CreateArrayBufferWithCopy(void* buffer,
size_t length);

#if CEF_API_ADDED(CEF_NEXT)
///
/// Create a new CefV8Value object of type ArrayBuffer from a backing store
/// previously created with CefV8BackingStore::Create(). This is a zero-copy
/// operation — the ArrayBuffer uses the memory already allocated by the
/// backing store. The backing store is consumed and becomes invalid after
/// this call. This method should only be called from within the scope of a
/// CefRenderProcessHandler, CefV8Handler or CefV8Accessor callback, or in
/// combination with calling Enter() and Exit() on a stored CefV8Context
/// reference.
///
/*--cef(added=next)--*/
static CefRefPtr<CefV8Value> CreateArrayBufferFromBackingStore(
CefRefPtr<CefV8BackingStore> backing_store);
#endif

///
/// Create a new CefV8Value object of type function. This method should only
/// be called from within the scope of a CefRenderProcessHandler, CefV8Handler
Expand Down
83 changes: 83 additions & 0 deletions libcef/renderer/v8_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,89 @@ CefRefPtr<CefV8Value> CefV8Value::CreateArrayBufferWithCopy(void* buffer,
return impl;
}

// CefV8BackingStoreImpl implementation.

CefV8BackingStoreImpl::CefV8BackingStoreImpl(
std::unique_ptr<v8::BackingStore> backing_store)
: backing_store_(std::move(backing_store)) {}

void* CefV8BackingStoreImpl::Data() {
base::AutoLock lock(lock_);
return backing_store_ ? backing_store_->Data() : nullptr;
}

size_t CefV8BackingStoreImpl::ByteLength() {
base::AutoLock lock(lock_);
return backing_store_ ? backing_store_->ByteLength() : 0;
}

bool CefV8BackingStoreImpl::IsValid() {
base::AutoLock lock(lock_);
return backing_store_ != nullptr;
}

std::unique_ptr<v8::BackingStore> CefV8BackingStoreImpl::TakeBackingStore() {
base::AutoLock lock(lock_);
return std::move(backing_store_);
}

// static
CefRefPtr<CefV8BackingStore> CefV8BackingStore::Create(size_t byte_length) {
CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);

if (byte_length == 0) {
return nullptr;
}

v8::Isolate* isolate = CefV8IsolateManager::Get()->isolate();
std::unique_ptr<v8::BackingStore> backing =
v8::ArrayBuffer::NewBackingStore(isolate, byte_length);
if (!backing) {
return nullptr;
}

return new CefV8BackingStoreImpl(std::move(backing));
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateArrayBufferFromBackingStore(
CefRefPtr<CefV8BackingStore> backing_store) {
CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);

if (!backing_store || !backing_store->IsValid()) {
return nullptr;
}

v8::Isolate* isolate = CefV8IsolateManager::Get()->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
if (context.IsEmpty()) {
DCHECK(false) << "not currently in a V8 context";
return nullptr;
}

CefV8BackingStoreImpl* impl =
static_cast<CefV8BackingStoreImpl*>(backing_store.get());
std::unique_ptr<v8::BackingStore> store = impl->TakeBackingStore();
if (!store) {
return nullptr;
}

v8::Local<v8::ArrayBuffer> ab =
v8::ArrayBuffer::New(isolate, std::move(store));

// Create a tracker object that will cause the user data reference to be
// released when the V8 object is destroyed.
V8TrackObject* tracker = new V8TrackObject(isolate);

// Attach the tracker object.
tracker->AttachTo(context, ab);

CefRefPtr<CefV8ValueImpl> value = new CefV8ValueImpl(isolate);
value->InitObject(ab, tracker);
return value.get();
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateFunction(
const CefString& name,
Expand Down
26 changes: 26 additions & 0 deletions libcef/renderer/v8_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
#define CEF_LIBCEF_RENDERER_V8_IMPL_H_
#pragma once

#include <memory>
#include <vector>

#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "cef/include/cef_v8.h"
#include "cef/libcef/common/tracker.h"
Expand Down Expand Up @@ -363,6 +365,30 @@ class CefV8ValueImpl : public CefV8Value {
IMPLEMENT_REFCOUNTING(CefV8ValueImpl);
};

class CefV8BackingStoreImpl : public CefV8BackingStore {
public:
explicit CefV8BackingStoreImpl(
std::unique_ptr<v8::BackingStore> backing_store);

CefV8BackingStoreImpl(const CefV8BackingStoreImpl&) = delete;
CefV8BackingStoreImpl& operator=(const CefV8BackingStoreImpl&) = delete;

// CefV8BackingStore methods.
void* Data() override;
size_t ByteLength() override;
bool IsValid() override;

// Transfer ownership of the underlying BackingStore. Returns nullptr if
// already consumed. Must be called on the V8 thread.
[[nodiscard]] std::unique_ptr<v8::BackingStore> TakeBackingStore();

private:
base::Lock lock_;
std::unique_ptr<v8::BackingStore> backing_store_;

IMPLEMENT_REFCOUNTING(CefV8BackingStoreImpl);
};

class CefV8StackTraceImpl : public CefV8StackTrace {
public:
CefV8StackTraceImpl(v8::Isolate* isolate, v8::Local<v8::StackTrace> handle);
Expand Down
64 changes: 64 additions & 0 deletions tests/ceftests/v8_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ enum V8TestMode {
#endif // CEF_V8_ENABLE_SANDBOX
V8TEST_ARRAY_BUFFER_CREATE_EMPTY,
V8TEST_ARRAY_BUFFER_COPY,
V8TEST_ARRAY_BUFFER_BACKING_STORE,
V8TEST_OBJECT_CREATE,
V8TEST_OBJECT_USERDATA,
V8TEST_OBJECT_ACCESSOR,
Expand Down Expand Up @@ -166,6 +167,9 @@ class V8RendererTest : public ClientAppRenderer::Delegate,
case V8TEST_ARRAY_BUFFER_COPY:
RunArrayBufferCopyTest();
break;
case V8TEST_ARRAY_BUFFER_BACKING_STORE:
RunArrayBufferBackingStoreTest();
break;
case V8TEST_OBJECT_CREATE:
RunObjectCreateTest();
break;
Expand Down Expand Up @@ -706,6 +710,65 @@ class V8RendererTest : public ClientAppRenderer::Delegate,
DestroyTest();
}

void RunArrayBufferBackingStoreTest() {
CefRefPtr<CefV8Context> context = GetContext();

// Enter the V8 context.
EXPECT_TRUE(context->Enter());
{
// Creating a backing store with zero size should fail.
CefRefPtr<CefV8BackingStore> empty_store =
CefV8BackingStore::Create(0);
EXPECT_FALSE(empty_store.get());

const size_t byte_length = 64;
CefRefPtr<CefV8BackingStore> backing_store =
CefV8BackingStore::Create(byte_length);
EXPECT_TRUE(backing_store.get());
EXPECT_TRUE(backing_store->IsValid());
EXPECT_EQ(backing_store->ByteLength(), byte_length);

void* data = backing_store->Data();
EXPECT_NE(data, nullptr);

// Write data into the backing store (simulates background thread work).
memset(data, 0xAB, byte_length);

// Create ArrayBuffer from the backing store (zero-copy).
CefRefPtr<CefV8Value> value =
CefV8Value::CreateArrayBufferFromBackingStore(backing_store);
EXPECT_TRUE(value.get());
EXPECT_TRUE(value->IsArrayBuffer());
EXPECT_TRUE(value->IsObject());
EXPECT_EQ(value->GetArrayBufferByteLength(), byte_length);

// Verify the data is accessible through the ArrayBuffer.
void* ab_data = value->GetArrayBufferData();
EXPECT_NE(ab_data, nullptr);
EXPECT_EQ(static_cast<uint8_t*>(ab_data)[0], 0xAB);
EXPECT_EQ(static_cast<uint8_t*>(ab_data)[byte_length - 1], 0xAB);

// The backing store should be consumed (invalid) after creating the
// ArrayBuffer.
EXPECT_FALSE(backing_store->IsValid());
EXPECT_EQ(backing_store->Data(), nullptr);
EXPECT_EQ(backing_store->ByteLength(), static_cast<size_t>(0));

// Creating a second ArrayBuffer from the consumed backing store should
// fail.
CefRefPtr<CefV8Value> value2 =
CefV8Value::CreateArrayBufferFromBackingStore(backing_store);
EXPECT_FALSE(value2.get());

// Verify the ArrayBuffer can be neutered.
EXPECT_TRUE(value->NeuterArrayBuffer());
EXPECT_EQ(value->GetArrayBufferByteLength(), static_cast<size_t>(0));
}
// Exit the V8 context.
EXPECT_TRUE(context->Exit());
DestroyTest();
}

#ifndef CEF_V8_ENABLE_SANDBOX
void RunArrayBufferValueTest() {
class TestArrayBufferReleaseCallback
Expand Down Expand Up @@ -3472,6 +3535,7 @@ V8_TEST(ArrayBufferValue, V8TEST_ARRAY_BUFFER_VALUE)
#endif // CEF_V8_ENABLE_SANDBOX
V8_TEST(ArrayBufferCreateEmpty, V8TEST_ARRAY_BUFFER_CREATE_EMPTY)
V8_TEST(ArrayBufferCopy, V8TEST_ARRAY_BUFFER_COPY)
V8_TEST(ArrayBufferBackingStore, V8TEST_ARRAY_BUFFER_BACKING_STORE)
V8_TEST(ObjectCreate, V8TEST_OBJECT_CREATE)
V8_TEST(ObjectUserData, V8TEST_OBJECT_USERDATA)
V8_TEST(ObjectAccessor, V8TEST_OBJECT_ACCESSOR)
Expand Down