123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901 |
- // Copyright 2016 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "extensions/renderer/bindings/api_request_handler.h"
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/strings/strcat.h"
- #include "base/strings/stringprintf.h"
- #include "base/test/bind.h"
- #include "base/values.h"
- #include "extensions/renderer/bindings/api_binding_test.h"
- #include "extensions/renderer/bindings/api_binding_test_util.h"
- #include "extensions/renderer/bindings/exception_handler.h"
- #include "extensions/renderer/bindings/test_interaction_provider.h"
- #include "extensions/renderer/bindings/test_js_runner.h"
- #include "extensions/renderer/v8_helpers.h"
- #include "gin/converter.h"
- #include "gin/function_template.h"
- #include "gin/public/context_holder.h"
- #include "gin/public/isolate_holder.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- namespace extensions {
- namespace {
- const char kEchoArgs[] =
- "(function() { this.result = Array.from(arguments); })";
- const char kMethod[] = "method";
- // TODO(devlin): We should probably hoist this up to e.g. api_binding_types.h.
- using ArgumentList = std::vector<v8::Local<v8::Value>>;
- // TODO(devlin): Should we move some parts of api_binding_unittest.cc to here?
- } // namespace
- class APIRequestHandlerTest : public APIBindingTest {
- public:
- APIRequestHandlerTest(const APIRequestHandlerTest&) = delete;
- APIRequestHandlerTest& operator=(const APIRequestHandlerTest&) = delete;
- std::unique_ptr<APIRequestHandler> CreateRequestHandler() {
- return std::make_unique<APIRequestHandler>(
- base::DoNothing(),
- APILastError(APILastError::GetParent(), binding::AddConsoleError()),
- exception_handler(), interaction_provider());
- }
- void SaveUserActivationState(v8::Local<v8::Context> context,
- absl::optional<bool>* ran_with_user_gesture) {
- *ran_with_user_gesture =
- interaction_provider()->HasActiveInteraction(context);
- }
- protected:
- APIRequestHandlerTest() {}
- ~APIRequestHandlerTest() override {}
- std::unique_ptr<TestJSRunner::Scope> CreateTestJSRunner() override {
- return std::make_unique<TestJSRunner::Scope>(
- std::make_unique<TestJSRunner>(base::BindRepeating(
- &APIRequestHandlerTest::SetDidRunJS, base::Unretained(this))));
- }
- InteractionProvider* interaction_provider() {
- if (!interaction_provider_)
- interaction_provider_ = std::make_unique<TestInteractionProvider>();
- return interaction_provider_.get();
- }
- ExceptionHandler* exception_handler() {
- if (!exception_handler_) {
- exception_handler_ =
- std::make_unique<ExceptionHandler>(binding::AddConsoleError());
- }
- return exception_handler_.get();
- }
- bool did_run_js() const { return did_run_js_; }
- private:
- void SetDidRunJS() { did_run_js_ = true; }
- bool did_run_js_ = false;
- std::unique_ptr<TestInteractionProvider> interaction_provider_;
- std::unique_ptr<ExceptionHandler> exception_handler_;
- };
- // Tests adding a request to the request handler, and then triggering the
- // response.
- TEST_F(APIRequestHandlerTest, AddRequestAndCompleteRequestTest) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- v8::Local<v8::Function> function = FunctionFromString(context, kEchoArgs);
- ASSERT_FALSE(function.IsEmpty());
- request_handler->StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, function,
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- int request_id = request_handler->last_sent_request_id();
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- const char kArguments[] = "['foo',1,{'prop1':'bar'}]";
- request_handler->CompleteRequest(request_id, ListValueFromString(kArguments),
- std::string());
- EXPECT_TRUE(did_run_js());
- EXPECT_EQ(ReplaceSingleQuotes(kArguments),
- GetStringPropertyFromObject(context->Global(), context, "result"));
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- request_handler->StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- request_id = request_handler->last_sent_request_id();
- EXPECT_NE(-1, request_id);
- request_handler->CompleteRequest(request_id, base::Value::List(),
- std::string());
- }
- // Tests that trying to run non-existent or invalided requests is a no-op.
- TEST_F(APIRequestHandlerTest, InvalidRequestsTest) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- v8::Local<v8::Function> function = FunctionFromString(context, kEchoArgs);
- ASSERT_FALSE(function.IsEmpty());
- request_handler->StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, function,
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- int request_id = request_handler->last_sent_request_id();
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- // Try running with a non-existent request id.
- int fake_request_id = 42;
- request_handler->CompleteRequest(
- fake_request_id, ListValueFromString("['foo']"), std::string());
- EXPECT_FALSE(did_run_js());
- // Try running with a request from an invalidated context.
- request_handler->InvalidateContext(context);
- request_handler->CompleteRequest(request_id, ListValueFromString("['foo']"),
- std::string());
- EXPECT_FALSE(did_run_js());
- }
- TEST_F(APIRequestHandlerTest, MultipleRequestsAndContexts) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context_a = MainContext();
- v8::Local<v8::Context> context_b = AddContext();
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- // By having both different arguments and different behaviors in the
- // callbacks, we can easily verify that the right function is called in the
- // right context.
- v8::Local<v8::Function> function_a = FunctionFromString(
- context_a, "(function(res) { this.result = res + 'alpha'; })");
- v8::Local<v8::Function> function_b = FunctionFromString(
- context_b, "(function(res) { this.result = res + 'beta'; })");
- request_handler->StartRequest(
- context_a, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, function_a,
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- int request_a = request_handler->last_sent_request_id();
- request_handler->StartRequest(
- context_b, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, function_b,
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- int request_b = request_handler->last_sent_request_id();
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_a, request_b));
- request_handler->CompleteRequest(
- request_a, ListValueFromString("['response_a:']"), std::string());
- EXPECT_TRUE(did_run_js());
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_b));
- EXPECT_EQ(
- ReplaceSingleQuotes("'response_a:alpha'"),
- GetStringPropertyFromObject(context_a->Global(), context_a, "result"));
- request_handler->CompleteRequest(
- request_b, ListValueFromString("['response_b:']"), std::string());
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- EXPECT_EQ(
- ReplaceSingleQuotes("'response_b:beta'"),
- GetStringPropertyFromObject(context_b->Global(), context_b, "result"));
- }
- TEST_F(APIRequestHandlerTest, CustomCallbackArguments) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- v8::Local<v8::Function> custom_callback =
- FunctionFromString(context, kEchoArgs);
- v8::Local<v8::Function> callback = FunctionFromString(
- context, "(function(arg) {this.callbackCalled = arg})");
- ASSERT_FALSE(callback.IsEmpty());
- ASSERT_FALSE(custom_callback.IsEmpty());
- request_handler->StartRequest(
- context, "method", std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, callback, custom_callback,
- binding::ResultModifierFunction());
- int request_id = request_handler->last_sent_request_id();
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- request_handler->CompleteRequest(
- request_id, ListValueFromString("['response', 'arguments']"),
- std::string());
- EXPECT_TRUE(did_run_js());
- v8::Local<v8::Array> result;
- ASSERT_TRUE(
- GetPropertyFromObjectAs(context->Global(), context, "result", &result));
- ArgumentList args;
- ASSERT_TRUE(gin::Converter<ArgumentList>::FromV8(isolate(), result, &args));
- ASSERT_EQ(3u, args.size());
- EXPECT_TRUE(args[0]->IsFunction());
- EXPECT_EQ(R"("response")", V8ToString(args[1], context));
- EXPECT_EQ(R"("arguments")", V8ToString(args[2], context));
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- // The function passed to the custom callback isn't actually the same callback
- // that was passed in when calling the API, but invoking it below should still
- // result in the original callback being run.
- EXPECT_TRUE(
- GetPropertyFromObject(context->Global(), context, "callbackCalled")
- ->IsUndefined());
- v8::Local<v8::Value> callback_args[] = {gin::StringToV8(isolate(), "foo")};
- RunFunctionOnGlobal(args[0].As<v8::Function>(), context, 1, callback_args);
- EXPECT_EQ(R"("foo")", GetStringPropertyFromObject(context->Global(), context,
- "callbackCalled"));
- }
- TEST_F(APIRequestHandlerTest, CustomCallbackWithErrorInExtensionCallback) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- auto add_console_error = [](absl::optional<std::string>* error_out,
- v8::Local<v8::Context> context,
- const std::string& error) { *error_out = error; };
- absl::optional<std::string> logged_error;
- ExceptionHandler exception_handler(
- base::BindRepeating(add_console_error, &logged_error));
- APIRequestHandler request_handler(
- base::DoNothing(),
- APILastError(APILastError::GetParent(), binding::AddConsoleError()),
- &exception_handler, interaction_provider());
- constexpr char kExtensionCallback[] =
- R"((function() {
- this.callbackCalled = true;
- throw new Error('hello');
- }))";
- v8::Local<v8::Function> callback_throwing_error =
- FunctionFromString(context, kExtensionCallback);
- constexpr char kCustomCallback[] =
- R"((function(callback) {
- this.customCallbackCalled = true;
- callback();
- }))";
- v8::Local<v8::Function> custom_callback =
- FunctionFromString(context, kCustomCallback);
- ASSERT_FALSE(callback_throwing_error.IsEmpty());
- ASSERT_FALSE(custom_callback.IsEmpty());
- request_handler.StartRequest(
- context, "method", std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, callback_throwing_error,
- custom_callback, binding::ResultModifierFunction());
- int request_id = request_handler.last_sent_request_id();
- EXPECT_THAT(request_handler.GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- v8::TryCatch try_catch(isolate());
- {
- TestJSRunner::AllowErrors allow_errors;
- request_handler.CompleteRequest(request_id, base::Value::List(),
- std::string());
- }
- EXPECT_TRUE(did_run_js());
- EXPECT_TRUE(request_handler.GetPendingRequestIdsForTesting().empty());
- EXPECT_EQ("true", GetStringPropertyFromObject(context->Global(), context,
- "customCallbackCalled"));
- EXPECT_EQ("true", GetStringPropertyFromObject(context->Global(), context,
- "callbackCalled"));
- // The `try_catch` should not have caught an error. This is important to not
- // disrupt our bindings code (or other running JS) when asynchronously
- // returning from an API call. Instead, the error should be caught and handled
- // by the exception handler.
- EXPECT_FALSE(try_catch.HasCaught());
- ASSERT_TRUE(logged_error);
- EXPECT_THAT(*logged_error,
- testing::StartsWith("Error handling response: Error: hello"));
- }
- TEST_F(APIRequestHandlerTest, CustomCallbackPromiseBased) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- v8::Local<v8::Function> custom_callback =
- FunctionFromString(context, kEchoArgs);
- ASSERT_FALSE(custom_callback.IsEmpty());
- v8::Local<v8::Promise> promise = request_handler->StartRequest(
- context, "method", std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
- custom_callback, binding::ResultModifierFunction());
- ASSERT_FALSE(promise.IsEmpty());
- int request_id = request_handler->last_sent_request_id();
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- request_handler->CompleteRequest(
- request_id, ListValueFromString("['response', 'arguments']"),
- std::string());
- EXPECT_TRUE(did_run_js());
- v8::Local<v8::Array> result;
- ASSERT_TRUE(
- GetPropertyFromObjectAs(context->Global(), context, "result", &result));
- ArgumentList args;
- ASSERT_TRUE(gin::Converter<ArgumentList>::FromV8(isolate(), result, &args));
- ASSERT_EQ(3u, args.size());
- // Even though this is a promise based request the custom callbacks expect a
- // function argument to be passed to them, hence why we get a function here.
- // Invoking the callback however, should still result in the promise being
- // resolved.
- EXPECT_TRUE(args[0]->IsFunction());
- EXPECT_EQ(R"("response")", V8ToString(args[1], context));
- EXPECT_EQ(R"("arguments")", V8ToString(args[2], context));
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- EXPECT_EQ(v8::Promise::kPending, promise->State());
- v8::Local<v8::Value> callback_args[] = {gin::StringToV8(isolate(), "foo")};
- RunFunctionOnGlobal(args[0].As<v8::Function>(), context, 1, callback_args);
- EXPECT_EQ(v8::Promise::kFulfilled, promise->State());
- EXPECT_EQ(R"("foo")", V8ToString(promise->Result(), context));
- }
- // Test that having a custom callback without an extension-provided callback
- // doesn't crash.
- TEST_F(APIRequestHandlerTest, CustomCallbackArgumentsWithEmptyCallback) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- v8::Local<v8::Function> custom_callback =
- FunctionFromString(context, kEchoArgs);
- ASSERT_FALSE(custom_callback.IsEmpty());
- v8::Local<v8::Function> empty_callback;
- request_handler->StartRequest(
- context, "method", std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kNone, empty_callback, custom_callback,
- binding::ResultModifierFunction());
- int request_id = request_handler->last_sent_request_id();
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- request_handler->CompleteRequest(request_id, base::Value::List(),
- std::string());
- EXPECT_TRUE(did_run_js());
- v8::Local<v8::Array> result;
- ASSERT_TRUE(
- GetPropertyFromObjectAs(context->Global(), context, "result", &result));
- ArgumentList args;
- ASSERT_TRUE(gin::Converter<ArgumentList>::FromV8(isolate(), result, &args));
- ASSERT_EQ(1u, args.size());
- EXPECT_TRUE(args[0]->IsUndefined());
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- }
- TEST_F(APIRequestHandlerTest, ResultModifier) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- binding::ResultModifierFunction result_modifier =
- base::BindOnce([](const std::vector<v8::Local<v8::Value>>& result_args,
- v8::Local<v8::Context> context,
- binding::AsyncResponseType async_type) {
- EXPECT_EQ(1u, result_args.size());
- EXPECT_TRUE(result_args[0]->IsObject());
- v8::Local<v8::Object> result_obj = result_args[0].As<v8::Object>();
- v8::Local<v8::Value> prop_1;
- bool success =
- v8_helpers::GetProperty(context, result_obj, "prop1", &prop_1);
- DCHECK(success);
- v8::Local<v8::Value> prop_2;
- success =
- v8_helpers::GetProperty(context, result_obj, "prop2", &prop_2);
- DCHECK(success);
- std::vector<v8::Local<v8::Value>> new_args{prop_1, prop_2};
- return new_args;
- });
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- v8::Local<v8::Function> callback = FunctionFromString(
- context, "(function(arg1, arg2) {this.arg1 = arg1; this.arg2 = arg2});");
- ASSERT_FALSE(callback.IsEmpty());
- request_handler->StartRequest(
- context, "method", std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, callback,
- v8::Local<v8::Function>(), std::move(result_modifier));
- int request_id = request_handler->last_sent_request_id();
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- request_handler->CompleteRequest(
- request_id, ListValueFromString("[{'prop1':'foo', 'prop2':'bar'}]"),
- std::string());
- EXPECT_TRUE(did_run_js());
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- EXPECT_EQ(R"("foo")",
- GetStringPropertyFromObject(context->Global(), context, "arg1"));
- EXPECT_EQ(R"("bar")",
- GetStringPropertyFromObject(context->Global(), context, "arg2"));
- }
- // Test user gestures being curried around for API requests.
- TEST_F(APIRequestHandlerTest, UserGestureTest) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- // Set up a callback to be used with the request so we can check if a user
- // gesture was active.
- absl::optional<bool> ran_with_user_gesture;
- v8::Local<v8::FunctionTemplate> function_template =
- gin::CreateFunctionTemplate(
- isolate(),
- base::BindRepeating(&APIRequestHandlerTest::SaveUserActivationState,
- base::Unretained(this), context,
- &ran_with_user_gesture));
- v8::Local<v8::Function> v8_callback =
- function_template->GetFunction(context).ToLocalChecked();
- // Try first without a user gesture.
- request_handler->StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, v8_callback,
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- int request_id = request_handler->last_sent_request_id();
- request_handler->CompleteRequest(request_id, ListValueFromString("[]"),
- std::string());
- ASSERT_TRUE(ran_with_user_gesture);
- EXPECT_FALSE(*ran_with_user_gesture);
- ran_with_user_gesture.reset();
- // Next try calling with a user gesture. Since a gesture will be active at the
- // time of the call, it should also be active during the callback.
- ScopedTestUserActivation test_user_activation;
- // TODO(devlin): This isn't quite right with UAv1/UAv2. V1 should properly
- // activate a new user gesture on the stack, and v2 should rely on the gesture
- // being persisted (or generated from the browser). We should clean this up.
- EXPECT_TRUE(interaction_provider()->HasActiveInteraction(context));
- request_handler->StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, v8_callback,
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- request_id = request_handler->last_sent_request_id();
- request_handler->CompleteRequest(request_id, ListValueFromString("[]"),
- std::string());
- ASSERT_TRUE(ran_with_user_gesture);
- EXPECT_TRUE(*ran_with_user_gesture);
- // Sanity check: the callback doesn't change the state
- EXPECT_TRUE(interaction_provider()->HasActiveInteraction(context));
- }
- TEST_F(APIRequestHandlerTest, SettingLastError) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- absl::optional<std::string> logged_error;
- auto get_parent = [](v8::Local<v8::Context> context,
- v8::Local<v8::Object>* secondary_parent) {
- return context->Global();
- };
- auto log_error = [](absl::optional<std::string>* logged_error,
- v8::Local<v8::Context> context,
- const std::string& error) { *logged_error = error; };
- APIRequestHandler request_handler(
- base::DoNothing(),
- APILastError(base::BindRepeating(get_parent),
- base::BindRepeating(log_error, &logged_error)),
- exception_handler(), interaction_provider());
- const char kReportExposedLastError[] =
- "(function() {\n"
- " if (this.lastError)\n"
- " this.seenLastError = this.lastError.message;\n"
- "})";
- auto get_exposed_error = [context]() {
- return GetStringPropertyFromObject(context->Global(), context,
- "seenLastError");
- };
- {
- // Test a successful function call. No last error should be emitted to the
- // console or exposed to the callback.
- v8::Local<v8::Function> callback =
- FunctionFromString(context, kReportExposedLastError);
- request_handler.StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, callback,
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- int request_id = request_handler.last_sent_request_id();
- request_handler.CompleteRequest(request_id, base::Value::List(),
- std::string());
- EXPECT_FALSE(logged_error);
- EXPECT_EQ("undefined", get_exposed_error());
- logged_error.reset();
- }
- {
- // Test a function call resulting in an error. Since the callback checks the
- // last error, no error should be logged to the console (but it should be
- // exposed to the callback).
- v8::Local<v8::Function> callback =
- FunctionFromString(context, kReportExposedLastError);
- request_handler.StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, callback,
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- int request_id = request_handler.last_sent_request_id();
- request_handler.CompleteRequest(request_id, base::Value::List(),
- "some error");
- EXPECT_FALSE(logged_error);
- EXPECT_EQ("\"some error\"", get_exposed_error());
- logged_error.reset();
- }
- {
- // Test a function call resulting in an error that goes unchecked by the
- // callback. The error should be logged.
- v8::Local<v8::Function> callback =
- FunctionFromString(context, "(function() {})");
- request_handler.StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, callback,
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- int request_id = request_handler.last_sent_request_id();
- request_handler.CompleteRequest(request_id, base::Value::List(),
- "some error");
- ASSERT_TRUE(logged_error);
- EXPECT_EQ("Unchecked runtime.lastError: some error", *logged_error);
- logged_error.reset();
- }
- {
- // Test a function call resulting in an error with only a custom callback,
- // and no author-script-provided callback. The error should be logged.
- v8::Local<v8::Function> custom_callback =
- FunctionFromString(context, "(function() {})");
- request_handler.StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
- custom_callback, binding::ResultModifierFunction());
- int request_id = request_handler.last_sent_request_id();
- request_handler.CompleteRequest(request_id, base::Value::List(),
- "some error");
- ASSERT_TRUE(logged_error);
- EXPECT_EQ("Unchecked runtime.lastError: some error", *logged_error);
- logged_error.reset();
- }
- {
- // Test a function call resulting in an error that does not have an
- // associated callback callback. The error should be logged.
- request_handler.StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- int request_id = request_handler.last_sent_request_id();
- request_handler.CompleteRequest(request_id, base::Value::List(),
- "some error");
- ASSERT_TRUE(logged_error);
- EXPECT_EQ("Unchecked runtime.lastError: some error", *logged_error);
- logged_error.reset();
- }
- {
- // Test a function call resulting in an error for a request handler that has
- // an associated result modifier. The result modifier should never be called
- // and since the callback checks last error no error should be logged to the
- // console.
- bool result_modifier_called = false;
- auto result_modifier =
- [&result_modifier_called](
- const std::vector<v8::Local<v8::Value>>& result_args,
- v8::Local<v8::Context> context,
- binding::AsyncResponseType async_type) {
- result_modifier_called = true;
- return result_args;
- };
- v8::Local<v8::Function> callback =
- FunctionFromString(context, kReportExposedLastError);
- request_handler.StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kCallback, callback,
- v8::Local<v8::Function>(), base::BindLambdaForTesting(result_modifier));
- int request_id = request_handler.last_sent_request_id();
- request_handler.CompleteRequest(request_id, base::Value::List(),
- "some error");
- EXPECT_FALSE(logged_error);
- EXPECT_EQ("\"some error\"", get_exposed_error());
- EXPECT_FALSE(result_modifier_called);
- logged_error.reset();
- }
- }
- TEST_F(APIRequestHandlerTest, AddPendingRequestCallback) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- bool dispatched_request = false;
- auto handle_request = [](bool* dispatched_request,
- std::unique_ptr<APIRequestHandler::Request> request,
- v8::Local<v8::Context> context) {
- *dispatched_request = true;
- };
- APIRequestHandler request_handler(
- base::BindRepeating(handle_request, &dispatched_request),
- APILastError(APILastError::GetParent(), binding::AddConsoleError()),
- exception_handler(), interaction_provider());
- EXPECT_TRUE(request_handler.GetPendingRequestIdsForTesting().empty());
- v8::Local<v8::Function> function = FunctionFromString(context, kEchoArgs);
- ASSERT_FALSE(function.IsEmpty());
- auto details = request_handler.AddPendingRequest(
- context, binding::AsyncResponseType::kCallback, function,
- binding::ResultModifierFunction());
- int request_id = details.request_id;
- EXPECT_TRUE(details.promise.IsEmpty());
- EXPECT_THAT(request_handler.GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- // Even though we add a pending request, we shouldn't have dispatched anything
- // because AddPendingRequest() is intended for renderer-side implementations.
- EXPECT_FALSE(dispatched_request);
- const char kArguments[] = "['foo',1,{'prop1':'bar'}]";
- request_handler.CompleteRequest(request_id, ListValueFromString(kArguments),
- std::string());
- EXPECT_EQ(ReplaceSingleQuotes(kArguments),
- GetStringPropertyFromObject(context->Global(), context, "result"));
- EXPECT_TRUE(request_handler.GetPendingRequestIdsForTesting().empty());
- EXPECT_FALSE(dispatched_request);
- }
- TEST_F(APIRequestHandlerTest, AddPendingRequestPromise) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- bool dispatched_request = false;
- auto handle_request = [](bool* dispatched_request,
- std::unique_ptr<APIRequestHandler::Request> request,
- v8::Local<v8::Context> context) {
- *dispatched_request = true;
- };
- APIRequestHandler request_handler(
- base::BindRepeating(handle_request, &dispatched_request),
- APILastError(APILastError::GetParent(), binding::AddConsoleError()),
- exception_handler(), interaction_provider());
- EXPECT_TRUE(request_handler.GetPendingRequestIdsForTesting().empty());
- auto details = request_handler.AddPendingRequest(
- context, binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
- binding::ResultModifierFunction());
- int request_id = details.request_id;
- v8::Local<v8::Promise> promise = details.promise;
- EXPECT_THAT(request_handler.GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- ASSERT_FALSE(promise.IsEmpty());
- EXPECT_EQ(v8::Promise::kPending, promise->State());
- // Even though we add a pending request, we shouldn't have dispatched anything
- // because AddPendingRequest() is intended for renderer-side implementations.
- EXPECT_FALSE(dispatched_request);
- request_handler.CompleteRequest(
- request_id, ListValueFromString("[{'foo': 'bar'}]"), std::string());
- ASSERT_EQ(v8::Promise::kFulfilled, promise->State());
- EXPECT_EQ(R"({"foo":"bar"})", V8ToString(promise->Result(), context));
- EXPECT_TRUE(request_handler.GetPendingRequestIdsForTesting().empty());
- EXPECT_FALSE(dispatched_request);
- }
- TEST_F(APIRequestHandlerTest, AddPendingRequestWithResultModifier) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- binding::ResultModifierFunction result_modifier =
- base::BindOnce([](const std::vector<v8::Local<v8::Value>>& result_args,
- v8::Local<v8::Context> context,
- binding::AsyncResponseType async_type) {
- DCHECK_EQ(1u, result_args.size());
- DCHECK(result_args[0]->IsObject());
- v8::Local<v8::Object> result_obj = result_args[0].As<v8::Object>();
- v8::Local<v8::Value> prop_1;
- bool success =
- v8_helpers::GetProperty(context, result_obj, "prop1", &prop_1);
- DCHECK(success);
- v8::Local<v8::Value> prop_2;
- success =
- v8_helpers::GetProperty(context, result_obj, "prop2", &prop_2);
- DCHECK(success);
- std::vector<v8::Local<v8::Value>> new_args{prop_1, prop_2};
- return new_args;
- });
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- v8::Local<v8::Function> function = FunctionFromString(context, kEchoArgs);
- ASSERT_FALSE(function.IsEmpty());
- auto details = request_handler->AddPendingRequest(
- context, binding::AsyncResponseType::kCallback, function,
- std::move(result_modifier));
- int request_id = details.request_id;
- EXPECT_TRUE(details.promise.IsEmpty());
- const char kArguments[] = "[{'prop1':'bar', 'prop2':'baz'}]";
- request_handler->CompleteRequest(request_id, ListValueFromString(kArguments),
- std::string());
- EXPECT_EQ(R"(["bar","baz"])",
- GetStringPropertyFromObject(context->Global(), context, "result"));
- }
- // Tests that throwing an exception in a callback is properly handled.
- TEST_F(APIRequestHandlerTest, ThrowExceptionInCallback) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- auto add_console_error = [](absl::optional<std::string>* error_out,
- v8::Local<v8::Context> context,
- const std::string& error) { *error_out = error; };
- absl::optional<std::string> logged_error;
- ExceptionHandler exception_handler(
- base::BindRepeating(add_console_error, &logged_error));
- APIRequestHandler request_handler(
- base::DoNothing(),
- APILastError(APILastError::GetParent(), binding::AddConsoleError()),
- &exception_handler, interaction_provider());
- v8::TryCatch outer_try_catch(isolate());
- v8::Local<v8::Function> callback_throwing_error =
- FunctionFromString(context, "(function() { throw new Error('hello'); })");
- auto details = request_handler.AddPendingRequest(
- context, binding::AsyncResponseType::kCallback, callback_throwing_error,
- binding::ResultModifierFunction());
- int request_id = details.request_id;
- EXPECT_TRUE(details.promise.IsEmpty());
- {
- TestJSRunner::AllowErrors allow_errors;
- request_handler.CompleteRequest(request_id, base::Value::List(),
- std::string());
- }
- // |outer_try_catch| should not have caught an error. This is important to not
- // disrupt our bindings code (or other running JS) when asynchronously
- // returning from an API call. Instead, the error should be caught and handled
- // by the exception handler.
- EXPECT_FALSE(outer_try_catch.HasCaught());
- ASSERT_TRUE(logged_error);
- EXPECT_THAT(*logged_error,
- testing::StartsWith("Error handling response: Error: hello"));
- }
- // Tests promise-based requests with the promise being fulfilled.
- TEST_F(APIRequestHandlerTest, PromiseBasedRequests_Fulfilled) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- v8::Local<v8::Promise> promise = request_handler->StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- ASSERT_FALSE(promise.IsEmpty());
- int request_id = request_handler->last_sent_request_id();
- EXPECT_NE(-1, request_id);
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- EXPECT_EQ(v8::Promise::kPending, promise->State());
- request_handler->CompleteRequest(request_id, ListValueFromString("['foo']"),
- std::string());
- ASSERT_EQ(v8::Promise::kFulfilled, promise->State());
- EXPECT_EQ(R"("foo")", V8ToString(promise->Result(), context));
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- }
- // Tests promise-based requests with the promise being rejected.
- TEST_F(APIRequestHandlerTest, PromiseBasedRequests_Rejected) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- v8::Local<v8::Promise> promise = request_handler->StartRequest(
- context, kMethod, std::make_unique<base::ListValue>(),
- binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
- v8::Local<v8::Function>(), binding::ResultModifierFunction());
- ASSERT_FALSE(promise.IsEmpty());
- int request_id = request_handler->last_sent_request_id();
- EXPECT_NE(-1, request_id);
- EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
- testing::UnorderedElementsAre(request_id));
- EXPECT_EQ(v8::Promise::kPending, promise->State());
- constexpr char kError[] = "Something went wrong!";
- request_handler->CompleteRequest(request_id, base::Value::List(), kError);
- ASSERT_EQ(v8::Promise::kRejected, promise->State());
- v8::Local<v8::Value> result = promise->Result();
- ASSERT_FALSE(result.IsEmpty());
- EXPECT_EQ(
- base::StrCat({"Error: ", kError}),
- gin::V8ToString(isolate(), result->ToString(context).ToLocalChecked()));
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- }
- } // namespace extensions
|