Commit 5c1d6041 authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Extensions] Add promise support to APIRequestHandler

Add a StartPromiseBasedRequest() method to APIRequestHandler to begin an
API request that returns a Promise.  The promise will be fulfilled or
rejected once the API returns. An API error (i.e., one that would
normally be presented in chrome.runtime.lastError) rejects the promise,
and otherwise it is resolved with the result of the API call.  Add
unittests for the same.

Since promises can only be resolved with a single value, only APIs that
return a single result are supported.

Note this only adds support in the APIRequestHandler class; this is not
currently hooked up to any APIs, and cannot be used directly from
extensions yet.

Bug: 978538

Change-Id: I2bcd7c48b82656187d8303f088211b0557fc0ac4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1626654Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#676985}
parent 139c3ac5
...@@ -67,6 +67,14 @@ class APIRequestHandler { ...@@ -67,6 +67,14 @@ class APIRequestHandler {
v8::Local<v8::Function> custom_callback, v8::Local<v8::Function> custom_callback,
binding::RequestThread thread); binding::RequestThread thread);
// Starts a request and returns a promise, which will be resolved or rejected
// when the request is completed.
std::pair<int, v8::Local<v8::Promise>> StartPromiseBasedRequest(
v8::Local<v8::Context> context,
const std::string& method,
std::unique_ptr<base::ListValue> arguments,
binding::RequestThread thread);
// Adds a pending request for the request handler to manage (and complete via // Adds a pending request for the request handler to manage (and complete via
// CompleteRequest). This is used by renderer-side implementations that // CompleteRequest). This is used by renderer-side implementations that
// shouldn't be dispatched to the browser in the normal flow, but means other // shouldn't be dispatched to the browser in the normal flow, but means other
...@@ -102,15 +110,16 @@ class APIRequestHandler { ...@@ -102,15 +110,16 @@ class APIRequestHandler {
private: private:
class ArgumentAdapter; class ArgumentAdapter;
class AsyncResultHandler;
struct PendingRequest { struct PendingRequest {
PendingRequest( PendingRequest(
v8::Isolate* isolate, v8::Isolate* isolate,
v8::Local<v8::Context> context, v8::Local<v8::Context> context,
const std::string& method_name, const std::string& method_name,
v8::Local<v8::Function> callback, std::unique_ptr<AsyncResultHandler> async_handler,
const base::Optional<std::vector<v8::Local<v8::Value>>>& callback_args,
std::unique_ptr<InteractionProvider::Token> user_gesture_token); std::unique_ptr<InteractionProvider::Token> user_gesture_token);
~PendingRequest(); ~PendingRequest();
PendingRequest(PendingRequest&&); PendingRequest(PendingRequest&&);
PendingRequest& operator=(PendingRequest&&); PendingRequest& operator=(PendingRequest&&);
...@@ -119,13 +128,24 @@ class APIRequestHandler { ...@@ -119,13 +128,24 @@ class APIRequestHandler {
v8::Global<v8::Context> context; v8::Global<v8::Context> context;
std::string method_name; std::string method_name;
// The following are only populated for requests with a callback. std::unique_ptr<AsyncResultHandler> async_handler;
base::Optional<v8::Global<v8::Function>> callback;
base::Optional<std::vector<v8::Global<v8::Value>>> callback_arguments;
// Note: We can't use base::Optional here for derived Token instances. // Note: We can't use base::Optional here for derived Token instances.
std::unique_ptr<InteractionProvider::Token> user_gesture_token; std::unique_ptr<InteractionProvider::Token> user_gesture_token;
}; };
// Returns the next request ID to be used.
int GetNextRequestId();
// Common implementation for starting a request.
void StartRequestImpl(v8::Local<v8::Context> context,
int request_id,
const std::string& method,
std::unique_ptr<base::ListValue> arguments,
binding::RequestThread thread,
std::unique_ptr<AsyncResultHandler> async_handler);
// Common implementation for completing a request.
void CompleteRequestImpl(int request_id, void CompleteRequestImpl(int request_id,
const ArgumentAdapter& arguments, const ArgumentAdapter& arguments,
const std::string& error); const std::string& error);
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/values.h" #include "base/values.h"
#include "extensions/renderer/bindings/api_binding_test.h" #include "extensions/renderer/bindings/api_binding_test.h"
...@@ -554,4 +555,68 @@ TEST_F(APIRequestHandlerTest, ThrowExceptionInCallback) { ...@@ -554,4 +555,68 @@ TEST_F(APIRequestHandlerTest, ThrowExceptionInCallback) {
testing::StartsWith("Error handling response: Error: hello")); 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;
int request_id = -1;
std::tie(request_id, promise) = request_handler->StartPromiseBasedRequest(
context, kMethod, std::make_unique<base::ListValue>(),
binding::RequestThread::UI);
EXPECT_NE(-1, request_id);
ASSERT_FALSE(promise.IsEmpty());
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;
int request_id = -1;
std::tie(request_id, promise) = request_handler->StartPromiseBasedRequest(
context, kMethod, std::make_unique<base::ListValue>(),
binding::RequestThread::UI);
EXPECT_NE(-1, request_id);
ASSERT_FALSE(promise.IsEmpty());
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::ListValue(), 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 } // namespace extensions
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment