// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

// MSVC++ requires this to be set before any other includes to get M_PI.
// Otherwise there will be compile errors in wtf/MathExtras.h.
#define _USE_MATH_DEFINES

#include <map>
#include <string>

#include "base/command_line.h"
#include "build/build_config.h"

// Enable deprecation warnings for MSVC and Clang. See http://crbug.com/585142.
#if BUILDFLAG(IS_WIN)
#if defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic error "-Wdeprecated-declarations"
#else
#pragma warning(push)
#pragma warning(default : 4996)
#endif
#endif

#include "libcef/renderer/v8_impl.h"

#include "libcef/common/app_manager.h"
#include "libcef/common/cef_switches.h"
#include "libcef/common/task_runner_impl.h"
#include "libcef/common/tracker.h"
#include "libcef/renderer/blink_glue.h"
#include "libcef/renderer/browser_impl.h"
#include "libcef/renderer/render_frame_util.h"
#include "libcef/renderer/thread_util.h"

#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_local.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_frame.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "url/gurl.h"

namespace {

static const char kCefTrackObject[] = "Cef::TrackObject";

void MessageListenerCallbackImpl(v8::Handle<v8::Message> message,
                                 v8::Handle<v8::Value> data);

// The following *Private functions are convenience wrappers for methods on
// v8::Object with the corresponding names.
// Based on extensions/renderer/object_backed_native_handler.cc.

void SetPrivate(v8::Local<v8::Context> context,
                v8::Local<v8::Object> obj,
                const char* key,
                v8::Local<v8::Value> value) {
  v8::Isolate* isolate = context->GetIsolate();
  obj->SetPrivate(context,
                  v8::Private::ForApi(
                      isolate, v8::String::NewFromUtf8(
                                   isolate, key, v8::NewStringType::kNormal)
                                   .ToLocalChecked()),
                  value)
      .FromJust();
}

bool GetPrivate(v8::Local<v8::Context> context,
                v8::Local<v8::Object> obj,
                const char* key,
                v8::Local<v8::Value>* result) {
  v8::Isolate* isolate = context->GetIsolate();
  return obj
      ->GetPrivate(context,
                   v8::Private::ForApi(
                       isolate, v8::String::NewFromUtf8(
                                    isolate, key, v8::NewStringType::kNormal)
                                    .ToLocalChecked()))
      .ToLocal(result);
}

// Manages memory and state information associated with a single Isolate.
class CefV8IsolateManager {
 public:
  CefV8IsolateManager()
      : isolate_(v8::Isolate::GetCurrent()),
        task_runner_(CEF_RENDER_TASK_RUNNER()),
        message_listener_registered_(false),
        worker_id_(0) {
    DCHECK(isolate_);
    DCHECK(task_runner_.get());
  }
  ~CefV8IsolateManager() {
    DCHECK_EQ(isolate_, v8::Isolate::GetCurrent());
    DCHECK(context_map_.empty());
  }

  scoped_refptr<CefV8ContextState> GetContextState(
      v8::Local<v8::Context> context) {
    DCHECK_EQ(isolate_, v8::Isolate::GetCurrent());
    DCHECK(context.IsEmpty() || isolate_ == context->GetIsolate());

    if (context.IsEmpty()) {
      if (isolate_->InContext())
        context = isolate_->GetCurrentContext();
      else
        return scoped_refptr<CefV8ContextState>();
    }

    int hash = context->Global()->GetIdentityHash();
    ContextMap::const_iterator it = context_map_.find(hash);
    if (it != context_map_.end())
      return it->second;

    scoped_refptr<CefV8ContextState> state = new CefV8ContextState();
    context_map_.insert(std::make_pair(hash, state));

    return state;
  }

  void ReleaseContext(v8::Local<v8::Context> context) {
    DCHECK_EQ(isolate_, v8::Isolate::GetCurrent());
    DCHECK_EQ(isolate_, context->GetIsolate());

    int hash = context->Global()->GetIdentityHash();
    ContextMap::iterator it = context_map_.find(hash);
    if (it != context_map_.end()) {
      it->second->Detach();
      context_map_.erase(it);
    }
  }

  void AddGlobalTrackObject(CefTrackNode* object) {
    DCHECK_EQ(isolate_, v8::Isolate::GetCurrent());
    global_manager_.Add(object);
  }

  void DeleteGlobalTrackObject(CefTrackNode* object) {
    DCHECK_EQ(isolate_, v8::Isolate::GetCurrent());
    global_manager_.Delete(object);
  }

  void SetUncaughtExceptionStackSize(int stack_size) {
    if (stack_size <= 0)
      return;

    if (!message_listener_registered_) {
      isolate_->AddMessageListener(&MessageListenerCallbackImpl);
      message_listener_registered_ = true;
    }

    isolate_->SetCaptureStackTraceForUncaughtExceptions(
        true, stack_size, v8::StackTrace::kDetailed);
  }

  void SetWorkerAttributes(int worker_id, const GURL& worker_url) {
    worker_id_ = worker_id;
    worker_url_ = worker_url;
  }

  v8::Isolate* isolate() const { return isolate_; }
  scoped_refptr<base::SingleThreadTaskRunner> task_runner() const {
    return task_runner_;
  }

  int worker_id() const { return worker_id_; }

  const GURL& worker_url() const { return worker_url_; }

 private:
  v8::Isolate* isolate_;
  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;

  using ContextMap = std::map<int, scoped_refptr<CefV8ContextState>>;
  ContextMap context_map_;

  // Used for globally tracked objects that are not associated with a particular
  // context.
  CefTrackManager global_manager_;

  // True if the message listener has been registered.
  bool message_listener_registered_;

  // Attributes associated with WebWorker threads.
  int worker_id_;
  GURL worker_url_;
};

// Chromium uses the default Isolate for the main render process thread and a
// new Isolate for each WebWorker thread. Continue this pattern by tracking
// Isolate information on a per-thread basis. This implementation will need to
// be re-worked (perhaps using a map keyed on v8::Isolate::GetCurrent()) if
// in the future Chromium begins using the same Isolate across multiple threads.
class CefV8StateManager {
 public:
  CefV8StateManager() {}

  void CreateIsolateManager() {
    DCHECK(!current_tls_.Get());
    current_tls_.Set(new CefV8IsolateManager());
  }

  void DestroyIsolateManager() {
    DCHECK(current_tls_.Get());
    delete current_tls_.Get();
    current_tls_.Set(nullptr);
  }

  CefV8IsolateManager* GetIsolateManager() {
    CefV8IsolateManager* manager = current_tls_.Get();
    DCHECK(manager);
    return manager;
  }

 private:
  base::ThreadLocalPointer<CefV8IsolateManager> current_tls_;
};

base::LazyInstance<CefV8StateManager>::Leaky g_v8_state =
    LAZY_INSTANCE_INITIALIZER;

CefV8IsolateManager* GetIsolateManager() {
  return g_v8_state.Pointer()->GetIsolateManager();
}

class V8TrackObject : public CefTrackNode {
 public:
  explicit V8TrackObject(v8::Isolate* isolate)
      : isolate_(isolate), external_memory_(0) {
    DCHECK(isolate_);
    isolate_->AdjustAmountOfExternalAllocatedMemory(
        static_cast<int>(sizeof(V8TrackObject)));
  }
  ~V8TrackObject() {
    isolate_->AdjustAmountOfExternalAllocatedMemory(
        -static_cast<int>(sizeof(V8TrackObject)) - external_memory_);
  }

  inline int GetExternallyAllocatedMemory() { return external_memory_; }

  int AdjustExternallyAllocatedMemory(int change_in_bytes) {
    int new_value = external_memory_ + change_in_bytes;
    if (new_value < 0) {
      NOTREACHED() << "External memory usage cannot be less than 0 bytes";
      change_in_bytes = -(external_memory_);
      new_value = 0;
    }

    if (change_in_bytes != 0)
      isolate_->AdjustAmountOfExternalAllocatedMemory(change_in_bytes);
    external_memory_ = new_value;

    return new_value;
  }

  inline void SetAccessor(CefRefPtr<CefV8Accessor> accessor) {
    accessor_ = accessor;
  }

  inline CefRefPtr<CefV8Accessor> GetAccessor() { return accessor_; }

  inline void SetInterceptor(CefRefPtr<CefV8Interceptor> interceptor) {
    interceptor_ = interceptor;
  }

  inline CefRefPtr<CefV8Interceptor> GetInterceptor() { return interceptor_; }

  inline void SetHandler(CefRefPtr<CefV8Handler> handler) {
    handler_ = handler;
  }

  inline CefRefPtr<CefV8Handler> GetHandler() { return handler_; }

  inline void SetUserData(CefRefPtr<CefBaseRefCounted> user_data) {
    user_data_ = user_data;
  }

  inline CefRefPtr<CefBaseRefCounted> GetUserData() { return user_data_; }

  // Attach this track object to the specified V8 object.
  void AttachTo(v8::Local<v8::Context> context, v8::Local<v8::Object> object) {
    SetPrivate(context, object, kCefTrackObject,
               v8::External::New(isolate_, this));
  }

  // Retrieve the track object for the specified V8 object.
  static V8TrackObject* Unwrap(v8::Local<v8::Context> context,
                               v8::Local<v8::Object> object) {
    v8::Local<v8::Value> value;
    if (GetPrivate(context, object, kCefTrackObject, &value))
      return static_cast<V8TrackObject*>(v8::External::Cast(*value)->Value());

    return nullptr;
  }

 private:
  v8::Isolate* isolate_;
  CefRefPtr<CefV8Accessor> accessor_;
  CefRefPtr<CefV8Interceptor> interceptor_;
  CefRefPtr<CefV8Handler> handler_;
  CefRefPtr<CefBaseRefCounted> user_data_;
  int external_memory_;
};

class V8TrackString : public CefTrackNode {
 public:
  explicit V8TrackString(const std::string& str) : string_(str) {}
  const char* GetString() { return string_.c_str(); }

 private:
  std::string string_;
};

class V8TrackArrayBuffer : public CefTrackNode {
 public:
  explicit V8TrackArrayBuffer(
      v8::Isolate* isolate,
      CefRefPtr<CefV8ArrayBufferReleaseCallback> release_callback)
      : isolate_(isolate), release_callback_(release_callback) {
    DCHECK(isolate_);
    isolate_->AdjustAmountOfExternalAllocatedMemory(
        static_cast<int>(sizeof(V8TrackArrayBuffer)));
  }

  ~V8TrackArrayBuffer() {
    isolate_->AdjustAmountOfExternalAllocatedMemory(
        -static_cast<int>(sizeof(V8TrackArrayBuffer)));
  }

  CefRefPtr<CefV8ArrayBufferReleaseCallback> GetReleaseCallback() {
    return release_callback_;
  }

  // Attach this track object to the specified V8 object.
  void AttachTo(v8::Local<v8::Context> context,
                v8::Local<v8::ArrayBuffer> arrayBuffer) {
    SetPrivate(context, arrayBuffer, kCefTrackObject,
               v8::External::New(isolate_, this));
  }

  // Retrieve the track object for the specified V8 object.
  static V8TrackArrayBuffer* Unwrap(v8::Local<v8::Context> context,
                                    v8::Local<v8::Object> object) {
    v8::Local<v8::Value> value;
    if (GetPrivate(context, object, kCefTrackObject, &value))
      return static_cast<V8TrackArrayBuffer*>(
          v8::External::Cast(*value)->Value());

    return nullptr;
  }

 private:
  v8::Isolate* isolate_;
  CefRefPtr<CefV8ArrayBufferReleaseCallback> release_callback_;
};

// Object wrapped in a v8::External and passed as the Data argument to
// v8::FunctionTemplate::New.
class V8FunctionData {
 public:
  static v8::Local<v8::External> Create(v8::Isolate* isolate,
                                        const CefString& function_name,
                                        CefRefPtr<CefV8Handler> handler) {
    // |data| will be deleted if/when the returned v8::External is GC'd.
    V8FunctionData* data = new V8FunctionData(isolate, function_name, handler);
    return data->CreateExternal();
  }

  static V8FunctionData* Unwrap(v8::Local<v8::Value> data) {
    DCHECK(data->IsExternal());
    return static_cast<V8FunctionData*>(v8::External::Cast(*data)->Value());
  }

  CefString function_name() const { return function_name_; }

  CefV8Handler* handler() const {
    if (!handler_)
      return nullptr;
    return handler_.get();
  }

 private:
  V8FunctionData(v8::Isolate* isolate,
                 const CefString& function_name,
                 CefRefPtr<CefV8Handler> handler)
      : isolate_(isolate), function_name_(function_name), handler_(handler) {
    DCHECK(isolate_);
    DCHECK(handler_);
  }

  ~V8FunctionData() {
    isolate_->AdjustAmountOfExternalAllocatedMemory(
        -static_cast<int>(sizeof(V8FunctionData)));
    handler_ = nullptr;
    function_name_ = "FreedFunction";
  }

  v8::Local<v8::External> CreateExternal() {
    v8::Local<v8::External> external = v8::External::New(isolate_, this);

    isolate_->AdjustAmountOfExternalAllocatedMemory(
        static_cast<int>(sizeof(V8FunctionData)));

    handle_.Reset(isolate_, external);
    handle_.SetWeak(this, FirstWeakCallback, v8::WeakCallbackType::kParameter);

    return external;
  }

  static void FirstWeakCallback(
      const v8::WeakCallbackInfo<V8FunctionData>& data) {
    V8FunctionData* wrapper = data.GetParameter();
    wrapper->handle_.Reset();
    data.SetSecondPassCallback(SecondWeakCallback);
  }

  static void SecondWeakCallback(
      const v8::WeakCallbackInfo<V8FunctionData>& data) {
    V8FunctionData* wrapper = data.GetParameter();
    delete wrapper;
  }

  v8::Isolate* isolate_;
  CefString function_name_;
  CefRefPtr<CefV8Handler> handler_;
  v8::Persistent<v8::External> handle_;
};

// Convert a CefString to a V8::String.
v8::Local<v8::String> GetV8String(v8::Isolate* isolate, const CefString& str) {
#if defined(CEF_STRING_TYPE_UTF16)
  // Already a UTF16 string.
  return v8::String::NewFromTwoByte(
             isolate,
             reinterpret_cast<uint16_t*>(
                 const_cast<CefString::char_type*>(str.c_str())),
             v8::NewStringType::kNormal, str.length())
      .ToLocalChecked();
#elif defined(CEF_STRING_TYPE_UTF8)
  // Already a UTF8 string.
  return v8::String::NewFromUtf8(isolate, const_cast<char*>(str.c_str()),
                                 v8::NewStringType::kNormal, str.length())
      .ToLocalChecked();
#else
  // Convert the string to UTF8.
  std::string tmpStr = str;
  return v8::String::NewFromUtf8(isolate, tmpStr.c_str(),
                                 v8::NewStringType::kNormal, tmpStr.length())
      .ToLocalChecked();
#endif
}

#if defined(CEF_STRING_TYPE_UTF16)
void v8impl_string_dtor(char16* str) {
  delete[] str;
}
#elif defined(CEF_STRING_TYPE_UTF8)
void v8impl_string_dtor(char* str) {
  delete[] str;
}
#endif

// Convert a v8::String to CefString.
void GetCefString(v8::Isolate* isolate,
                  v8::Local<v8::String> str,
                  CefString& out) {
  if (str.IsEmpty())
    return;

#if defined(CEF_STRING_TYPE_WIDE)
  // Allocate enough space for a worst-case conversion.
  int len = str->Utf8Length();
  if (len == 0)
    return;
  char* buf = new char[len + 1];
  str->WriteUtf8(isolate, buf, len + 1);

  // Perform conversion to the wide type.
  cef_string_t* retws = out.GetWritableStruct();
  cef_string_utf8_to_wide(buf, len, retws);

  delete[] buf;
#else  // !defined(CEF_STRING_TYPE_WIDE)
#if defined(CEF_STRING_TYPE_UTF16)
  int len = str->Length();
  if (len == 0)
    return;
  char16* buf = new char16[len + 1];
  str->Write(isolate, reinterpret_cast<uint16_t*>(buf), 0, len + 1);
#else
  // Allocate enough space for a worst-case conversion.
  int len = str->Utf8Length();
  if (len == 0)
    return;
  char* buf = new char[len + 1];
  str->WriteUtf8(isolate, buf, len + 1);
#endif

  // Don't perform an extra string copy.
  out.clear();
  cef_string_t* retws = out.GetWritableStruct();
  retws->str = buf;
  retws->length = len;
  retws->dtor = v8impl_string_dtor;
#endif  // !defined(CEF_STRING_TYPE_WIDE)
}

// V8 function callback.
void FunctionCallbackImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Context> context = isolate->GetCurrentContext();

  V8FunctionData* data = V8FunctionData::Unwrap(info.Data());
  if (!data->handler()) {
    // handler has gone away, bail!
    info.GetReturnValue().SetUndefined();
    return;
  }
  CefV8ValueList params;
  for (int i = 0; i < info.Length(); i++)
    params.push_back(new CefV8ValueImpl(isolate, context, info[i]));

  CefRefPtr<CefV8Value> object =
      new CefV8ValueImpl(isolate, context, info.This());
  CefRefPtr<CefV8Value> retval;
  CefString exception;

  if (data->handler()->Execute(data->function_name(), object, params, retval,
                               exception)) {
    if (!exception.empty()) {
      info.GetReturnValue().Set(isolate->ThrowException(
          v8::Exception::Error(GetV8String(isolate, exception))));
      return;
    } else {
      CefV8ValueImpl* rv = static_cast<CefV8ValueImpl*>(retval.get());
      if (rv && rv->IsValid()) {
        info.GetReturnValue().Set(rv->GetV8Value(true));
        return;
      }
    }
  }

  info.GetReturnValue().SetUndefined();
}

// V8 Accessor callbacks
void AccessorNameGetterCallbackImpl(
    v8::Local<v8::Name> property,
    const v8::PropertyCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Context> context = isolate->GetCurrentContext();

  v8::Local<v8::Object> obj = info.This();

  CefRefPtr<CefV8Accessor> accessorPtr;

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker)
    accessorPtr = tracker->GetAccessor();

  if (accessorPtr.get()) {
    CefRefPtr<CefV8Value> retval;
    CefRefPtr<CefV8Value> object = new CefV8ValueImpl(isolate, context, obj);
    CefString name, exception;
    GetCefString(isolate, v8::Local<v8::String>::Cast(property), name);
    if (accessorPtr->Get(name, object, retval, exception)) {
      if (!exception.empty()) {
        info.GetReturnValue().Set(isolate->ThrowException(
            v8::Exception::Error(GetV8String(isolate, exception))));
        return;
      } else {
        CefV8ValueImpl* rv = static_cast<CefV8ValueImpl*>(retval.get());
        if (rv && rv->IsValid()) {
          info.GetReturnValue().Set(rv->GetV8Value(true));
          return;
        }
      }
    }
  }

  return info.GetReturnValue().SetUndefined();
}

void AccessorNameSetterCallbackImpl(
    v8::Local<v8::Name> property,
    v8::Local<v8::Value> value,
    const v8::PropertyCallbackInfo<void>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Context> context = isolate->GetCurrentContext();

  v8::Local<v8::Object> obj = info.This();

  CefRefPtr<CefV8Accessor> accessorPtr;

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker)
    accessorPtr = tracker->GetAccessor();

  if (accessorPtr.get()) {
    CefRefPtr<CefV8Value> object = new CefV8ValueImpl(isolate, context, obj);
    CefRefPtr<CefV8Value> cefValue =
        new CefV8ValueImpl(isolate, context, value);
    CefString name, exception;
    GetCefString(isolate, v8::Local<v8::String>::Cast(property), name);
    accessorPtr->Set(name, object, cefValue, exception);
    if (!exception.empty()) {
      isolate->ThrowException(
          v8::Exception::Error(GetV8String(isolate, exception)));
      return;
    }
  }
}

// Two helper functions for V8 Interceptor callbacks.
CefString PropertyToIndex(v8::Isolate* isolate, v8::Local<v8::Name> property) {
  CefString name;
  GetCefString(isolate, property.As<v8::String>(), name);
  return name;
}

int PropertyToIndex(v8::Isolate* isolate, uint32_t index) {
  return static_cast<int>(index);
}

// V8 Interceptor callbacks.
// T == v8::Local<v8::Name> for named property handlers and
// T == uint32_t for indexed property handlers
template <typename T>
void InterceptorGetterCallbackImpl(
    T property,
    const v8::PropertyCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Context> context = isolate->GetCurrentContext();

  v8::Handle<v8::Object> obj = info.This();
  CefRefPtr<CefV8Interceptor> interceptorPtr;

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker)
    interceptorPtr = tracker->GetInterceptor();
  if (!interceptorPtr.get())
    return;

  CefRefPtr<CefV8Value> object = new CefV8ValueImpl(isolate, context, obj);
  CefRefPtr<CefV8Value> retval;
  CefString exception;
  interceptorPtr->Get(PropertyToIndex(isolate, property), object, retval,
                      exception);
  if (!exception.empty()) {
    info.GetReturnValue().Set(isolate->ThrowException(
        v8::Exception::Error(GetV8String(isolate, exception))));
  } else {
    CefV8ValueImpl* retval_impl = static_cast<CefV8ValueImpl*>(retval.get());
    if (retval_impl && retval_impl->IsValid()) {
      info.GetReturnValue().Set(retval_impl->GetV8Value(true));
    }
  }
}

template <typename T>
void InterceptorSetterCallbackImpl(
    T property,
    v8::Local<v8::Value> value,
    const v8::PropertyCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::Handle<v8::Object> obj = info.This();
  CefRefPtr<CefV8Interceptor> interceptorPtr;

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker)
    interceptorPtr = tracker->GetInterceptor();

  if (!interceptorPtr.get())
    return;
  CefRefPtr<CefV8Value> object = new CefV8ValueImpl(isolate, context, obj);
  CefRefPtr<CefV8Value> cefValue = new CefV8ValueImpl(isolate, context, value);
  CefString exception;
  interceptorPtr->Set(PropertyToIndex(isolate, property), object, cefValue,
                      exception);
  if (!exception.empty()) {
    isolate->ThrowException(
        v8::Exception::Error(GetV8String(isolate, exception)));
  }
}

// V8 extension registration.

class ExtensionWrapper : public v8::Extension {
 public:
  ExtensionWrapper(const char* extension_name,
                   const char* javascript_code,
                   CefV8Handler* handler)
      : v8::Extension(extension_name, javascript_code), handler_(handler) {}

  v8::Handle<v8::FunctionTemplate> GetNativeFunctionTemplate(
      v8::Isolate* isolate,
      v8::Handle<v8::String> name) override {
    if (!handler_)
      return v8::Local<v8::FunctionTemplate>();

    CefString func_name;
    GetCefString(isolate, name, func_name);

    v8::Local<v8::External> function_data =
        V8FunctionData::Create(isolate, func_name, handler_);

    return v8::FunctionTemplate::New(isolate, FunctionCallbackImpl,
                                     function_data);
  }

 private:
  CefV8Handler* handler_;
};

class CefV8ExceptionImpl : public CefV8Exception {
 public:
  CefV8ExceptionImpl(v8::Local<v8::Context> context,
                     v8::Local<v8::Message> message)
      : line_number_(0),
        start_position_(0),
        end_position_(0),
        start_column_(0),
        end_column_(0) {
    if (message.IsEmpty())
      return;

    v8::Isolate* isolate = context->GetIsolate();
    GetCefString(isolate, message->Get(), message_);
    v8::MaybeLocal<v8::String> source_line = message->GetSourceLine(context);
    if (!source_line.IsEmpty())
      GetCefString(isolate, source_line.ToLocalChecked(), source_line_);

    if (!message->GetScriptResourceName().IsEmpty()) {
      GetCefString(
          isolate,
          message->GetScriptResourceName()->ToString(context).ToLocalChecked(),
          script_);
    }

    v8::Maybe<int> line_number = message->GetLineNumber(context);
    if (!line_number.IsNothing())
      line_number_ = line_number.ToChecked();
    start_position_ = message->GetStartPosition();
    end_position_ = message->GetEndPosition();
    start_column_ = message->GetStartColumn(context).FromJust();
    end_column_ = message->GetEndColumn(context).FromJust();
  }

  CefString GetMessage() override { return message_; }
  CefString GetSourceLine() override { return source_line_; }
  CefString GetScriptResourceName() override { return script_; }
  int GetLineNumber() override { return line_number_; }
  int GetStartPosition() override { return start_position_; }
  int GetEndPosition() override { return end_position_; }
  int GetStartColumn() override { return start_column_; }
  int GetEndColumn() override { return end_column_; }

 protected:
  CefString message_;
  CefString source_line_;
  CefString script_;
  int line_number_;
  int start_position_;
  int end_position_;
  int start_column_;
  int end_column_;

  IMPLEMENT_REFCOUNTING(CefV8ExceptionImpl);
};

void MessageListenerCallbackImpl(v8::Handle<v8::Message> message,
                                 v8::Handle<v8::Value> data) {
  CefRefPtr<CefApp> application = CefAppManager::Get()->GetApplication();
  if (!application.get())
    return;

  CefRefPtr<CefRenderProcessHandler> handler =
      application->GetRenderProcessHandler();
  if (!handler.get())
    return;

  v8::Isolate* isolate = GetIsolateManager()->isolate();
  CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
  v8::Local<v8::StackTrace> v8Stack = message->GetStackTrace();
  CefRefPtr<CefV8StackTrace> stackTrace =
      new CefV8StackTraceImpl(isolate, v8Stack);

  CefRefPtr<CefV8Exception> exception = new CefV8ExceptionImpl(
      static_cast<CefV8ContextImpl*>(context.get())->GetV8Context(), message);

  CefRefPtr<CefBrowser> browser = context->GetBrowser();
  if (browser) {
    handler->OnUncaughtException(browser, context->GetFrame(), context,
                                 exception, stackTrace);
  }
}

}  // namespace

// Global functions.

void CefV8IsolateCreated() {
  g_v8_state.Pointer()->CreateIsolateManager();
}

void CefV8IsolateDestroyed() {
  g_v8_state.Pointer()->DestroyIsolateManager();
}

void CefV8ReleaseContext(v8::Local<v8::Context> context) {
  GetIsolateManager()->ReleaseContext(context);
}

void CefV8SetUncaughtExceptionStackSize(int stack_size) {
  GetIsolateManager()->SetUncaughtExceptionStackSize(stack_size);
}

void CefV8SetWorkerAttributes(int worker_id, const GURL& worker_url) {
  GetIsolateManager()->SetWorkerAttributes(worker_id, worker_url);
}

bool CefRegisterExtension(const CefString& extension_name,
                          const CefString& javascript_code,
                          CefRefPtr<CefV8Handler> handler) {
  // Verify that this method was called on the correct thread.
  CEF_REQUIRE_RT_RETURN(false);

  CefV8IsolateManager* isolate_manager = GetIsolateManager();

  V8TrackString* name = new V8TrackString(extension_name);
  isolate_manager->AddGlobalTrackObject(name);
  V8TrackString* code = new V8TrackString(javascript_code);
  isolate_manager->AddGlobalTrackObject(code);

  if (handler.get()) {
    // The reference will be released when the process exits.
    V8TrackObject* object = new V8TrackObject(isolate_manager->isolate());
    object->SetHandler(handler);
    isolate_manager->AddGlobalTrackObject(object);
  }

  std::unique_ptr<v8::Extension> wrapper(new ExtensionWrapper(
      name->GetString(), code->GetString(), handler.get()));

  content::RenderThread::Get()->RegisterExtension(std::move(wrapper));
  return true;
}

