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

#include "extensions/renderer/bindings/test_js_runner.h"

#include <ostream>

#include "base/functional/bind.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/renderer/bindings/api_binding_test_util.h"

namespace extensions {

namespace {

// NOTE(devlin): These aren't thread-safe. If we have multi-threaded unittests,
// we'll need to expand these.
bool g_allow_errors = false;
bool g_suspended = false;

std::optional<base::Value> Convert(v8::MaybeLocal<v8::Value> maybe_value,
                                   v8::Local<v8::Context> context) {
  v8::Local<v8::Value> v8_value;
  if (!maybe_value.ToLocal(&v8_value))
    return std::nullopt;

  if (std::unique_ptr<base::Value> value =
          content::V8ValueConverter::Create()->FromV8Value(v8_value, context)) {
    return base::Value::FromUniquePtrValue(std::move(value));
  }
  return std::nullopt;
}

}  // namespace

TestJSRunner::Scope::Scope(std::unique_ptr<JSRunner> runner)
    : runner_(std::move(runner)),
      old_runner_(JSRunner::GetInstanceForTesting()) {
  DCHECK_NE(runner_.get(), old_runner_);
  JSRunner::SetInstanceForTesting(runner_.get());
}

TestJSRunner::Scope::~Scope() {
  DCHECK_EQ(runner_.get(), JSRunner::GetInstanceForTesting());
  JSRunner::SetInstanceForTesting(old_runner_);
}

TestJSRunner::AllowErrors::AllowErrors() {
  DCHECK(!g_allow_errors) << "Nested AllowErrors() blocks are not allowed.";
  g_allow_errors = true;
}

TestJSRunner::AllowErrors::~AllowErrors() {
  DCHECK(g_allow_errors);
  g_allow_errors = false;
}

TestJSRunner::Suspension::Suspension() {
  DCHECK(!g_suspended) << "Nested Suspension() blocks are not allowed.";
  g_suspended = true;
}

TestJSRunner::Suspension::~Suspension() {
  DCHECK(g_suspended);
  g_suspended = false;
  TestJSRunner* test_runner =
      static_cast<TestJSRunner*>(JSRunner::GetInstanceForTesting());
  DCHECK(test_runner);
  test_runner->Flush();
}

TestJSRunner::PendingCall::PendingCall() = default;
TestJSRunner::PendingCall::~PendingCall() = default;
TestJSRunner::PendingCall::PendingCall(PendingCall&& other) = default;

TestJSRunner::TestJSRunner() = default;
TestJSRunner::TestJSRunner(const base::RepeatingClosure& will_call_js)
    : will_call_js_(will_call_js) {}
TestJSRunner::~TestJSRunner() = default;

void TestJSRunner::RunJSFunction(v8::Local<v8::Function> function,
                                 v8::Local<v8::Context> context,
                                 base::span<v8::Local<v8::Value>> args,
                                 ResultCallback callback) {
  if (g_suspended) {
    // Script is suspended. Queue up the call and return.
    v8::Isolate* isolate = v8::Isolate::GetCurrent();
    PendingCall call;
    call.isolate = isolate;
    call.function.Reset(isolate, function);
    call.context.Reset(isolate, context);
    call.arguments.reserve(args.size());
    call.callback = std::move(callback);
    for (auto& arg : args) {
      call.arguments.push_back(v8::Global<v8::Value>(isolate, arg));
    }
    pending_calls_.push_back(std::move(call));
    return;
  }

  // Functions should always run in the scope of the context.
  v8::Context::Scope context_scope(context);

  if (will_call_js_)
    will_call_js_.Run();

  v8::MaybeLocal<v8::Value> result;
  if (g_allow_errors) {
    result =
        function->Call(context, context->Global(), args.size(), GetArgv(args));
  } else {
    result = RunFunctionOnGlobal(function, context, args.size(), GetArgv(args));
  }

  if (callback)
    std::move(callback).Run(context, Convert(result, context));
}

v8::MaybeLocal<v8::Value> TestJSRunner::RunJSFunctionSync(
    v8::Local<v8::Function> function,
    v8::Local<v8::Context> context,
    base::span<v8::Local<v8::Value>> args) {
  // Note: deliberately circumvent g_suspension, since this should only be used
  // in response to JS interaction.
  if (will_call_js_)
    will_call_js_.Run();

  if (g_allow_errors) {
    v8::MaybeLocal<v8::Value> result =
        function->Call(context, context->Global(), args.size(), GetArgv(args));
    return result;
  }
  return RunFunctionOnGlobal(function, context, args.size(), GetArgv(args));
}

void TestJSRunner::Flush() {
  // Move pending_calls_ in case running any pending calls results in more calls
  // into the JSRunner.
  std::vector<PendingCall> calls = std::move(pending_calls_);
  pending_calls_.clear();
  for (auto& call : calls) {
    v8::Isolate* isolate = call.isolate;
    v8::Local<v8::Context> context = call.context.Get(isolate);
    v8::Context::Scope context_scope(context);
    v8::LocalVector<v8::Value> local_arguments(isolate);
    local_arguments.reserve(call.arguments.size());
    for (auto& arg : call.arguments)
      local_arguments.push_back(arg.Get(isolate));
    v8::MaybeLocal<v8::Value> result =
        RunJSFunctionSync(call.function.Get(isolate), context, local_arguments);
    if (call.callback)
      std::move(call.callback).Run(context, Convert(result, context));
  }
}

}  // namespace extensions