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/exception_handler.h"

#include <optional>
#include <string>
#include <tuple>

#include "base/functional/bind.h"
#include "base/strings/stringprintf.h"
#include "extensions/renderer/bindings/api_binding_test.h"
#include "extensions/renderer/bindings/api_binding_test_util.h"
#include "gin/converter.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace extensions {

namespace {

void PopulateError(std::optional<std::string>* error_out,
                   v8::Local<v8::Context> context,
                   const std::string& error) {
  *error_out = error;
}

void ThrowException(v8::Local<v8::Context> context,
                    const std::string& to_throw,
                    ExceptionHandler* handler) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::TryCatch try_catch(isolate);
  v8::Local<v8::Function> function = FunctionFromString(
      context,
      base::StringPrintf("(function() { throw %s; })", to_throw.c_str()));
  std::ignore = function->Call(context, v8::Undefined(isolate), 0, nullptr);
  ASSERT_TRUE(try_catch.HasCaught());
  handler->HandleException(context, "handled", &try_catch);
}

}  // namespace

using ExceptionHandlerTest = APIBindingTest;

TEST_F(ExceptionHandlerTest, TestBasicHandling) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  std::optional<std::string> logged_error;
  ExceptionHandler handler(base::BindRepeating(&PopulateError, &logged_error));

  ThrowException(context, "new Error('some error')", &handler);

  ASSERT_TRUE(logged_error);
  EXPECT_THAT(*logged_error, testing::StartsWith("handled: Error: some error"));
}

TEST_F(ExceptionHandlerTest, PerContextHandlers) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context_a = MainContext();
  v8::Local<v8::Context> context_b = AddContext();

  std::optional<std::string> logged_error;
  ExceptionHandler handler(base::BindRepeating(&PopulateError, &logged_error));

  v8::Local<v8::Function> custom_handler = FunctionFromString(
      context_a,
      "(function(message, exception) {\n"
      "  this.loggedMessage = message;\n"
      "  this.loggedExceptionMessage = exception && exception.message;\n"
      "})");

  handler.SetHandlerForContext(context_a, custom_handler);
  ThrowException(context_a, "new Error('context a error')", &handler);
  EXPECT_FALSE(logged_error);
  EXPECT_THAT(GetStringPropertyFromObject(context_a->Global(), context_a,
                                          "loggedMessage"),
              testing::StartsWith("\"handled: Error: context a error"));
  EXPECT_EQ("\"context a error\"",
            GetStringPropertyFromObject(context_a->Global(), context_a,
                                        "loggedExceptionMessage"));

  ASSERT_TRUE(context_a->Global()
                  ->Set(context_a,
                        gin::StringToSymbol(isolate(), "loggedMessage"),
                        v8::Undefined(isolate()))
                  .ToChecked());
  ASSERT_TRUE(
      context_a->Global()
          ->Set(context_a,
                gin::StringToSymbol(isolate(), "loggedExceptionMessage"),
                v8::Undefined(isolate()))
          .ToChecked());

  ThrowException(context_b, "new Error('context b error')", &handler);
  ASSERT_TRUE(logged_error);
  EXPECT_THAT(*logged_error,
              testing::StartsWith("handled: Error: context b error"));
  EXPECT_EQ("undefined", GetStringPropertyFromObject(
                             context_a->Global(), context_a, "loggedMessage"));
  EXPECT_EQ("undefined",
            GetStringPropertyFromObject(context_a->Global(), context_a,
                                        "loggedExceptionMessage"));
}

TEST_F(ExceptionHandlerTest, ThrowingNonErrors) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  std::optional<std::string> logged_error;
  ExceptionHandler handler(base::BindRepeating(&PopulateError, &logged_error));

  ThrowException(context, "'hello'", &handler);
  ASSERT_TRUE(logged_error);
  EXPECT_EQ("handled: Uncaught hello", *logged_error);
  logged_error.reset();

  ThrowException(context, "{ message: 'hello' }", &handler);
  ASSERT_TRUE(logged_error);
  EXPECT_EQ("handled: Uncaught #<Object>", *logged_error);
  logged_error.reset();

  ThrowException(context, "{ toString: function() { throw 'goodbye' } }",
                 &handler);
  ASSERT_TRUE(logged_error);
  EXPECT_EQ("handled: Uncaught [object Object]", *logged_error);

  v8::Local<v8::Function> custom_handler =
      FunctionFromString(context,
                         "(function(message, exception) {\n"
                         "  this.loggedMessage = message;\n"
                         "  this.loggedException = exception;\n"
                         "})");

  handler.SetHandlerForContext(context, custom_handler);
  ThrowException(context, "'hello'", &handler);
  EXPECT_EQ(
      "\"handled: Uncaught hello\"",
      GetStringPropertyFromObject(context->Global(), context, "loggedMessage"));
  EXPECT_EQ("\"hello\"", GetStringPropertyFromObject(context->Global(), context,
                                                     "loggedException"));
}

TEST_F(ExceptionHandlerTest, StackTraces) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  std::optional<std::string> logged_error;
  ExceptionHandler handler(base::BindRepeating(&PopulateError, &logged_error));

  {
    v8::TryCatch try_catch(isolate());
    v8::Local<v8::Script> script =
        v8::Script::Compile(context,
                            gin::StringToV8(v8::Isolate::GetCurrent(),
                                            "throw new Error('simple');"))
            .ToLocalChecked();
    ASSERT_TRUE(script->Run(context).IsEmpty());
    ASSERT_TRUE(try_catch.HasCaught());
    handler.HandleException(context, "handled", &try_catch);

    ASSERT_TRUE(logged_error);
    EXPECT_EQ("handled: Error: simple\n    at <anonymous>:1:7", *logged_error);
  }

  logged_error.reset();

  {
    v8::TryCatch try_catch(isolate());
    v8::Local<v8::Function> throw_error_function = FunctionFromString(
        context, "(function() { throw new Error('function'); })");
    std::ignore = throw_error_function->Call(context, v8::Undefined(isolate()),
                                             0, nullptr);
    ASSERT_TRUE(try_catch.HasCaught());
    handler.HandleException(context, "handled", &try_catch);
    ASSERT_TRUE(logged_error);
    EXPECT_EQ("handled: Error: function\n    at <anonymous>:1:21",
              *logged_error);
  }

  logged_error.reset();

  {
    v8::TryCatch try_catch(isolate());
    const char kNestedCall[] =
        "function throwError() { throw new Error('nested'); }\n"
        "function callThrowError() { throwError(); }\n"
        "callThrowError()\n";
    v8::Local<v8::Script> script =
        v8::Script::Compile(
            context, gin::StringToV8(v8::Isolate::GetCurrent(), kNestedCall))
            .ToLocalChecked();
    ASSERT_TRUE(script->Run(context).IsEmpty());
    ASSERT_TRUE(try_catch.HasCaught());
    handler.HandleException(context, "handled", &try_catch);
    ASSERT_TRUE(logged_error);
    const char kExpectedError[] =
        "handled: Error: nested\n"
        "    at throwError (<anonymous>:1:31)\n"
        "    at callThrowError (<anonymous>:2:29)\n"
        "    at <anonymous>:3:1";
    EXPECT_EQ(kExpectedError, *logged_error);
  }
}

}  // namespace extensions