// Helper macros

#define CEF_V8_HAS_ISOLATE() (!!GetIsolateManager())
#define CEF_V8_REQUIRE_ISOLATE_RETURN(var)     \
  if (!CEF_V8_HAS_ISOLATE()) {                 \
    NOTREACHED() << "V8 isolate is not valid"; \
    return var;                                \
  }

#define CEF_V8_CURRENTLY_ON_MLT() \
  (!handle_.get() || handle_->BelongsToCurrentThread())
#define CEF_V8_REQUIRE_MLT_RETURN(var)            \
  CEF_V8_REQUIRE_ISOLATE_RETURN(var);             \
  if (!CEF_V8_CURRENTLY_ON_MLT()) {               \
    NOTREACHED() << "called on incorrect thread"; \
    return var;                                   \
  }

#define CEF_V8_HANDLE_IS_VALID() (handle_.get() && handle_->IsValid())
#define CEF_V8_REQUIRE_VALID_HANDLE_RETURN(ret) \
  CEF_V8_REQUIRE_MLT_RETURN(ret);               \
  if (!CEF_V8_HANDLE_IS_VALID()) {              \
    NOTREACHED() << "V8 handle is not valid";   \
    return ret;                                 \
  }

#define CEF_V8_IS_VALID()                               \
  (CEF_V8_HAS_ISOLATE() && CEF_V8_CURRENTLY_ON_MLT() && \
   CEF_V8_HANDLE_IS_VALID())

#define CEF_V8_REQUIRE_OBJECT_RETURN(ret)        \
  CEF_V8_REQUIRE_VALID_HANDLE_RETURN(ret);       \
  if (type_ != TYPE_OBJECT) {                    \
    NOTREACHED() << "V8 value is not an object"; \
    return ret;                                  \
  }

// CefV8HandleBase

CefV8HandleBase::~CefV8HandleBase() {
  DCHECK(BelongsToCurrentThread());
}

bool CefV8HandleBase::BelongsToCurrentThread() const {
  return task_runner_->RunsTasksInCurrentSequence();
}

CefV8HandleBase::CefV8HandleBase(v8::Isolate* isolate,
                                 v8::Local<v8::Context> context)
    : isolate_(isolate) {
  DCHECK(isolate_);

  CefV8IsolateManager* manager = GetIsolateManager();
  DCHECK(manager);
  DCHECK_EQ(isolate_, manager->isolate());

  task_runner_ = manager->task_runner();
  context_state_ = manager->GetContextState(context);
}

// CefV8Context

// static
CefRefPtr<CefV8Context> CefV8Context::GetCurrentContext() {
  CefRefPtr<CefV8Context> context;
  CEF_V8_REQUIRE_ISOLATE_RETURN(context);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  if (isolate->InContext()) {
    v8::HandleScope handle_scope(isolate);
    context = new CefV8ContextImpl(isolate, isolate->GetCurrentContext());
  }
  return context;
}

// static
CefRefPtr<CefV8Context> CefV8Context::GetEnteredContext() {
  CefRefPtr<CefV8Context> context;
  CEF_V8_REQUIRE_ISOLATE_RETURN(context);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  if (isolate->InContext()) {
    v8::HandleScope handle_scope(isolate);
    context =
        new CefV8ContextImpl(isolate, isolate->GetEnteredOrMicrotaskContext());
  }
  return context;
}

// static
bool CefV8Context::InContext() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  return isolate->InContext();
}

// CefV8ContextImpl

CefV8ContextImpl::CefV8ContextImpl(v8::Isolate* isolate,
                                   v8::Local<v8::Context> context)
    : handle_(new Handle(isolate, context, context)), enter_count_(0) {}

CefV8ContextImpl::~CefV8ContextImpl() {
  DLOG_ASSERT(0 == enter_count_);
}

CefRefPtr<CefTaskRunner> CefV8ContextImpl::GetTaskRunner() {
  return new CefTaskRunnerImpl(handle_->task_runner());
}

bool CefV8ContextImpl::IsValid() {
  return CEF_V8_IS_VALID();
}

CefRefPtr<CefBrowser> CefV8ContextImpl::GetBrowser() {
  CefRefPtr<CefBrowser> browser;
  CEF_V8_REQUIRE_VALID_HANDLE_RETURN(browser);

  blink::WebLocalFrame* webframe = GetWebFrame();
  if (webframe)
    browser = CefBrowserImpl::GetBrowserForMainFrame(webframe->Top());

  return browser;
}

CefRefPtr<CefFrame> CefV8ContextImpl::GetFrame() {
  CefRefPtr<CefFrame> frame;
  CEF_V8_REQUIRE_VALID_HANDLE_RETURN(frame);

  blink::WebLocalFrame* webframe = GetWebFrame();
  if (webframe) {
    CefRefPtr<CefBrowserImpl> browser =
        CefBrowserImpl::GetBrowserForMainFrame(webframe->Top());
    if (browser) {
      frame = browser->GetFrame(render_frame_util::GetIdentifier(webframe));
    }
  }

  return frame;
}

CefRefPtr<CefV8Value> CefV8ContextImpl::GetGlobal() {
  CEF_V8_REQUIRE_VALID_HANDLE_RETURN(nullptr);

  if (blink_glue::IsScriptForbidden())
    return nullptr;

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Context> context = GetV8Context();
  v8::Context::Scope context_scope(context);
  return new CefV8ValueImpl(isolate, context, context->Global());
}

bool CefV8ContextImpl::Enter() {
  CEF_V8_REQUIRE_VALID_HANDLE_RETURN(false);

  if (blink_glue::IsScriptForbidden())
    return false;

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  if (!microtasks_scope_) {
    // Increment the MicrotasksScope recursion level.
    microtasks_scope_.reset(
        new v8::MicrotasksScope(isolate, v8::MicrotasksScope::kRunMicrotasks));
  }

  ++enter_count_;
  handle_->GetNewV8Handle()->Enter();

  return true;
}

bool CefV8ContextImpl::Exit() {
  CEF_V8_REQUIRE_VALID_HANDLE_RETURN(false);

  if (blink_glue::IsScriptForbidden())
    return false;

  if (enter_count_ <= 0) {
    LOG(ERROR) << "Call to CefV8Context::Exit() without matching call to "
                  "CefV8Context::Enter()";
    return false;
  }

  v8::HandleScope handle_scope(handle_->isolate());

  handle_->GetNewV8Handle()->Exit();

  if (--enter_count_ == 0) {
    // Decrement the MicrotasksScope recursion level.
    microtasks_scope_.reset(nullptr);
  }

  return true;
}

bool CefV8ContextImpl::IsSame(CefRefPtr<CefV8Context> that) {
  CEF_V8_REQUIRE_VALID_HANDLE_RETURN(false);

  CefV8ContextImpl* impl = static_cast<CefV8ContextImpl*>(that.get());
  if (!impl || !impl->IsValid())
    return false;

  return (handle_->GetPersistentV8Handle() ==
          impl->handle_->GetPersistentV8Handle());
}

bool CefV8ContextImpl::Eval(const CefString& code,
                            const CefString& script_url,
                            int start_line,
                            CefRefPtr<CefV8Value>& retval,
                            CefRefPtr<CefV8Exception>& exception) {
  retval = nullptr;
  exception = nullptr;

  CEF_V8_REQUIRE_VALID_HANDLE_RETURN(false);

  if (blink_glue::IsScriptForbidden())
    return false;

  if (code.empty()) {
    NOTREACHED() << "invalid input parameter";
    return false;
  }

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Context> context = GetV8Context();
  v8::Context::Scope context_scope(context);

  const blink::WebString& source =
      blink::WebString::FromUTF16(code.ToString16());
  const blink::WebString& source_url =
      blink::WebString::FromUTF16(script_url.ToString16());

  v8::TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);

  v8::Local<v8::Value> func_rv = blink_glue::ExecuteV8ScriptAndReturnValue(
      source, source_url, start_line, context, try_catch);

  if (try_catch.HasCaught()) {
    exception = new CefV8ExceptionImpl(context, try_catch.Message());
    return false;
  } else if (!func_rv.IsEmpty()) {
    retval = new CefV8ValueImpl(isolate, context, func_rv);
    return true;
  }

  NOTREACHED();
  return false;
}

v8::Local<v8::Context> CefV8ContextImpl::GetV8Context() {
  return handle_->GetNewV8Handle();
}

blink::WebLocalFrame* CefV8ContextImpl::GetWebFrame() {
  CEF_REQUIRE_RT();

  if (blink_glue::IsScriptForbidden())
    return nullptr;

  v8::HandleScope handle_scope(handle_->isolate());
  v8::Local<v8::Context> context = GetV8Context();
  v8::Context::Scope context_scope(context);
  return blink::WebLocalFrame::FrameForContext(context);
}

