Commit 93527953 authored by Tim Judkins's avatar Tim Judkins Committed by Commit Bot

[Extensions Bindings] Add support for returns_async schema format

Changes the expected schema format that indicates promise support to no
longer specify a callback in the parameters, but instead supply a
"returns_async" key. This key species the format of the return value to
the promise, or to the callback if one is supplied. Also adjusts
associated tests to check for such.

Bug: 978538
Change-Id: Ifcb0b8e8152fd6b6c9049e2d856b10d577c955fa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2253205Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Commit-Queue: Tim Judkins <tjudkins@chromium.org>
Cr-Commit-Position: refs/heads/master@{#782031}
parent bce93d49
...@@ -65,23 +65,31 @@ struct SignaturePair { ...@@ -65,23 +65,31 @@ struct SignaturePair {
std::unique_ptr<APISignature> callback_signature; std::unique_ptr<APISignature> callback_signature;
}; };
SignaturePair GetAPISignatureFromDictionary(const base::DictionaryValue* dict) { SignaturePair GetAPISignatureFromDictionary(const base::Value* dict) {
const base::ListValue* params = nullptr; const base::Value* params =
CHECK(dict->GetList("parameters", &params)); dict->FindKeyOfType("parameters", base::Value::Type::LIST);
CHECK(params);
bool supports_promises = false; // The inclusion of the "returns_async" property indicates that an API
dict->GetBoolean("supportsPromises", &supports_promises); // supports promises.
const base::Value* returns_async =
dict->FindKeyOfType("returns_async", base::Value::Type::DICTIONARY);
SignaturePair result; SignaturePair result;
result.method_signature = std::make_unique<APISignature>(*params); result.method_signature =
std::make_unique<APISignature>(*params, returns_async);
result.method_signature->set_promise_support( result.method_signature->set_promise_support(
supports_promises ? binding::PromiseSupport::kAllowed returns_async && APIBinding::enable_promise_support_for_testing
? binding::PromiseSupport::kAllowed
: binding::PromiseSupport::kDisallowed); : binding::PromiseSupport::kDisallowed);
// If response validation is enabled, parse the callback signature. Otherwise, // If response validation is enabled, parse the callback signature. Otherwise,
// there's no reason to, so don't bother. // there's no reason to, so don't bother.
if (result.method_signature->has_callback() && if (result.method_signature->has_callback() &&
binding::IsResponseValidationEnabled()) { binding::IsResponseValidationEnabled()) {
const base::Value* callback_params = params->GetList().back().FindKeyOfType( const base::Value* callback_params =
returns_async ? returns_async->FindKeyOfType("parameters",
base::Value::Type::LIST)
: params->GetList().back().FindKeyOfType(
"parameters", base::Value::Type::LIST); "parameters", base::Value::Type::LIST);
if (callback_params) { if (callback_params) {
const base::ListValue* params_as_list = nullptr; const base::ListValue* params_as_list = nullptr;
...@@ -531,6 +539,9 @@ void APIBinding::DecorateTemplateWithProperties( ...@@ -531,6 +539,9 @@ void APIBinding::DecorateTemplateWithProperties(
} }
} }
// static
bool APIBinding::enable_promise_support_for_testing = false;
// static // static
void APIBinding::GetEventObject( void APIBinding::GetEventObject(
v8::Local<v8::Name> property, v8::Local<v8::Name> property,
......
...@@ -83,6 +83,11 @@ class APIBinding { ...@@ -83,6 +83,11 @@ class APIBinding {
APIBindingHooks* hooks() { return binding_hooks_.get(); } APIBindingHooks* hooks() { return binding_hooks_.get(); }
// Global bool to allow for testing of promise support.
// TODO(tjudkins): Replace this with a runtime determined condition gated on
// MV3.
static bool enable_promise_support_for_testing;
private: private:
// Initializes the object_template_ for this API. Called lazily when the // Initializes the object_template_ for this API. Called lazily when the
// first instance is created. // first instance is created.
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "extensions/renderer/bindings/api_binding.h" #include "extensions/renderer/bindings/api_binding.h"
#include "base/auto_reset.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -1672,21 +1673,22 @@ TEST_F(APIBindingUnittest, ...@@ -1672,21 +1673,22 @@ TEST_F(APIBindingUnittest,
// Tests promise-based APIs exposed on bindings. // Tests promise-based APIs exposed on bindings.
TEST_F(APIBindingUnittest, PromiseBasedAPIs) { TEST_F(APIBindingUnittest, PromiseBasedAPIs) {
// TODO(tjudkins): Remove this once promise support is fully integrated into
// the API calling flow.
base::AutoReset<bool> auto_reset(
&APIBinding::enable_promise_support_for_testing, true);
constexpr char kFunctions[] = constexpr char kFunctions[] =
R"([{ R"([{
'name': 'supportsPromises', 'name': 'supportsPromises',
'supportsPromises': true,
'parameters': [{ 'parameters': [{
'name': 'int', 'name': 'int',
'type': 'integer' 'type': 'integer'
}, { }],
'name': 'callback', "returns_async": {
'type': 'function',
'parameters': [{
'name': 'strResult', 'name': 'strResult',
'type': 'string' 'type': 'string'
}] }
}]
}])"; }])";
SetFunctions(kFunctions); SetFunctions(kFunctions);
...@@ -1701,7 +1703,7 @@ TEST_F(APIBindingUnittest, PromiseBasedAPIs) { ...@@ -1701,7 +1703,7 @@ TEST_F(APIBindingUnittest, PromiseBasedAPIs) {
R"((function(api) { R"((function(api) {
this.apiResult = api.supportsPromises(3); this.apiResult = api.supportsPromises(3);
this.apiResult.then((strResult) => { this.apiResult.then((strResult) => {
this.strResult = strResult; this.promiseResult = strResult;
}); });
}))"; }))";
v8::Local<v8::Function> promise_api_call = v8::Local<v8::Function> promise_api_call =
...@@ -1723,8 +1725,29 @@ TEST_F(APIBindingUnittest, PromiseBasedAPIs) { ...@@ -1723,8 +1725,29 @@ TEST_F(APIBindingUnittest, PromiseBasedAPIs) {
EXPECT_EQ(v8::Promise::kFulfilled, promise->State()); EXPECT_EQ(v8::Promise::kFulfilled, promise->State());
EXPECT_EQ(R"("foo")", V8ToString(promise->Result(), context)); EXPECT_EQ(R"("foo")", V8ToString(promise->Result(), context));
EXPECT_EQ(R"("foo")", GetStringPropertyFromObject(context->Global(), EXPECT_EQ(R"("foo")", GetStringPropertyFromObject(
context, "strResult")); context->Global(), context, "promiseResult"));
}
// Also test that promise-based APIs still support passing a callback.
{
constexpr char kFunctionCall[] =
R"((function(api) {
api.supportsPromises(3, (strResult) => {
this.callbackResult = strResult
});
}))";
v8::Local<v8::Function> promise_api_call =
FunctionFromString(context, kFunctionCall);
v8::Local<v8::Value> args[] = {binding_object};
RunFunctionOnGlobal(promise_api_call, context, base::size(args), args);
ASSERT_TRUE(last_request());
request_handler()->CompleteRequest(last_request()->request_id,
*ListValueFromString(R"(["bar"])"),
std::string());
EXPECT_EQ(R"("bar")", GetStringPropertyFromObject(
context->Global(), context, "callbackResult"));
} }
} }
......
...@@ -19,10 +19,9 @@ namespace extensions { ...@@ -19,10 +19,9 @@ namespace extensions {
namespace { namespace {
bool HasCallback(const std::vector<std::unique_ptr<ArgumentSpec>>& signature) { bool HasCallback(const std::vector<std::unique_ptr<ArgumentSpec>>& signature) {
// TODO(devlin): This is how extension APIs have always determined if a // TODO(tjudkins): Once we change the APISignature to represent the whole
// function has a callback, but it seems a little silly. In the long run (once // signature including any asynchronous return, we should replace this with a
// signatures are generated), it probably makes sense to indicate this // method on the APISignature object itself.
// differently.
return !signature.empty() && return !signature.empty() &&
signature.back()->type() == ArgumentType::FUNCTION; signature.back()->type() == ArgumentType::FUNCTION;
} }
...@@ -409,6 +408,27 @@ APISignature::APISignature(const base::ListValue& specification) { ...@@ -409,6 +408,27 @@ APISignature::APISignature(const base::ListValue& specification) {
has_callback_ = HasCallback(signature_); has_callback_ = HasCallback(signature_);
} }
APISignature::APISignature(const base::Value& specification_list,
bool supports_promises) {
auto size = specification_list.GetList().size() + (supports_promises ? 1 : 0);
signature_.reserve(size);
for (const auto& value : specification_list.GetList()) {
CHECK(value.is_dict());
signature_.push_back(std::make_unique<ArgumentSpec>(value));
}
// To allow promise supporting APIs to instead take a callback, we add an
// allowed function to the end of the signature.
if (supports_promises) {
auto callback = std::make_unique<ArgumentSpec>(ArgumentType::FUNCTION);
callback->set_name("callback");
signature_.push_back(std::move(callback));
}
has_callback_ = HasCallback(signature_);
DCHECK(!supports_promises || has_callback_)
<< "If an API supports promises, it must also support callbacks";
}
APISignature::APISignature(std::vector<std::unique_ptr<ArgumentSpec>> signature) APISignature::APISignature(std::vector<std::unique_ptr<ArgumentSpec>> signature)
: signature_(std::move(signature)), : signature_(std::move(signature)),
has_callback_(HasCallback(signature_)) {} has_callback_(HasCallback(signature_)) {}
......
...@@ -28,6 +28,7 @@ class ArgumentSpec; ...@@ -28,6 +28,7 @@ class ArgumentSpec;
class APISignature { class APISignature {
public: public:
explicit APISignature(const base::ListValue& specification); explicit APISignature(const base::ListValue& specification);
APISignature(const base::Value& specification_list, bool supports_promises);
explicit APISignature(std::vector<std::unique_ptr<ArgumentSpec>> signature); explicit APISignature(std::vector<std::unique_ptr<ArgumentSpec>> signature);
~APISignature(); ~APISignature();
......
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