chromium/services/accessibility/features/bindings_isolate_holder.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/accessibility/features/bindings_isolate_holder.h"

#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "gin/v8_initializer.h"
#include "v8/include/v8-exception.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-primitive.h"
#include "v8/include/v8-script.h"

namespace ax {

// static
void BindingsIsolateHolder::InitializeV8() {
  // Only initialize V8 for the Accessibility Service once.
  if (gin::IsolateHolder::Initialized())
    return;

#ifdef V8_USE_EXTERNAL_STARTUP_DATA
  gin::V8Initializer::LoadV8Snapshot();
#endif
  gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode,
                                 gin::ArrayBufferAllocator::SharedInstance());
}

BindingsIsolateHolder::BindingsIsolateHolder() = default;

BindingsIsolateHolder::~BindingsIsolateHolder() = default;

void BindingsIsolateHolder::AddObserver(IsolateObserver* observer) {
  observers_.AddObserver(observer);
}

void BindingsIsolateHolder::RemoveObserver(IsolateObserver* observer) {
  observers_.RemoveObserver(observer);
}

void BindingsIsolateHolder::NotifyIsolateWillDestroy() {
  for (IsolateObserver& obs : observers_) {
    obs.OnIsolateWillDestroy();
  }
}

bool BindingsIsolateHolder::ExecuteScriptInContext(const std::string& script) {
  // Enter isolate scope.
  v8::Isolate::Scope isolate_scope(GetIsolate());

  // Creates and enters stack-allocated handle scope.
  // All the Local handles (Local<>) in this function will belong to this
  // HandleScope and will be garbage collected when it goes out of scope in this
  // C++ function.
  v8::HandleScope handle_scope(GetIsolate());

  // Enter the context for compiling and running the script.
  v8::Context::Scope context_scope(GetContext());
  {
    const char* code_c = script.c_str();
    v8::Local<v8::String> source =
        v8::String::NewFromUtf8(GetIsolate(), code_c).ToLocalChecked();

    v8::TryCatch trycatch(GetIsolate());

    // Compile the source code, checking for errors.
    v8::Local<v8::Script> compiled;
    if (!v8::Script::Compile(GetContext(), source).ToLocal(&compiled)) {
      DCHECK(trycatch.HasCaught());
      HandleError(ExceptionToString(trycatch));
      return false;
    }

    // Run the script, checking for errors.
    v8::MaybeLocal<v8::Value> maybe_result = compiled->Run(GetContext());
    if (maybe_result.IsEmpty()) {
      DCHECK(trycatch.HasCaught());
      HandleError(ExceptionToString(trycatch));
      return false;
    }

    return true;
  }
}

void BindingsIsolateHolder::HandleError(const std::string& message) {
  LOG(ERROR) << message;
}

// From chrome/test/base/v8_unit_test.cc.
std::string BindingsIsolateHolder::ExceptionToString(
    const v8::TryCatch& try_catch) {
  std::string str;
  v8::Isolate* isolate = GetIsolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::String::Utf8Value exception(isolate, try_catch.Exception());
  v8::Local<v8::Message> message(try_catch.Message());
  if (message.IsEmpty()) {
    str.append(base::StringPrintf("%s\n", *exception));
  } else {
    v8::String::Utf8Value filename(isolate,
                                   message->GetScriptOrigin().ResourceName());
    int linenum = message->GetLineNumber(context).ToChecked();
    int colnum = message->GetStartColumn(context).ToChecked();
    str.append(base::StringPrintf("%s:%i:%i %s\n", *filename, linenum, colnum,
                                  *exception));
    v8::String::Utf8Value sourceline(
        isolate, message->GetSourceLine(context).ToLocalChecked());
    str.append(base::StringPrintf("%s\n", *sourceline));
  }
  return str;
}

}  // namespace ax