// CefV8ValueImpl::Handle

CefV8ValueImpl::Handle::Handle(v8::Isolate* isolate,
                               v8::Local<v8::Context> context,
                               handleType v,
                               CefTrackNode* tracker)
    : CefV8HandleBase(isolate, context),
      handle_(isolate, v),
      tracker_(tracker),
      should_persist_(false),
      is_set_weak_(false) {}

CefV8ValueImpl::Handle::~Handle() {
  DCHECK(BelongsToCurrentThread());

  if (tracker_) {
    if (is_set_weak_) {
      if (context_state_.get()) {
        // If the associated context is still valid then delete |tracker_|.
        // Otherwise, |tracker_| will already have been deleted.
        if (context_state_->IsValid())
          context_state_->DeleteTrackObject(tracker_);
      } else {
        GetIsolateManager()->DeleteGlobalTrackObject(tracker_);
      }
    } else {
      delete tracker_;
    }
  }

  if (is_set_weak_) {
    isolate_->AdjustAmountOfExternalAllocatedMemory(
        -static_cast<int>(sizeof(Handle)));
  } else {
    // SetWeak was not called so reset now.
    handle_.Reset();
  }
}

CefV8ValueImpl::Handle::handleType CefV8ValueImpl::Handle::GetNewV8Handle(
    bool should_persist) {
  DCHECK(IsValid());
  if (should_persist && !should_persist_)
    should_persist_ = true;
  return handleType::New(isolate(), handle_);
}

CefV8ValueImpl::Handle::persistentType&
CefV8ValueImpl::Handle::GetPersistentV8Handle() {
  return handle_;
}

void CefV8ValueImpl::Handle::SetWeakIfNecessary() {
  if (!BelongsToCurrentThread()) {
    task_runner()->PostTask(
        FROM_HERE,
        base::BindOnce(&CefV8ValueImpl::Handle::SetWeakIfNecessary, this));
    return;
  }

  // Persist the handle (call SetWeak) if:
  // A. The handle has been passed into a V8 function or used as a return value
  //    from a V8 callback, and
  // B. The associated context, if any, is still valid.
  if (should_persist_ && (!context_state_.get() || context_state_->IsValid())) {
    is_set_weak_ = true;

    if (tracker_) {
      if (context_state_.get()) {
        // |tracker_| will be deleted when:
        // A. The associated context is released, or
        // B. SecondWeakCallback is called for the weak handle.
        DCHECK(context_state_->IsValid());
        context_state_->AddTrackObject(tracker_);
      } else {
        // |tracker_| will be deleted when:
        // A. The process shuts down, or
        // B. SecondWeakCallback is called for the weak handle.
        GetIsolateManager()->AddGlobalTrackObject(tracker_);
      }
    }

    isolate_->AdjustAmountOfExternalAllocatedMemory(
        static_cast<int>(sizeof(Handle)));

    // The added reference will be released in SecondWeakCallback.
    AddRef();
    handle_.SetWeak(this, FirstWeakCallback, v8::WeakCallbackType::kParameter);
  }
}

// static
void CefV8ValueImpl::Handle::FirstWeakCallback(
    const v8::WeakCallbackInfo<Handle>& data) {
  Handle* wrapper = data.GetParameter();
  wrapper->handle_.Reset();
  data.SetSecondPassCallback(SecondWeakCallback);
}

// static
void CefV8ValueImpl::Handle::SecondWeakCallback(
    const v8::WeakCallbackInfo<Handle>& data) {
  Handle* wrapper = data.GetParameter();
  wrapper->Release();
}

// CefV8Value

// static
CefRefPtr<CefV8Value> CefV8Value::CreateUndefined() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  CefRefPtr<CefV8ValueImpl> impl = new CefV8ValueImpl(isolate);
  impl->InitUndefined();
  return impl.get();
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateNull() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  CefRefPtr<CefV8ValueImpl> impl = new CefV8ValueImpl(isolate);
  impl->InitNull();
  return impl.get();
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateBool(bool value) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  CefRefPtr<CefV8ValueImpl> impl = new CefV8ValueImpl(isolate);
  impl->InitBool(value);
  return impl.get();
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateInt(int32 value) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  CefRefPtr<CefV8ValueImpl> impl = new CefV8ValueImpl(isolate);
  impl->InitInt(value);
  return impl.get();
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateUInt(uint32 value) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  CefRefPtr<CefV8ValueImpl> impl = new CefV8ValueImpl(isolate);
  impl->InitUInt(value);
  return impl.get();
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateDouble(double value) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  CefRefPtr<CefV8ValueImpl> impl = new CefV8ValueImpl(isolate);
  impl->InitDouble(value);
  return impl.get();
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateDate(const CefTime& value) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  CefRefPtr<CefV8ValueImpl> impl = new CefV8ValueImpl(isolate);
  impl->InitDate(value);
  return impl.get();
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateString(const CefString& value) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);
  v8::Isolate* isolate = GetIsolateManager()->isolate();
  CefRefPtr<CefV8ValueImpl> impl = new CefV8ValueImpl(isolate);
  CefString str(value);
  impl->InitString(str);
  return impl.get();
}

// static
CefRefPtr<CefV8Value> CefV8Value::CreateObject(
    CefRefPtr<CefV8Accessor> accessor,
    CefRefPtr<CefV8Interceptor> interceptor) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);

  v8::Isolate* isolate = GetIsolateManager()->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return nullptr;
  }

  // Create the new V8 object. If an interceptor is passed, create object from
  // template and set property handlers.
  v8::Local<v8::Object> obj;
  if (interceptor.get()) {
    v8::Local<v8::ObjectTemplate> tmpl = v8::ObjectTemplate::New(isolate);
    tmpl->SetHandler(v8::NamedPropertyHandlerConfiguration(
        InterceptorGetterCallbackImpl<v8::Local<v8::Name>>,
        InterceptorSetterCallbackImpl<v8::Local<v8::Name>>, nullptr, nullptr,
        nullptr, v8::Local<v8::Value>(),
        v8::PropertyHandlerFlags::kOnlyInterceptStrings));

    tmpl->SetIndexedPropertyHandler(InterceptorGetterCallbackImpl<uint32_t>,
                                    InterceptorSetterCallbackImpl<uint32_t>);

    v8::MaybeLocal<v8::Object> maybe_object = tmpl->NewInstance(context);
    if (!maybe_object.ToLocal<v8::Object>(&obj)) {
      NOTREACHED() << "Failed to create V8 Object with interceptor";
      return nullptr;
    }
  } else {
    obj = v8::Object::New(isolate);
  }

  // Create a tracker object that will cause the user data and/or accessor
  // and/or interceptor reference to be released when the V8 object is
  // destroyed.
  V8TrackObject* tracker = new V8TrackObject(isolate);
  tracker->SetAccessor(accessor);
  tracker->SetInterceptor(interceptor);

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

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

// static
CefRefPtr<CefV8Value> CefV8Value::CreateArray(int length) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);

  v8::Isolate* isolate = GetIsolateManager()->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return nullptr;
  }

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

  // Create the new V8 array.
  v8::Local<v8::Array> arr = v8::Array::New(isolate, length);

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

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

// static
CefRefPtr<CefV8Value> CefV8Value::CreateArrayBuffer(
    void* buffer,
    size_t length,
    CefRefPtr<CefV8ArrayBufferReleaseCallback> release_callback) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);

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

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

  if (release_callback)
    release_callback->AddRef();

  auto deleter = [](void* data, size_t length, void* deleter_data) {
    auto* release_callback =
        reinterpret_cast<CefV8ArrayBufferReleaseCallback*>(deleter_data);
    if (release_callback) {
      release_callback->ReleaseBuffer(data);
      release_callback->Release();
    }
  };

  std::unique_ptr<v8::BackingStore> backing = v8::ArrayBuffer::NewBackingStore(
      buffer, length, deleter, release_callback.get());
  v8::Local<v8::ArrayBuffer> ab =
      v8::ArrayBuffer::New(isolate, std::move(backing));

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

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

// static
CefRefPtr<CefV8Value> CefV8Value::CreateFunction(
    const CefString& name,
    CefRefPtr<CefV8Handler> handler) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);

  if (!handler.get()) {
    NOTREACHED() << "invalid parameter";
    return nullptr;
  }

  v8::Isolate* isolate = GetIsolateManager()->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return nullptr;
  }

  v8::Local<v8::External> function_data =
      V8FunctionData::Create(isolate, name, handler);

  // Create a new V8 function template.
  v8::Local<v8::FunctionTemplate> tmpl =
      v8::FunctionTemplate::New(isolate, FunctionCallbackImpl, function_data);

  // Retrieve the function object and set the name.
  v8::MaybeLocal<v8::Function> maybe_func = tmpl->GetFunction(context);
  v8::Local<v8::Function> func;
  if (!maybe_func.ToLocal(&func)) {
    NOTREACHED() << "failed to create V8 function";
    return nullptr;
  }

  func->SetName(GetV8String(isolate, name));

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

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

  // Create the CefV8ValueImpl and provide a tracker object that will cause
  // the handler reference to be released when the V8 object is destroyed.
  CefRefPtr<CefV8ValueImpl> impl = new CefV8ValueImpl(isolate);
  impl->InitObject(func, tracker);
  return impl.get();
}

// CefV8ValueImpl

CefV8ValueImpl::CefV8ValueImpl(v8::Isolate* isolate)
    : isolate_(isolate), type_(TYPE_INVALID), rethrow_exceptions_(false) {
  DCHECK(isolate_);
}

CefV8ValueImpl::CefV8ValueImpl(v8::Isolate* isolate,
                               v8::Local<v8::Context> context,
                               v8::Local<v8::Value> value)
    : isolate_(isolate), type_(TYPE_INVALID), rethrow_exceptions_(false) {
  DCHECK(isolate_);
  InitFromV8Value(context, value);
}

CefV8ValueImpl::~CefV8ValueImpl() {
  if (type_ == TYPE_STRING)
    cef_string_clear(&string_value_);
  if (handle_.get())
    handle_->SetWeakIfNecessary();
}

void CefV8ValueImpl::InitFromV8Value(v8::Local<v8::Context> context,
                                     v8::Local<v8::Value> value) {
  if (value->IsUndefined()) {
    InitUndefined();
  } else if (value->IsNull()) {
    InitNull();
  } else if (value->IsTrue()) {
    InitBool(true);
  } else if (value->IsFalse()) {
    InitBool(false);
  } else if (value->IsBoolean()) {
    InitBool(value->ToBoolean(context->GetIsolate())->Value());
  } else if (value->IsInt32()) {
    InitInt(value->ToInt32(context).ToLocalChecked()->Value());
  } else if (value->IsUint32()) {
    InitUInt(value->ToUint32(context).ToLocalChecked()->Value());
  } else if (value->IsNumber()) {
    InitDouble(value->ToNumber(context).ToLocalChecked()->Value());
  } else if (value->IsDate()) {
    // Convert from milliseconds to seconds.
    InitDate(
        CefTime(value->ToNumber(context).ToLocalChecked()->Value() / 1000));
  } else if (value->IsString()) {
    CefString rv;
    GetCefString(context->GetIsolate(),
                 value->ToString(context).ToLocalChecked(), rv);
    InitString(rv);
  } else if (value->IsObject()) {
    InitObject(value, nullptr);
  }
}

void CefV8ValueImpl::InitUndefined() {
  DCHECK_EQ(type_, TYPE_INVALID);
  type_ = TYPE_UNDEFINED;
}

void CefV8ValueImpl::InitNull() {
  DCHECK_EQ(type_, TYPE_INVALID);
  type_ = TYPE_NULL;
}

void CefV8ValueImpl::InitBool(bool value) {
  DCHECK_EQ(type_, TYPE_INVALID);
  type_ = TYPE_BOOL;
  bool_value_ = value;
}

void CefV8ValueImpl::InitInt(int32 value) {
  DCHECK_EQ(type_, TYPE_INVALID);
  type_ = TYPE_INT;
  int_value_ = value;
}

void CefV8ValueImpl::InitUInt(uint32 value) {
  DCHECK_EQ(type_, TYPE_INVALID);
  type_ = TYPE_UINT;
  uint_value_ = value;
}

void CefV8ValueImpl::InitDouble(double value) {
  DCHECK_EQ(type_, TYPE_INVALID);
  type_ = TYPE_DOUBLE;
  double_value_ = value;
}

void CefV8ValueImpl::InitDate(const CefTime& value) {
  DCHECK_EQ(type_, TYPE_INVALID);
  type_ = TYPE_DATE;
  date_value_ = value;
}

void CefV8ValueImpl::InitString(CefString& value) {
  DCHECK_EQ(type_, TYPE_INVALID);
  type_ = TYPE_STRING;
  // Take ownership of the underling string value.
  const cef_string_t* str = value.GetStruct();
  if (str) {
    string_value_ = *str;
    cef_string_t* writable_struct = value.GetWritableStruct();
    writable_struct->str = nullptr;
    writable_struct->length = 0;
  } else {
    string_value_.str = nullptr;
    string_value_.length = 0;
  }
}

void CefV8ValueImpl::InitObject(v8::Local<v8::Value> value,
                                CefTrackNode* tracker) {
  DCHECK_EQ(type_, TYPE_INVALID);
  type_ = TYPE_OBJECT;
  handle_ = new Handle(isolate_, v8::Local<v8::Context>(), value, tracker);
}

v8::Local<v8::Value> CefV8ValueImpl::GetV8Value(bool should_persist) {
  switch (type_) {
    case TYPE_UNDEFINED:
      return v8::Undefined(isolate_);
    case TYPE_NULL:
      return v8::Null(isolate_);
    case TYPE_BOOL:
      return v8::Boolean::New(isolate_, bool_value_);
    case TYPE_INT:
      return v8::Int32::New(isolate_, int_value_);
    case TYPE_UINT:
      return v8::Uint32::New(isolate_, uint_value_);
    case TYPE_DOUBLE:
      return v8::Number::New(isolate_, double_value_);
    case TYPE_DATE:
      // Convert from seconds to milliseconds.
      return v8::Date::New(isolate_->GetCurrentContext(),
                           CefTime(date_value_).GetDoubleT() * 1000)
          .ToLocalChecked();
    case TYPE_STRING:
      return GetV8String(isolate_, CefString(&string_value_));
    case TYPE_OBJECT:
      return handle_->GetNewV8Handle(should_persist);
    default:
      break;
  }

  NOTREACHED() << "Invalid type for CefV8ValueImpl";
  return v8::Local<v8::Value>();
}

bool CefV8ValueImpl::IsValid() {
  if (!CEF_V8_HAS_ISOLATE() || type_ == TYPE_INVALID ||
      (type_ == TYPE_OBJECT &&
       (!handle_->BelongsToCurrentThread() || !handle_->IsValid()))) {
    return false;
  }
  return true;
}

bool CefV8ValueImpl::IsUndefined() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  return (type_ == TYPE_UNDEFINED);
}

bool CefV8ValueImpl::IsNull() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  return (type_ == TYPE_NULL);
}

bool CefV8ValueImpl::IsBool() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  return (type_ == TYPE_BOOL);
}

bool CefV8ValueImpl::IsInt() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  return (type_ == TYPE_INT || type_ == TYPE_UINT);
}

bool CefV8ValueImpl::IsUInt() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  return (type_ == TYPE_INT || type_ == TYPE_UINT);
}

bool CefV8ValueImpl::IsDouble() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  return (type_ == TYPE_INT || type_ == TYPE_UINT || type_ == TYPE_DOUBLE);
}

bool CefV8ValueImpl::IsDate() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  return (type_ == TYPE_DATE);
}

bool CefV8ValueImpl::IsString() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  return (type_ == TYPE_STRING);
}

bool CefV8ValueImpl::IsObject() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  return (type_ == TYPE_OBJECT);
}

bool CefV8ValueImpl::IsArray() {
  CEF_V8_REQUIRE_MLT_RETURN(false);
  if (type_ == TYPE_OBJECT) {
    v8::HandleScope handle_scope(handle_->isolate());
    return handle_->GetNewV8Handle(false)->IsArray();
  } else {
    return false;
  }
}

bool CefV8ValueImpl::IsArrayBuffer() {
  CEF_V8_REQUIRE_MLT_RETURN(false);
  if (type_ == TYPE_OBJECT) {
    v8::HandleScope handle_scope(handle_->isolate());
    return handle_->GetNewV8Handle(false)->IsArrayBuffer();
  } else {
    return false;
  }
}

bool CefV8ValueImpl::IsFunction() {
  CEF_V8_REQUIRE_MLT_RETURN(false);
  if (type_ == TYPE_OBJECT) {
    v8::HandleScope handle_scope(handle_->isolate());
    return handle_->GetNewV8Handle(false)->IsFunction();
  } else {
    return false;
  }
}

bool CefV8ValueImpl::IsSame(CefRefPtr<CefV8Value> that) {
  CEF_V8_REQUIRE_MLT_RETURN(false);

  CefV8ValueImpl* thatValue = static_cast<CefV8ValueImpl*>(that.get());
  if (!thatValue || !thatValue->IsValid() || type_ != thatValue->type_)
    return false;

  switch (type_) {
    case TYPE_UNDEFINED:
    case TYPE_NULL:
      return true;
    case TYPE_BOOL:
      return (bool_value_ == thatValue->bool_value_);
    case TYPE_INT:
      return (int_value_ == thatValue->int_value_);
    case TYPE_UINT:
      return (uint_value_ == thatValue->uint_value_);
    case TYPE_DOUBLE:
      return (double_value_ == thatValue->double_value_);
    case TYPE_DATE:
      return (CefTime(date_value_).GetTimeT() ==
              CefTime(thatValue->date_value_).GetTimeT());
    case TYPE_STRING:
      return (CefString(&string_value_) ==
              CefString(&thatValue->string_value_));
    case TYPE_OBJECT: {
      return (handle_->GetPersistentV8Handle() ==
              thatValue->handle_->GetPersistentV8Handle());
    }
    default:
      break;
  }

  return false;
}

bool CefV8ValueImpl::GetBoolValue() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(false);
  if (type_ == TYPE_BOOL)
    return bool_value_;
  return false;
}

int32 CefV8ValueImpl::GetIntValue() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(0);
  if (type_ == TYPE_INT || type_ == TYPE_UINT)
    return int_value_;
  return 0;
}

uint32 CefV8ValueImpl::GetUIntValue() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(0);
  if (type_ == TYPE_INT || type_ == TYPE_UINT)
    return uint_value_;
  return 0;
}

double CefV8ValueImpl::GetDoubleValue() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(0.);
  if (type_ == TYPE_DOUBLE)
    return double_value_;
  else if (type_ == TYPE_INT)
    return int_value_;
  else if (type_ == TYPE_UINT)
    return uint_value_;
  return 0.;
}

CefTime CefV8ValueImpl::GetDateValue() {
  CEF_V8_REQUIRE_ISOLATE_RETURN(CefTime(0.));
  if (type_ == TYPE_DATE)
    return date_value_;
  return CefTime(0.);
}

CefString CefV8ValueImpl::GetStringValue() {
  CefString rv;
  CEF_V8_REQUIRE_ISOLATE_RETURN(rv);
  if (type_ == TYPE_STRING)
    rv = CefString(&string_value_);
  return rv;
}

bool CefV8ValueImpl::IsUserCreated() {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return false;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  return (tracker != nullptr);
}

bool CefV8ValueImpl::HasException() {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  return (last_exception_.get() != nullptr);
}

CefRefPtr<CefV8Exception> CefV8ValueImpl::GetException() {
  CEF_V8_REQUIRE_OBJECT_RETURN(nullptr);

  return last_exception_;
}

bool CefV8ValueImpl::ClearException() {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  last_exception_ = nullptr;
  return true;
}

bool CefV8ValueImpl::WillRethrowExceptions() {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  return rethrow_exceptions_;
}

bool CefV8ValueImpl::SetRethrowExceptions(bool rethrow) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  rethrow_exceptions_ = rethrow;
  return true;
}

bool CefV8ValueImpl::HasValue(const CefString& key) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return false;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();
  return obj->Has(context, GetV8String(isolate, key)).FromJust();
}

bool CefV8ValueImpl::HasValue(int index) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  if (index < 0) {
    NOTREACHED() << "invalid input parameter";
    return false;
  }

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return false;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();
  return obj->Has(context, index).FromJust();
}

bool CefV8ValueImpl::DeleteValue(const CefString& key) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return false;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  v8::TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);
  v8::Maybe<bool> del = obj->Delete(context, GetV8String(isolate, key));
  return (!HasCaught(context, try_catch) && del.FromJust());
}

bool CefV8ValueImpl::DeleteValue(int index) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  if (index < 0) {
    NOTREACHED() << "invalid input parameter";
    return false;
  }

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return false;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  v8::TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);
  v8::Maybe<bool> del = obj->Delete(context, index);
  return (!HasCaught(context, try_catch) && del.FromJust());
}

CefRefPtr<CefV8Value> CefV8ValueImpl::GetValue(const CefString& key) {
  CEF_V8_REQUIRE_OBJECT_RETURN(nullptr);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return nullptr;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  v8::TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);
  v8::MaybeLocal<v8::Value> ret_value =
      obj->Get(context, GetV8String(isolate, key));
  if (!HasCaught(context, try_catch) && !ret_value.IsEmpty()) {
    return new CefV8ValueImpl(isolate, context, ret_value.ToLocalChecked());
  }
  return nullptr;
}

CefRefPtr<CefV8Value> CefV8ValueImpl::GetValue(int index) {
  CEF_V8_REQUIRE_OBJECT_RETURN(nullptr);

  if (index < 0) {
    NOTREACHED() << "invalid input parameter";
    return nullptr;
  }

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return nullptr;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  v8::TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);
  v8::MaybeLocal<v8::Value> ret_value =
      obj->Get(context, v8::Number::New(isolate, index));
  if (!HasCaught(context, try_catch) && !ret_value.IsEmpty()) {
    return new CefV8ValueImpl(isolate, context, ret_value.ToLocalChecked());
  }
  return nullptr;
}

bool CefV8ValueImpl::SetValue(const CefString& key,
                              CefRefPtr<CefV8Value> value,
                              PropertyAttribute attribute) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  CefV8ValueImpl* impl = static_cast<CefV8ValueImpl*>(value.get());
  if (impl && impl->IsValid()) {
    v8::Isolate* isolate = handle_->isolate();
    v8::HandleScope handle_scope(isolate);

    v8::Local<v8::Context> context = isolate->GetCurrentContext();
    if (context.IsEmpty()) {
      NOTREACHED() << "not currently in a V8 context";
      return false;
    }

    v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
    v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

    v8::TryCatch try_catch(isolate);
    try_catch.SetVerbose(true);
    // TODO(cef): This usage may not exactly match the previous implementation.
    // Set will trigger interceptors and/or accessors whereas DefineOwnProperty
    // will not. It might be better to split this functionality into separate
    // methods.
    if (attribute == V8_PROPERTY_ATTRIBUTE_NONE) {
      v8::Maybe<bool> set =
          obj->Set(context, GetV8String(isolate, key), impl->GetV8Value(true));
      return (!HasCaught(context, try_catch) && set.FromJust());
    } else {
      v8::Maybe<bool> set = obj->DefineOwnProperty(
          context, GetV8String(isolate, key), impl->GetV8Value(true),
          static_cast<v8::PropertyAttribute>(attribute));
      return (!HasCaught(context, try_catch) && set.FromJust());
    }
  } else {
    NOTREACHED() << "invalid input parameter";
    return false;
  }
}

bool CefV8ValueImpl::SetValue(int index, CefRefPtr<CefV8Value> value) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  if (index < 0) {
    NOTREACHED() << "invalid input parameter";
    return false;
  }

  CefV8ValueImpl* impl = static_cast<CefV8ValueImpl*>(value.get());
  if (impl && impl->IsValid()) {
    v8::Isolate* isolate = handle_->isolate();
    v8::HandleScope handle_scope(isolate);

    v8::Local<v8::Context> context = isolate->GetCurrentContext();
    if (context.IsEmpty()) {
      NOTREACHED() << "not currently in a V8 context";
      return false;
    }

    v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
    v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

    v8::TryCatch try_catch(isolate);
    try_catch.SetVerbose(true);
    v8::Maybe<bool> set = obj->Set(context, index, impl->GetV8Value(true));
    return (!HasCaught(context, try_catch) && set.FromJust());
  } else {
    NOTREACHED() << "invalid input parameter";
    return false;
  }
}

bool CefV8ValueImpl::SetValue(const CefString& key,
                              AccessControl settings,
                              PropertyAttribute attribute) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return false;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  CefRefPtr<CefV8Accessor> accessorPtr;

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker)
    accessorPtr = tracker->GetAccessor();

  // Verify that an accessor exists for this object.
  if (!accessorPtr.get())
    return false;

  v8::AccessorNameGetterCallback getter = AccessorNameGetterCallbackImpl;
  v8::AccessorNameSetterCallback setter =
      (attribute & V8_PROPERTY_ATTRIBUTE_READONLY)
          ? nullptr
          : AccessorNameSetterCallbackImpl;

  v8::TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);
  v8::Maybe<bool> set =
      obj->SetAccessor(context, GetV8String(isolate, key), getter, setter, obj,
                       static_cast<v8::AccessControl>(settings),
                       static_cast<v8::PropertyAttribute>(attribute));
  return (!HasCaught(context, try_catch) && set.FromJust());
}

bool CefV8ValueImpl::GetKeys(std::vector<CefString>& keys) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return false;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();
  v8::Local<v8::Array> arr_keys =
      obj->GetPropertyNames(context).ToLocalChecked();

  uint32_t len = arr_keys->Length();
  for (uint32_t i = 0; i < len; ++i) {
    v8::Local<v8::Value> value =
        arr_keys->Get(context, v8::Integer::New(isolate, i)).ToLocalChecked();
    CefString str;
    GetCefString(isolate, value->ToString(context).ToLocalChecked(), str);
    keys.push_back(str);
  }
  return true;
}

bool CefV8ValueImpl::SetUserData(CefRefPtr<CefBaseRefCounted> user_data) {
  CEF_V8_REQUIRE_OBJECT_RETURN(false);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return false;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker) {
    tracker->SetUserData(user_data);
    return true;
  }

  return false;
}

CefRefPtr<CefBaseRefCounted> CefV8ValueImpl::GetUserData() {
  CEF_V8_REQUIRE_OBJECT_RETURN(nullptr);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return nullptr;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker)
    return tracker->GetUserData();

  return nullptr;
}

int CefV8ValueImpl::GetExternallyAllocatedMemory() {
  CEF_V8_REQUIRE_OBJECT_RETURN(0);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return 0;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker)
    return tracker->GetExternallyAllocatedMemory();

  return 0;
}

int CefV8ValueImpl::AdjustExternallyAllocatedMemory(int change_in_bytes) {
  CEF_V8_REQUIRE_OBJECT_RETURN(0);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return 0;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker)
    return tracker->AdjustExternallyAllocatedMemory(change_in_bytes);

  return 0;
}

int CefV8ValueImpl::GetArrayLength() {
  CEF_V8_REQUIRE_OBJECT_RETURN(0);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return 0;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  if (!value->IsArray()) {
    NOTREACHED() << "V8 value is not an array";
    return 0;
  }

  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();
  v8::Local<v8::Array> arr = v8::Local<v8::Array>::Cast(obj);
  return arr->Length();
}

CefRefPtr<CefV8ArrayBufferReleaseCallback>
CefV8ValueImpl::GetArrayBufferReleaseCallback() {
  CEF_V8_REQUIRE_OBJECT_RETURN(nullptr);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return nullptr;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  if (!value->IsArrayBuffer()) {
    NOTREACHED() << "V8 value is not an array buffer";
    return nullptr;
  }

  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();

  V8TrackArrayBuffer* tracker = V8TrackArrayBuffer::Unwrap(context, obj);
  if (tracker)
    return tracker->GetReleaseCallback();

  return nullptr;
}

bool CefV8ValueImpl::NeuterArrayBuffer() {
  CEF_V8_REQUIRE_OBJECT_RETURN(0);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return false;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  if (!value->IsArrayBuffer()) {
    NOTREACHED() << "V8 value is not an array buffer";
    return false;
  }
  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();
  v8::Local<v8::ArrayBuffer> arr = v8::Local<v8::ArrayBuffer>::Cast(obj);
  if (!arr->IsDetachable()) {
    return false;
  }
  arr->Detach();

  return true;
}

CefString CefV8ValueImpl::GetFunctionName() {
  CefString rv;
  CEF_V8_REQUIRE_OBJECT_RETURN(rv);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return rv;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  if (!value->IsFunction()) {
    NOTREACHED() << "V8 value is not a function";
    return rv;
  }

  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();
  v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(obj);
  GetCefString(handle_->isolate(),
               v8::Handle<v8::String>::Cast(func->GetName()), rv);
  return rv;
}

CefRefPtr<CefV8Handler> CefV8ValueImpl::GetFunctionHandler() {
  CEF_V8_REQUIRE_OBJECT_RETURN(nullptr);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  if (context.IsEmpty()) {
    NOTREACHED() << "not currently in a V8 context";
    return nullptr;
  }

  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  if (!value->IsFunction()) {
    NOTREACHED() << "V8 value is not a function";
    return nullptr;
  }

  v8::Local<v8::Object> obj = value->ToObject(context).ToLocalChecked();
  V8TrackObject* tracker = V8TrackObject::Unwrap(context, obj);
  if (tracker)
    return tracker->GetHandler();

  return nullptr;
}

CefRefPtr<CefV8Value> CefV8ValueImpl::ExecuteFunction(
    CefRefPtr<CefV8Value> object,
    const CefV8ValueList& arguments) {
  // An empty context value defaults to the current context.
  CefRefPtr<CefV8Context> context;
  return ExecuteFunctionWithContext(context, object, arguments);
}

CefRefPtr<CefV8Value> CefV8ValueImpl::ExecuteFunctionWithContext(
    CefRefPtr<CefV8Context> context,
    CefRefPtr<CefV8Value> object,
    const CefV8ValueList& arguments) {
  CEF_V8_REQUIRE_OBJECT_RETURN(nullptr);

  v8::Isolate* isolate = handle_->isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Value> value = handle_->GetNewV8Handle(false);
  if (!value->IsFunction()) {
    NOTREACHED() << "V8 value is not a function";
    return nullptr;
  }

  if (context.get() && !context->IsValid()) {
    NOTREACHED() << "invalid V8 context parameter";
    return nullptr;
  }
  if (object.get() && (!object->IsValid() || !object->IsObject())) {
    NOTREACHED() << "invalid V8 object parameter";
    return nullptr;
  }

  int argc = arguments.size();
  if (argc > 0) {
    for (int i = 0; i < argc; ++i) {
      if (!arguments[i].get() || !arguments[i]->IsValid()) {
        NOTREACHED() << "invalid V8 arguments parameter";
        return nullptr;
      }
    }
  }

  v8::Local<v8::Context> context_local;
  if (context.get()) {
    CefV8ContextImpl* context_impl =
        static_cast<CefV8ContextImpl*>(context.get());
    context_local = context_impl->GetV8Context();
  } else {
    context_local = isolate->GetCurrentContext();
  }

  v8::Context::Scope context_scope(context_local);

  v8::Local<v8::Object> obj = value->ToObject(context_local).ToLocalChecked();
  v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(obj);
  v8::Local<v8::Object> recv;

  // Default to the global object if no object was provided.
  if (object.get()) {
    CefV8ValueImpl* recv_impl = static_cast<CefV8ValueImpl*>(object.get());
    recv = v8::Local<v8::Object>::Cast(recv_impl->GetV8Value(true));
  } else {
    recv = context_local->Global();
  }

  v8::Local<v8::Value>* argv = nullptr;
  if (argc > 0) {
    argv = new v8::Local<v8::Value>[argc];
    for (int i = 0; i < argc; ++i) {
      argv[i] =
          static_cast<CefV8ValueImpl*>(arguments[i].get())->GetV8Value(true);
    }
  }

  CefRefPtr<CefV8Value> retval;

  {
    v8::TryCatch try_catch(isolate);
    try_catch.SetVerbose(true);

    v8::MaybeLocal<v8::Value> func_rv = blink_glue::CallV8Function(
        context_local, func, recv, argc, argv, handle_->isolate());

    if (!HasCaught(context_local, try_catch) && !func_rv.IsEmpty()) {
      retval =
          new CefV8ValueImpl(isolate, context_local, func_rv.ToLocalChecked());
    }
  }

  if (argv)
    delete[] argv;

  return retval;
}

bool CefV8ValueImpl::HasCaught(v8::Local<v8::Context> context,
                               v8::TryCatch& try_catch) {
  if (try_catch.HasCaught()) {
    last_exception_ = new CefV8ExceptionImpl(context, try_catch.Message());
    if (rethrow_exceptions_)
      try_catch.ReThrow();
    return true;
  } else {
    if (last_exception_.get())
      last_exception_ = nullptr;
    return false;
  }
}

// CefV8StackTrace

// static
CefRefPtr<CefV8StackTrace> CefV8StackTrace::GetCurrent(int frame_limit) {
  CEF_V8_REQUIRE_ISOLATE_RETURN(nullptr);

  v8::Isolate* isolate = GetIsolateManager()->isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
      isolate, frame_limit, v8::StackTrace::kDetailed);
  if (stackTrace.IsEmpty())
    return nullptr;
  return new CefV8StackTraceImpl(isolate, stackTrace);
}

// CefV8StackTraceImpl

CefV8StackTraceImpl::CefV8StackTraceImpl(v8::Isolate* isolate,
                                         v8::Local<v8::StackTrace> handle) {
  if (!handle.IsEmpty()) {
    int frame_count = handle->GetFrameCount();
    if (frame_count > 0) {
      frames_.reserve(frame_count);
      for (int i = 0; i < frame_count; ++i)
        frames_.push_back(
            new CefV8StackFrameImpl(isolate, handle->GetFrame(isolate, i)));
    }
  }
}

CefV8StackTraceImpl::~CefV8StackTraceImpl() {}

bool CefV8StackTraceImpl::IsValid() {
  return true;
}

int CefV8StackTraceImpl::GetFrameCount() {
  return frames_.size();
}

CefRefPtr<CefV8StackFrame> CefV8StackTraceImpl::GetFrame(int index) {
  if (index < 0 || index >= static_cast<int>(frames_.size()))
    return nullptr;
  return frames_[index];
}

// CefV8StackFrameImpl

CefV8StackFrameImpl::CefV8StackFrameImpl(v8::Isolate* isolate,
                                         v8::Local<v8::StackFrame> handle)
    : line_number_(0), column_(0), is_eval_(false), is_constructor_(false) {
  if (handle.IsEmpty())
    return;
  GetCefString(isolate, handle->GetScriptName(), script_name_);
  GetCefString(isolate, handle->GetScriptNameOrSourceURL(),
               script_name_or_source_url_);
  GetCefString(isolate, handle->GetFunctionName(), function_name_);
  line_number_ = handle->GetLineNumber();
  column_ = handle->GetColumn();
  is_eval_ = handle->IsEval();
  is_constructor_ = handle->IsConstructor();
}

CefV8StackFrameImpl::~CefV8StackFrameImpl() {}

bool CefV8StackFrameImpl::IsValid() {
  return true;
}

CefString CefV8StackFrameImpl::GetScriptName() {
  return script_name_;
}

CefString CefV8StackFrameImpl::GetScriptNameOrSourceURL() {
  return script_name_or_source_url_;
}

CefString CefV8StackFrameImpl::GetFunctionName() {
  return function_name_;
}

int CefV8StackFrameImpl::GetLineNumber() {
  return line_number_;
}

int CefV8StackFrameImpl::GetColumn() {
  return column_;
}

bool CefV8StackFrameImpl::IsEval() {
  return is_eval_;
}

bool CefV8StackFrameImpl::IsConstructor() {
  return is_constructor_;
}

// Enable deprecation warnings on Windows. See http://crbug.com/585142.
#if BUILDFLAG(IS_WIN)
#if defined(__clang__)
#pragma GCC diagnostic pop
#else
#pragma warning(pop)
#endif
#endif
